摆脱变体构造函数
Getting rid of variant constructors
作为业余项目,我尝试在 OCaml 中实现 RDF 库的基础知识。
您可能知道(也可能不知道),一个 RDF 语句(或三元组)由 3 部分组成:
- subject可以是IRI也可以是空节点;
- 谓词必须是一个 IRI;
- 对象可以是IRI、空白节点或文字。
我有 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.t
或 Bnode.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]
)。
作为业余项目,我尝试在 OCaml 中实现 RDF 库的基础知识。
您可能知道(也可能不知道),一个 RDF 语句(或三元组)由 3 部分组成:
- subject可以是IRI也可以是空节点;
- 谓词必须是一个 IRI;
- 对象可以是IRI、空白节点或文字。
我有 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.t
或 Bnode.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]
)。