Richard-Degenne
Richard-Degenne

Reputation: 2949

Getting rid of variant constructors

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

Answers (2)

Thomas Leonard
Thomas Leonard

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

vonaka
vonaka

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

Related Questions