2011-07-20 14 views
5

Tôi đã tình cờ gặp một vấn đề OCaml khá đơn giản, nhưng tôi dường như không thể tìm thấy một giải pháp thanh lịch. Tôi đang làm việc với các hàm được áp dụng cho các mô-đun tương đối đơn giản (chúng thường định nghĩa một kiểu và một vài hàm trên kiểu đó) và mở rộng các mô-đun đơn giản đó bằng cách thêm các hàm, kiểu và mô-đun phức tạp hơn. Một phiên bản đơn giản sẽ là:Mô-đun và trường ghi

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 

Không cần để cung cấp cho các mô-đun đơn giản một tên bởi vì tất cả chức năng của nó hiện diện trong các mô-đun mở rộng, và các chức năng trong module đơn giản được tạo ra bởi camlp4 dựa trên loại . Việc sử dụng thành ngữ của những functors là:

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

Vấn đề xuất hiện khi tôi đang làm việc với các bản ghi:

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

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

Có vẻ là không có cách nào đơn giản truy cập vào các lĩnh vực xy định nghĩa ở trên từ bên ngoài mô-đun Point2D, chẳng hạn như location.x hoặc location.Point2D.x. Làm thế nào tôi có thể đạt được điều này?

EDIT: theo yêu cầu, đây là một ví dụ rất nhỏ hoàn toàn có hiển thị các vấn đề:

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

Viết mã có thể biên dịch rất nhiều sẽ giúp nhận được câu trả lời có tính tổng hợp. –

Trả lời

3

Trước hết, trong mẫu mã cuối cùng của bạn, dòng cuối cùng, bạn có thể có nghĩa là .a hơn .x.

Vấn đề với mã của bạn là, với cách bạn xác định Make functor của bạn, loại t là trừu tượng trong Made: thật vậy, các functors sử dụng chữ ký TYPE mà kín {a : int} như một loại trừu tượng.

Thiết kế sau đây phá vỡ vấn đề, nhưng, tốt, đó là một thiết kế khác.

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

Vấn đề là OCaml không có một tên để chỉ các thành phần chất lượng của các loại t (trong trường hợp này là một kỷ lục, nhưng cùng một vấn đề sẽ có mặt với phiên bản bình thường) bên ngoài Made. Đặt tên giấu tên giải quyết vấn đề:

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

module Made = Make(F) 

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

Bạn cũng có thể tuyên bố một cách rõ ràng các loại bên ngoài ứng dụng functorial:

type rcd = {a : int} 

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

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

Cảm ơn bạn đã trả lời. Tôi có thể sử dụng giải pháp đầu tiên của bạn, nhưng tôi đang tìm kiếm một cái gì đó thanh lịch hơn là giữ một module 'F' xung quanh chỉ để truy cập vào các lĩnh vực chính mình (như mô-đun đó sẽ không bao giờ được sử dụng cho bất cứ điều gì khác). Giải pháp thứ hai sẽ không hoạt động, vì 'let default' được tạo và xuất hiện ngay sau định nghĩa kiểu, vì vậy tôi sẽ phải sao chép nó vào mô-đun theo cách thủ công (trên thực tế, nó thực sự là một vài giá trị, không chỉ một, sao chép chúng trên là khá tiết). –

4

Hãy nhìn vào chữ ký của một số các module liên quan. Đây là những chữ ký được tạo ra bởi Ocaml, và chúng là chữ ký chính, tức là chúng là chữ ký chung nhất được cho phép bởi lý thuyết.

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 

Thông báo như thế nào phương trình Make(A).t = A.t được giữ lại (vì vậy Make(A).t là một loại chữ viết tắt trong suốt), nhưng Made.t là trừu tượng. Điều này là do Made là kết quả của việc áp dụng hàm functor cho một cấu trúc ẩn danh, do đó không có tên chuẩn cho kiểu đối số trong trường hợp này.

Loại bản ghi mang tính sinh sản.Ở cấp độ của lý thuyết kiểu cơ bản, tất cả các kiểu sinh sản đều hoạt động giống như các kiểu trừu tượng với một số đường cú pháp cho các hàm tạo và hàm hủy. Cách duy nhất để chỉ định một loại sinh sản là đặt tên của nó, hoặc là tên gốc hoặc một tên mở rộng đến tên gốc thông qua một loạt các phương trình kiểu.

Hãy xem xét những gì sẽ xảy ra nếu bạn lặp lại trong các định nghĩa của 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) 

Bạn nhận được hai loại khác nhau Made1.tMade2.t, mặc dù hai bên cánh tay phải của các định nghĩa đều giống nhau. Đó là tất cả những gì về tính tổng quát.

Made.t là trừu tượng, đây không phải là loại bản ghi. Nó không có bất kỳ hàm tạo nào. Các nhà thầu đã bị mất khi đối số cấu trúc bị đóng, vì thiếu tên.

Điều đó xảy ra với các bản ghi, người ta thường muốn đường cú pháp chứ không phải là tính tổng quát. Nhưng Ocaml không có bất kỳ loại bản ghi cấu trúc nào. Nó có các loại bản ghi sinh học, và nó có các đối tượng, mà từ một loại lý thuyết xem hồ sơ nhưng trong thực tế có thể là một ít công việc để sử dụng và có một hình phạt hiệu suất nhỏ.

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

Hoặc, nếu bạn muốn giữ nguyên định nghĩa loại, bạn cần phải cung cấp tên cho loại và công trình xây dựng, có nghĩa là đặt tên cấu trúc.

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

Lưu ý rằng nếu bạn xây dựng Make(A) hai lần, bạn sẽ có cùng loại xung quanh.

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

(Ok, điều này là không đáng kể ở đây, nhưng bạn vẫn muốn nhận được cùng một trừu tượng loại trong MadeA1MakeA2, không giống như các trường hợp Made1Made2 trên. Đó là bởi vì bây giờ có một tên cho các các loại: MadeA1.t = Make(A).t.)

+0

Giải thích tuyệt vời. Đặt tên trên các khái niệm ("generative") đã giúp rất nhiều ngay cả khi nó không mang đến một giải pháp thực sự thanh lịch cho vấn đề ban đầu. –

Các vấn đề liên quan