2011-07-20 20 views
5

Natknąłem się na dość prosty problem z OCaml, ale nie mogę znaleźć eleganckiego rozwiązania. Pracuję z funktorami, które są stosowane do względnie prostych modułów (zazwyczaj definiują typ i kilka funkcji tego typu) i rozszerzają te proste moduły o dodatkowe, bardziej złożone funkcje, typy i moduły. Uproszczona wersja będzie:Moduły i pola rekordów

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 

Nie ma potrzeby, aby dać prosty moduł nazwę, ponieważ wszystkie jego funkcjonalność jest obecna w rozszerzonym modułem, a funkcje w prosty moduł są generowane przez camlp4 w zależności od typu . Idiomatyczne wykorzystanie tych funktorów jest:

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

Problem pojawia się, gdy pracuję z zapisów:

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

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

Wydaje się, że nie prosty sposób dostępu pola x i y określono powyżej od poza modułem Point2D, takim jak location.x lub location.Point2D.x. Jak mogę to osiągnąć?

EDIT: zgodnie z wnioskiem, oto pełna minimalny przykład, który pokazuje problem:

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 *) 
+1

Zamieszczanie kodu umożliwiającego kompilację bardzo pomogłoby uzyskać kompilowalne odpowiedzi. –

Odpowiedz

3

Przede wszystkim, w swojej ostatniej próbki kodu, ostatnia linia, to prawdopodobnie znaczy .a zamiast .x.

Problem z kodem jest, że przy okazji można zdefiniować swoją Make funktor, typ t jest abstrakcyjna w Made: rzeczywiście, funktory użyć TYPE sygnaturę, która uszczelnia {a : int} jako abstrakcyjne typu.

Poniższy projekt omija problem, ale jego konstrukcja jest inna.

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

Problemem jest to, że OCaml nie ma nazwy, aby odnieść się do wykwalifikowanych komponentów typu t (w tym przypadku rekord, ale ten sam problem będzie obecny przy normalnych wariantów) poza Made. Naming bezimienny rozwiązuje problem:

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

module Made = Make(F) 

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

Można również zadeklarować jednoznacznie typ poza functorial aplikacji:

type rcd = {a : int} 

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

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

Dziękuję za odpowiedź. Mógłbym użyć twojego pierwszego rozwiązania, ale szukam czegoś bardziej eleganckiego niż trzymanie modułu 'F' tylko po to, aby uzyskać dostęp do samych pól (ponieważ ten moduł nigdy nie będzie używany do niczego innego). Drugie rozwiązanie nie zadziałałoby, ponieważ "let default" jest generowany i pojawia się zaraz po definicji typu, więc musiałbym ręcznie skopiować go do modułu (w praktyce jest to w rzeczywistości kilka wartości, a nie tylko jeden, więc ich kopiowanie jest dość szczegółowe). –

4

Spójrzmy na podpisanie niektórych modułów zaangażowanych. Są to sygnatury generowane przez Ocaml i są to główne sygnatury, tj. Są to najbardziej ogólne podpisy dopuszczone przez teorię.

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 

Wskazówki jak równanie Make(A).t = A.t jest zachowana (tak Make(A).t jest przezroczysty typ skrót), jeszcze Made.t jest abstrakcyjny. Dzieje się tak, ponieważ Made jest wynikiem zastosowania funktora do anonimowej struktury, więc w tym przypadku nie ma kanonicznej nazwy typu argumentu.

Typy rekordów są generatywne.Na poziomie podstawowej teorii typów wszystkie typy generatywne zachowują się jak typy abstrakcyjne z pewnym cukrem syntaktycznym dla konstruktorów i destruktorów. Jedynym sposobem na określenie typu generatywnego jest podanie jego nazwy, oryginalnej lub takiej, która rozwija się do oryginalnej nazwy za pomocą szeregu równań typu.

Zastanów się, co się dzieje, jeśli duplikat definicji 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) 

Dostajesz dwa różne typy Made1.t i Made2.t, choć boki prawej definicji są takie same. Na tym właśnie polega generatywność.

Ponieważ Made.t jest abstrakcyjna, nie jest to typ rekordu. Nie ma żadnego konstruktora. Konstruktory zostały utracone, gdy argument struktury został zamknięty, z powodu braku nazwy.

Tak się składa, że ​​z zapisami często chce się cukru syntaktycznego, ale nie generatywności. Ale Ocaml nie ma żadnych typów rekordów strukturalnych. Ma generatywne typy rekordów i posiada obiekty, które z teoretycznego widoku typu podbijają rekordy, ale w praktyce może być trochę więcej pracy do wykorzystania i mają małą karę wykonania.

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

Albo, jeśli chcesz zachować tę samą definicję typu, trzeba podać nazwę dla danego typu i jej konstruktorów, co oznacza strukturę nazewnictwa.

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

Zauważ, że jeśli budować Make(A) dwukrotnie, masz te same typy dookoła.

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

(Ok, to nie jest niezwykłe tutaj, ale że wciąż te same abstrakcyjnych typów w MadeA1 i MakeA2, inaczej niż w przypadku Made1 i Made2 powyżej. To dlatego, że teraz nie jest to nazwa dla nich typy: MadeA1.t = Make(A).t.)

+0

Świetne wyjaśnienie. Umieszczenie nazwy na koncepcjach ("generative") pomogło ogromnie, nawet jeśli nie przyniosło rzeczywistego eleganckiego rozwiązania pierwotnego problemu. –