我正在尝试使用相互递归模块定义一个变量,假设一个Todo可以有多个Note,而一个Note可以属于Todo:
module Sig = {
module type NoteSig = {type t;};
module type TodoSig = {type t;};
};
/* same file */
module Same = {
module rec Note: Sig.NoteSig = {
type t = {todo: Todo.t};
}
and Todo: Sig.TodoSig = {
type t = {
text: string,
notes: array(Note.t),
};
};
};
/* different files */
module A = {
module Note = (T: Sig.TodoSig) => {
type t = {todo: T.t};
};
};
module B = {
module Todo = (N: Sig.NoteSig) => {
type t = {notes: array(N.t)};
};
};
module C = {
module rec NoteImpl: Sig.NoteSig = A.Note(TodoImpl)
and TodoImpl: Sig.TodoSig = B.Todo(NoteImpl);
};
/* impl */
let todo1: Same.Todo.t = {notes: [||]};
let todo2: C.TodoImpl.t = {notes: [||]};
let todo3 = Same.Todo.{notes: [||]};
let todo4 = C.TodoImpl.{notes: [||]};
Js.log2(todo1, todo2);
但是我不能用这种类型定义变量,编译器会告诉:
36 │
37 │ /* impl */
38 │ let todo1: Same.Todo.t = {notes: [||]};
39 │ let todo2: C.TodoImpl.t = {notes: [||]};
40 │
The record field notes can't be found.
If it's defined in another module or file, bring it into scope by:
- Annotating it with said module name: let baby = {MyModule.age: 3}
- Or specifying its type: let baby: MyModule.person = {age: 3}
Ocaml中的相同代码(如果有帮助的话):
module Sig =
struct
module type NoteSig = sig type t end
module type TodoSig = sig type t end
end
module Same =
struct
module rec Note:Sig.NoteSig = struct type t = {
todo: Todo.t;} end
and Todo:Sig.TodoSig =
struct type t = {
text: string;
notes: Note.t array;} end
end
module A =
struct module Note(T:Sig.TodoSig) = struct type t = {
todo: T.t;} end end
module B =
struct
module Todo(N:Sig.NoteSig) = struct type t = {
notes: N.t array;} end
end
module C =
struct
module rec NoteImpl:Sig.NoteSig = A.Note(TodoImpl)
and TodoImpl:Sig.TodoSig = B.Todo(NoteImpl)
end
let todo1: Same.Todo.t = { notes = [||] }
let todo2: C.TodoImpl.t = { notes = [||] }
let todo3 = let open Same.Todo in { notes = [||] }
let todo4 = let open C.TodoImpl in { notes = [||] }
let _ = Js.log2 todo1 todo2
对于长代码很抱歉,请在下面丢弃这些行。
答案 0 :(得分:2)
首先,最简单的解决方案是使类型相互递归
type todo = { text:string, type todos:array(todo) }
and note = { todo:todo }
如果您确实需要将这两种类型拆分为单独的模块,则确实需要使用它们的递归模块。
在这种情况下,关键思想是签名代表模块内容的完整规范,换句话说就是签名
module type T = { type t }
是一个模块的规范,该模块实现了黑盒类型t
,而没有。
因此,签名约束Note:Sig.NoteSig
和Todo:TodoSig
在
module rec Note:Sig.NoteSig = { type t = { todo: Todo.t} }
and Todo:Sig.TodoSig = {
type t = { text: string, notes: array(Note.t)}
}
实际上正在擦除有关Note.t和Todo.t的实际实现的所有信息。
您要先写完整签名:
module type NoteSig = { type todo; type t = {todo: todo} }
module type TodoSig = {
type note;
type t = { text: string, notes: array(note)}
}
然后您可以将实现编写为
module rec Note: NoteSig with type todo := Todo.t = { type t = { todo: Todo.t} }
and Todo: TodoSig with type note := Note.t =
{ type t = { text: string, notes: array(Note.t)} }
如果模块中仅包含类型,则可以使用以下版本
module rec Note: NoteSig with type todo := Todo.t = Note
and Todo: TodoSig with type note := Note.t = Todo
对于functor版本,如果不需要模块中定义的功能,最简单的实现就是
module Make_Todo(Note: { type t;}) = {
type t = { text:string, notes:array(Note.t) }
}
module Make_Note(Todo: { type t;}) = { type t = { todo:Todo.t} }
(通常,对于初学者来说,通常最好让类型检查器来推断函子的结果类型。) 那么您可以使用
module rec Todo: TodoSig with type note := Note.t = Make_Todo(Note)
and Note : NoteSig with type todo := Todo.t = Make_Note(Todo)
如果您需要的功能不超过make functor中的其他模块,可以通过指定functor的参数实现 完整签名
module Make_Todo(Note: NoteSig) = {
type t = { text:string, notes:array(Note.t) }
}
module Make_Note(Todo: TodoSig) = { type t = { todo:Todo.t} }
但是随后模块的实例化变得更加复杂
module rec Todo: TodoSig with type note := Note.t =
Make_Todo({ include(Note); type todo = Todo.t })
and Note : NoteSig with type todo := Todo.t =
Make_Note({ include(Todo); type note = Note.t })
遇到复杂错误的风险更大。
答案 1 :(得分:0)
对于以后的访客,如果两个文件都在同一文件中,我将给出答案:
module Same = {
module rec Note: {
type t = {
title: string,
todo: option(Todo.t),
};
} = Note
and Todo: {
type t = {
title: string,
notes: option(array(Note.t)),
};
} = Todo;
};
let todo: Same.Todo.t = {
title: "helo",
notes: Some([|Same.Note.{title: "one", todo: None}|]),
};
[@bs.module "util"] external inspect : ('a, 'b) => 'c = "inspect";
Js.log(inspect(todo, {"depth": 10}));
尽管如此,我仍在寻找函子解决方案。