摆脱变体构造函数

Getting rid of variant constructors

作为业余项目,我尝试在 OCaml 中实现 RDF 库的基础知识。

您可能知道(也可能不知道),一个 RDF 语句(或三元组)由 3 部分组成:

我有 IRI、空白节点和文字的模块和类型,为了对上述规则进行类型验证,这是我开始写的内容:

(* 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

所以这一切都很好,但有一件事让我很烦恼:每当我想使用 Triple.create 时,我必须明确说明变体的构造函数:

let iri = (* Some Iri.t value *) in
let literal = (* Literal.t value *) in
Triple.create (Iri iri) iri (Literal literal)

我很确定 OCaml 有办法解决这个问题,但我不确定如何解决。

一些想法:我可以用它的主体类型和它的客体类型参数化 Triple.t 类型,但是我如何强制执行对参数类型的限制?或者这可能是 GADT 的一个很好的用例?

我不确定即使使用 GADT 也能完全实现这一点。在这种情况下 create 的类型是什么?第一个参数必须是 Iri.tBnode.t 除非一个是另一个的子类型,你不能写这样的函数(否则它会很笼统:'a -> ...)。

在任何情况下,您都需要提供参数的类型。你可以用 GADT 做的是 "move" 将有关类型的信息转换为另一种类型:

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编码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

但我真的怀疑这个版本是否比原版更好。

如果您不介意更改 Iri.t 的类型等,您可以这样做(在每种情况下将 internal = string 替换为实际类型):

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

请注意,在最后的示例中,相同的值 bob 既用作主语 ([Iri.t | Bnode.t]) 又用作宾语 ([Iri.t | Bnode.t | Literal.t])。