Модули и поля записи - PullRequest
5 голосов
/ 20 июля 2011

Я наткнулся на довольно простую проблему с OCaml, но не могу найти элегантного решения.Я работаю с функторами, которые применяются к относительно простым модулям (они обычно определяют тип и несколько функций для этого типа) и расширяют эти простые модули, добавляя дополнительные более сложные функции, типы и модули.Упрощенная версия будет выглядеть так:

module type SIMPLE = sig
  type t
  val  to_string : t -> string
  val  of_string : string -> t
end

module Complex = functor (S:SIMPLE) -> struct
  include S
  let write db id t = db # write id (S.to_string t)
  let read db id = db # read id |> BatOption.map S.of_string
end 

Нет необходимости давать имя простому модулю, поскольку все его функциональные возможности присутствуют в расширенном модуле, а функции в простом модуле генерируются camlp4 на основетип.Идиоматическое использование этих функторов:

module Int = Complex(struct
  type t = int
end)

Проблема возникает, когда я работаю с записями:

module Point2D = Complex(struct
  type t = { x : int ; y : int }
end)

let (Some location) = Point2D.read db "location"

Кажется, нет простого способа доступа к xи поля y, определенные выше вне модуля Point2D, такие как location.x или location.Point2D.x.Как мне этого добиться?

РЕДАКТИРОВАТЬ : в соответствии с запросом приведен полный минимальный пример, отображающий проблему:

module type TYPE = sig
  type t 
  val  default : t
end 

module Make = functor(Arg : TYPE) -> struct
  include Arg
  let get = function None -> default | Some x -> (x : t)
end

module Made = Make(struct
  type t = {a : int}
  let default = { a = 0 } (* <-- Generated by camlp4 based on type t above *)
end)

let _ = (Made.get None).a (* <-- ERROR *)

Ответы [ 3 ]

4 голосов
/ 21 июля 2011

Давайте посмотрим на сигнатуру некоторых задействованных модулей.Это сигнатуры, сгенерированные Ocaml, и они являются основными сигнатурами, т.е. они являются наиболее общими сигнатурами, допускаемыми теорией.

module Make : functor (Arg : TYPE) -> sig
  type t = Arg.t
  val default : t
  val get : t option -> t
end
module Made : sig
  type t
  val default : t
  val get : t option -> t
end

Обратите внимание, как сохраняется уравнение Make(A).t = A.t (поэтому Make(A).tявляется прозрачным сокращением типа), но Made.t является абстрактным.Это связано с тем, что Made является результатом применения функтора к анонимной структуре, поэтому в данном случае нет канонического имени для типа аргумента.

Типы записей являются порождающими.На уровне базовой теории типов все порождающие типы ведут себя как абстрактные типы с некоторым синтаксическим сахаром для конструкторов и деструкторов.Единственный способ обозначить порождающий тип - дать его имя, либо оригинальное имя, либо расширение, которое расширяется до исходного имени с помощью ряда уравнений типа.

Подумайте, что произойдет, если вы продублируете определение Made:

module Made1 = Make(struct
    type t = {a : int}
    let default = { a = 0 } (* <-- Generated by camlp4 based on type t above *)
  end)
module Made2 = Make(struct
    type t = {a : int}
    let default = { a = 0 } (* <-- Generated by camlp4 based on type t above *)
  end)

Вы получаете два разных типа Made1.t и Made2.t, хотя правые части определений одинаковы.Вот что такое генеративность.

Поскольку Made.t является абстрактным, это не тип записи.У него нет конструктора.Конструкторы были потеряны, когда аргумент структуры был закрыт из-за отсутствия имени.

Бывает, что с записями часто требуется синтаксический сахар, но не порождаемость.Но у Окамла нет структурных типов записей.У него есть генеративные типы записей, и у него есть объекты, которые с теоретической точки зрения типа включают записи, но на практике может потребоваться немного больше усилий для использования и небольшого снижения производительности.

module Made_object = Make(struct
    type t = <a : int>
    let default = object method a = 0 end
  end)

Или, если выЕсли вы хотите сохранить одно и то же определение типа, вам нужно предоставить имя для типа и его конструкторов, что означает присвоение имени структуре.

module A = struct
    type t = {a : int}
    let default = { a = 0 } (* <-- Generated by camlp4 based on type t above *)
  end
module MadeA = Make(A)

Обратите внимание, что если вы дважды создаете Make(A), вы получаете то же самоетипы вокруг.

module MadeA1 = Make(A)
module MadeA2 = Make(A)

(Хорошо, здесь это не замечательно, но вы все равно получите те же абстрактные типы в MadeA1 и MakeA2, в отличие отMade1 и Made2 случай выше. Это потому, что теперь есть имя для этих типов: MadeA1.t = Make(A).t.)

3 голосов
/ 20 июля 2011

Прежде всего, в последнем примере кода, в последней строке вы, вероятно, имеете в виду .a, а не .x.

Проблема с вашим кодом заключается в том, как вы определяете Make functor, тип t является абстрактным в Made: действительно, функторы используют TYPE подпись, которая запечатывает {a : int} в качестве абстрактного типа.

Следующий дизайн обходит проблему, но,ну, это другой дизайн.

module type TYPE = sig
  type t 
  val  default : t
end 

module Extend = functor(Arg : TYPE) -> struct
  open Arg
  let get = function None -> default | Some x -> (x : t)
end

module T = struct
  type t = {a : int}
  let default = { a = 0 }
end

module Made = struct
  include T
  include Extend(T)
end

let _ = Made.((get None).a)
1 голос
/ 21 июля 2011

Проблема в том, что у OCaml нет имени для ссылки на квалифицированные компоненты типа t (в данном случае запись, но такая же проблема будет присутствовать с обычными вариантами) за пределами Made. Именование безымянного решает проблему:

module F = struct
  type t = {a : int}
  let default = { a = 0 }
end

module Made = Make(F)

let _ = (Made.get None).F.a (* <-- WORKS *)

Вы также можете явно объявить тип вне функториального приложения:

type rcd = {a : int}

module Made = Make(struct
  type t = rcd
  let default = { a = 0 }
end)

let _ = (Made.get None).a (* <-- WORKS *)
...