Избавление от конструкторов вариантов - PullRequest
0 голосов
/ 20 ноября 2018

В качестве побочного проекта я пытаюсь реализовать основы библиотеки RDF в OCaml.

Как вы можете (или не можете знать), оператор RDF (или тройной) состоит из 3 частей:

  • субъект может быть 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?

Ответы [ 2 ]

0 голосов
/ 24 ноября 2018

Если вы не возражаете против изменения типов 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]).

0 голосов
/ 20 ноября 2018

Я не уверен, как вы можете полностью достичь этого даже с GADT. Каким будет тип create в этом случае? Первый аргумент должен быть либо Iri.t, либо Bnode.t, если один не является подтипом другого, вы не можете написать такую ​​функцию (или она будет очень общей: 'a -> ...).

В любом случае вам необходимо указать тип аргументов. Что вы можете сделать с помощью GADT - это переместить информацию о типах в другой тип:

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

Но я действительно сомневаюсь, что это лучшая версия, чем оригинальная.

...