ocaml - 找不到记录字段注释

标签 ocaml reason bucklescript

我正在尝试使用相互递归模块定义一个变量,假设一个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

抱歉,代码太长,请丢弃下面的这些行。

最佳答案

首先,最简单的解决方案是使类型相互递归

type todo = { text:string, type todos:array(todo) }
and note = { todo:todo } 

如果您确实需要将这两种类型拆分为单独的模块,那么递归模块确实是必要的。

在这种情况下,关键思想是签名代表模块内容的完整规范,换句话说就是签名

module type T = { type t }

是一个模块的规范,它实现了黑盒类型t并且没有其他。

因此,签名约束 Note:Sig.NoteSigTodo: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

对于仿函数版本,如果您不需要模块中定义的函数,最简单的实现就是

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 仿函数内其他模块的类型,您可以更进一步,指定仿函数的参数实现 完整签名

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 }) 

并且遇到复杂错误的风险更大。

关于ocaml - 找不到记录字段注释,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51178362/

相关文章:

ocaml - 模块依赖循环

OCaml |> 运算符

xsd - 将 XSD 中的列表读取到 OCaml 和 Coq 中的列表

带有多个参数的 Reasonml 类型,错误异常失败 ("nth")

reasonML 中的 Shebang 支持

promise - 为什么需要调用resolve()?

ocaml - 检查可变列表在ocaml中是否有循环?

ocaml - 多个工作进程的 ocp-build.conf 语法?

ios - ReasonML 可以用来制作原生 iOS 应用程序吗?