using CitableBase
struct Isbn10Urn <: Urn
::AbstractString
isbnend
Define your own URN type
Overview
ISBN numbers uniquely identify published editions of a book. In this tutorial, we will create a new URN type representing a 10-digit ISBN number. We will implement the UrnComparisonTrait
for our new URN so that we can compare ISBN numbers using URN logic.
(1) Defining the Isbn10Urn
type
The Urn
abstract type models a Uniform Resource Name (URN). We’ll follow the requirements of the URN standard to create a URN type for ISBN-10 numbers. Its URN strings will have three colon-delimited components, beginning with the required prefix urn
, then a URN type we’ll call isbn10
, followed by a 10-digit ISBN number. For example, the URN for Distant Horizons by Ted Underwood will be urn:isbn10:022661283X
. (Yes, the last “digit” of an ISBN number can be X
.)
We will make the new type a subtype of Urn
, so that we can use it freely with other packages that recognize URNs.
!!! warning “Note on the ISBN format and our Isbn10Urn
type”
There is in fact a URN namespace for ISBN numbers identified by the `isbn` namespace identifier. (See this [blogpost about citing publications with URNs](https://www.benmeadowcroft.com/webdev/articles/urns-and-citations/).) This guide invents an `isbn10` URN type solely to illustrate how you could create your own URN type using the `CitableBase` package.
Parsing the full [ISBN-10 format](https://en.wikipedia.org/wiki/International_Standard_Book_Number) is extremely complicated: ISBN-10 numbers have four components, each of which is variable in length! In this user's guide example, we'll restrict ourselves to ISBNs for books published in English-, French- or German-speaking countries, indicated by an initial digit of `0` or `1` (English), `2` (French) or `3` (German). In a real program, we would enforce this in the constructor, but to keep our example brief and focused on the `CitableBase` class, we blindly accept any string value for the `isbn` field of our type.
Our new type is a subtype of Urn
.
supertype(Isbn10Urn)
Urn
As often in Julia, we’ll override the default show
method for our type. (Note that in Julia this requires importing the specific method, not just using the package.)
import Base: show
function show(io::IO, u::Isbn10Urn)
print(io, u.isbn)
end
show (generic function with 325 methods)
Now when we create objects of our new type, the display in our REPL (or other contexts) will be easily recognizable as an Isbn10Urn
.
= Isbn10Urn("urn:isbn10:022661283X") distanthorizons
urn:isbn10:022661283X
(2) Defining the UrnComparisonTrait
Subtypes of Urn
are required to implement the UrnComparisonTrait
, and its three functions. CitableBase
uses the “Holy trait trick” to dispatch functions implementing URN comparison.
See this post on julia bloggers for an introduction to the “Tim Holy Trait Trick” (THTT).
We first define a subtype of the abstract UrnComparisonTrait
. It’s a singleton type with no fields which we’ll use as the trait value for our ISBN type. CitableBase
provides the urncomparisontrait
function to determine if a class implements the UrnComparisonTrait
so we’ll import urncomparisontrait
, and define a function returning a concrete value of IsbnComparable()
for the type Isbn10Urn
.
struct IsbnComparable <: UrnComparisonTrait end
import CitableBase: urncomparisontrait
function urncomparisontrait(::Type{Isbn10Urn})
IsbnComparable()
end
urncomparisontrait (generic function with 2 methods)
Let’s test it.
urncomparisontrait(typeof(distanthorizons))
IsbnComparable()
This lets us use CitableBase
s boolean function urncomparable
to test specific objects.
urncomparable(distanthorizons)
true
Implementing the logic of URN comparison
To fulfill the contract of the UrnComparisonTrait
, we must implement three boolean functions for three kinds of URN comparison: urnequals
(for equality), urncontains
(for containment) and and urnsimilar
(for similarity). Because we have defined our type as implementing the UrnComparisonTrait
, CitableBase
can dispatch to functions including an Isbn10Urn
as the first parameter.
Equality
The ==
function of Julia Base is overridden in CitableBase
for all subtypes of Urn
. This makes it trivial to implement urnequals
once we use CitableBase
and import urnequals
.
import CitableBase: urnequals
function urnequals(u1::Isbn10Urn, u2::Isbn10Urn)
== u2
u1 end
urnequals (generic function with 3 methods)
= distanthorizons
dupe urnequals(distanthorizons, dupe)
true
= Isbn10Urn("urn:isbn10:022656875X")
enumerations urnequals(distanthorizons, enumerations)
false
Our implementation of urnequals
uses two parameters of the same type to compare two URNs and produce a boolean result. In the following section, we will implement the functions of UrnComparisonTrait
with one URN parameter and one parameter giving a citable collection. In those implementations, we can filter the collection by comparing the URN parameter to the URNs of items in the collection. We will reserve ==
for comparing the contents of two collections, and use urnequals
to filter a collection’s content.
Containment
For our ISBN type, we’ll define “containment” as true when two ISBNS belong to the same initial-digit group (0
- 4
). We’ll use the components
functions from CitableBase
to extract the third part of each URN string, and compare their first characters.
import CitableBase: urncontains
function urncontains(u1::Isbn10Urn, u2::Isbn10Urn)
= components(u1.isbn)[3][1]
initial1 = components(u2.isbn)[3][1]
initial2
== initial2
initial1 end
urncontains (generic function with 2 methods)
Both Distant Horizons and Enumerations are in ISBN group 0.
urncontains(distanthorizons, enumerations)
true
But Can We Be Wrong? is in ISBN group 1.
= Isbn10Urn("urn:isbn10:1108922036")
wrong urncontains(distanthorizons, wrong)
false
Similarity
We’ll define “similarity” as belonging to the same language area. In this definition, both 0
and 1
indicate English-language countries.
# True if ISBN starts with `0` or `1`
function english(urn::Isbn10Urn)
= components(urn.isbn)[3][1]
langarea == '0' || langarea == '1'
langarea end
import CitableBase: urnsimilar
function urnsimilar(u1::Isbn10Urn, u2::Isbn10Urn)
= components(u1.isbn)[3][1]
initial1 = components(u2.isbn)[3][1]
initial2
english(u1) && english(u2)) || initial1 == initial2
(end
urnsimilar (generic function with 2 methods)
Both Distant Horizons and Can We Be Wrong? are published in English-language areas.
urnsimilar(distanthorizons, wrong)
true
But they are coded for different ISBN areas.
= Isbn10Urn("urn:isbn10:1108922036")
wrong urncontains(distanthorizons, wrong)
false
Recap: identifiers
On this page, we defined the Isnb10Urn
type as a subtype of Urn
and identified our type as implementing the UrnComparisonTrait
. You can test this with urncomparable
s.
@example urns urncomparable(distanthorizons)
We implemented the trait’s required functions to compare pairs of URNs based on URN logic for equality, similarity and containment, and return boolean values.
The next tutorial will make use of our URN type to define a citable object identified by Isbn10Urn
.