OCaml学习——Configuration-for-merlin

Configuration for merlin

Emacs+Spacemacs+ocaml layer 进行 OCaml 语法学习,刷题过程,调用外部库和自建模块时 Merlin 检测一直报错,并且 auto-completension 也会随之失效。今天在 Merlin Issue 查阅一些信息后,遵循Github Wiki,算是弄明白问题的原因,也了解了.merlin的配置。

Unbound module XXX

正确安装并加载tuareg-mode(提供语法高亮)以及merlin-mode后,Merlin 每次在C-x C-s保存时检查错误,比如在一个factorial目录中,我们创建了factorial.mltest.ml文件。

  • factorial.ml

    模块内包含一个计算累乘的函数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    let factorial =
    (* Because we have a closure, `ht` persists across calls to `factorial`. *)
    let ht = ref (Hashtbl.create 100) in
    let _ = Hashtbl.add (!ht) 0 (Big_int.big_int_of_int 1) in
    let max = ref 1 in
    fun (n:int) ->
    if n < 0 then
    failwith "n < 0"
    else
    let ht = !ht in
    try
    (* Check if `ht` contains the value of `factorial n`. Return it
    if so. *)
    Hashtbl.find ht n
    with Not_found ->
    let rec aux i n =
    let x = Big_int.mult_int_big_int i (Hashtbl.find ht (i - 1)) in
    if i = n then
    x
    else
    let () = Hashtbl.add ht i x in
    aux (i + 1) n in
    let result = aux !max n in
    (* Update `max`, and return the `result`. *)
    let _ = max := n in
    result
  • test.ml

    用于单元测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    open Big_int
    open Factorial
    open OUnit2

    let () = run_test_tt_main ("factorial n" >::: [

    "n < 0" >:: (fun _ ->
    assert_raises (Failure "n < 0") (fun () -> factorial (-1))
    );

    "n = 0" >:: (fun _ ->
    assert (eq_big_int (big_int_of_int 1) (factorial 0))
    );

    "n = 1" >:: (fun _ ->
    assert (eq_big_int (big_int_of_int 1) (factorial 1))
    );

    "n = 2" >:: (fun _ ->
    assert (eq_big_int (big_int_of_int 2) (factorial 2))
    );

    "n = 3" >:: (fun _ ->
    assert (eq_big_int (big_int_of_int 6) (factorial 3))
    );

    "n = 10" >:: (fun _ ->
    assert (eq_big_int (big_int_of_int 3628800) (factorial 10))
    );

    ])

我们发现,虽然顶层环境中可以进行简单的函数测试,但是保存test.ml时 Merlin 检测每次都会报如下错误,自动检测也随之失效了。

1
2
Error: Unbound module Factorial
Error: Unbound module OUnit2

而实际上,我们已经通过opam install ounit2安装了 OUnit2,并且构造完成 Factorial 模块,只是 Merlin 没有感知到。merlin 只能感知到编译完成的文件(严格地说应该是编译好接口)的文件,所以多文件(模块)下,没有编译时 Merlin 就会报 Error

Configuration for .merlin files

ocamlbuild xxx.cmi编译自己编写的模块,然后C-c C-f <RET> .merlin编辑.merlin配置文件如下。

1
2
B _build
PKG lwt lwt.unix ounit2

简单说一下.merlin文件的配置规则,更完整的文件配置见Merlin Wiki Project configuration

  • S: Source path

    S指定目录让 Merlin 感知到其他源文件。如果项目文件分布在不同文件中,使用S来告知 Merlin。

  • B: Build path

    B为了让 Merlin 能够从其他文件来提供 type of identifiers 以及补全,它需要知道在哪里可以找到项目的其他模块的.cmi文件(这也是本文解决的点)。

  • PKG: Packages

    如果使用了外部库,需要 findlib package,我们只需要在.merlin中添加 PKG,比如ounit2,需要小写(与传递给ocamlfind保持一致)。

Basic call

一些 Emacs 编辑.ml文件常用的快捷键

  • C-c C-e tuareg-eval-phrase 传递语句到终端(OCaml 顶层环境),最常用
  • C-c C-t merlin-type-enclosing 类型查询(配合C-和上下方向键控制包括区域)
  • C-c C-x merlin-error-next 下一个 error
  • C-c [: ocaml-open-module 打开模块
  • C-c ]: ocaml-close-module 关闭模块