Reputation: 2949
As a side project, I try to implement the basics of an RDF library in OCaml.
As you may (or may not) know, a RDF statement (or triple) is composed of 3 parts:
I have module and types for IRIs, blank nodes and literals, and in order to type-proof the rules described above, here is what I've started to write:
(* In `triple.ml` *)
type subject = Iri of Iri.t | Bnode of Bnode.t
type objekt = Iri of Iri.t | Bnode of Bnode.t | Literal of Literal.t
type t = subject * Iri.t * objekt
let create s p o = s, p, o
So this is nice and everything, but one thing grinds my gears: whenever I want to use Triple.create
, I must explicitly state the constructor of the variant:
let iri = (* Some Iri.t value *) in
let literal = (* Literal.t value *) in
Triple.create (Iri iri) iri (Literal literal)
I'm pretty sure OCaml has ways to work around that, but I'm not sure how.
Some thoughts: I could parameterize the Triple.t
type with the type of its subject and the type of its object, but then how do I enforce the restrictions on the parameter types? Or maybe it is a good use case for a GADT?
Upvotes: 1
Views: 122
Reputation: 7196
If you don't mind changing the types of Iri.t
, etc, you could do something like this (replacing internal = string
with the real type in each case):
module Iri : sig
type internal
type t = [`Iri of internal]
val v : string -> [> t]
end = struct
type internal = string
type t = [`Iri of internal]
let v x = `Iri x
end
module Bnode : sig
type internal
type t = [`Bnode of internal]
val v : string -> [> t]
end = struct
type internal = string
type t = [`Bnode of internal]
let v x = `Bnode x
end
module Literal : sig
type internal
type t = [`Literal of internal]
val v : string -> [> t]
end = struct
type internal = string
type t = [`Literal of internal]
let v x = `Literal x
end
module Triple = struct
type subject = [Iri.t | Bnode.t]
type objekt = [Iri.t | Bnode.t | Literal.t]
type t = subject * Iri.t * objekt
let v s p o : t = s, p, o
end
let alice = Iri.v "alice"
let knows = Iri.v "knows"
let bob = Iri.v "bob"
let x1 = Bnode.v "blank-x1"
let foo = Literal.v "foo"
let triple1 = Triple.v alice knows bob
let triple2 = Triple.v bob knows x1
let triple3 = Triple.v bob knows foo
Note that in the example at the end, the same value bob
is used both as a subject ([Iri.t | Bnode.t]
) and as an object ([Iri.t | Bnode.t | Literal.t]
).
Upvotes: 1
Reputation: 953
I'm not sure how you can fully achieve this even with GADT. What will be the type of create
in this case? First argument must be either Iri.t
or Bnode.t
unless one is a subtype of another, you can't write such function (or it will be very general: 'a -> ...
).
In any case you need to provide a type of the arguments. What you can do with GADT is to "move" the information about the types into another type:
type 'a rdf_ty = II : (Iri.t * Iri.t) rdf_ty |
BI : (Bnode.t * Iri.t) rdf_ty |
IB : (Iri.t * Bnode.t) rdf_ty |
BB : (Bnode.t * Bnode.t) rdf_ty |
IL : (Iri.t * Literal.t) rdf_ty |
BL : (Bnode.t * Literal.t) rdf_ty
rdf_ty
encode the types of the first and third arguments of create
:
type t = subject * Iri.t * objekt
let create : type a b. (a * b) rdf_ty -> a -> Iri.t -> b -> t = fun ty s p o ->
match ty with
| II -> Iri s, p, Iri o
| BI -> Bnode s, p, Iri o
| IB -> Iri s, p, Bnode o
| BB -> Bnode s, p, Bnode o
| IL -> Iri s, p, Literal o
| BL -> Bnode s, p, Literal o
let iri = (* Some Iri.t value *) in
let literal = (* Literal.t value *) in
create IL iri iri literal
But I really doubt that this is a better version than the original one.
Upvotes: 1