OCaml 相关
2014年09月07日 06:26GMT+8
(现在 ocaml 官网有新的安装包可以尝试下,还有就是对于 fdopen 的安装包可能需要添加仓库作为备选)
# 对于 fdopen 添加第二个仓库,作为备选仓库
opam repo add <NAME> https://github.com/ocaml/opam-repository.git
# 之后才可以可以 opam upgrade luv 到 0.5.12 版本
在 windows 中安装 ocaml
-
不要使用 cygwin 自带的 ocaml, 因为它不包含 opam, 也没法编译一些库。
-
使用 cygwin, 但是得把之前通过 setup.exe 安装的 ocaml 和 flexdll 卸载(uninstall) 掉, 如果你有的话.
-
照着 opam-repository-mingw/installation 一步一步来(我个人是参照 Manual Installation 安装的, 那个自动安装包我没尝试过。)
目前我使用:
ocaml-variants.4.07.1+mingw64c
版本 ocaml-variants.4.07.0+mingw64c
编译 haxe 源码:
-
在 setup.exe 里选上
mingw64-x86_64-zlib
和mingw64-x86_64-pcre
(参考 Makefile.win 里的 dll 文件搜索)注意: 对于 mingw64, x86_64 是 64 位版本的, i686 是 32 位
#通过参考 haxe/.github/workflows/main.yml make git zlib-devel rsync patch diffutils curl unzip tar m4 perl libpcre2-devel mbedtls-devel mingw64-$($env:MINGW_ARCH)-zlib mingw64-$($env:MINGW_ARCH)-gcc-core mingw64-$($env:MINGW_ARCH)-pcre2 perl-IPC-System-Simple
代理设置修改
.bash_profile
文件, 添加export http_proxy=http://127.0.0.1:49308
-
通过 opam 安装 camlp5, (可用 mintty 来代替 bash 以防止乱码).
-
参考 Makefile 文件, 执行
make -f Makefile.win
即可# 构建 haxe 编译器, 可附加: ADD_REVISION=1 make haxe -f Makefile.win ## # 对于 haxe 编译器快速开发, 编译成字节码似乎更合适: make haxe BYTECODE=1 # 构建 haxelib make tools
vscode
opam 需要安装 merlin 来产生语法提示,vscode 插件安装 reason
~~需要将 ocamlmerlin 所在路径添加到 PATH. ~~
或者设置 reason 的 ocamlfind, ocamlmerlin, opam 的真实路径
vscode 安装 Ocaml Platform
插件,这个插件需要从 cygwin 控制台输入 “code .” 进入 vsdode
- (必须)
opam install ocaml-lsp-server
- (可选)
opam install ocamlformat
速记
官方文档, 但网页引用了 google api, 你需要一个特殊的浏览器才能快速打开这个页面.
API 文件建议参考 cygwin/lib/ocaml 下的 mli 文件, 一些方法会提示是否为 tail-recursive
-
和 c 语言一样, 函数参数的传递是从右到左
-
整数字面量后缀:
l, L, n
分别表示为 int32, int64, nativeint. -
Warning 40: 在 ocaml 中, 直接访问某一模块 record 的属性将会弹出这个警告, 例:
module Foo = struct type point = { x: int; y: int; } let pos_new x y = {x; y} end let () = let p = Foo.pos_new 11 22 in print_int p.x (** Warning 40: x was selected from type Foo.point. *)
除非你使用 open 将 Foo 包含进来, 例如下边的局部打开:
let open Foo in let p = Foo.pos_new 11 22 in print_int p.x (* 或者 *) let p = Foo.pos_new 11 22 in print_int Foo.(p.x * p.x + p.y * p.y)
-
一些 API(外部库)在编译时需要指定:例如正则表达式匹配
ocamlc str.cma hello_world.ml ocamlopt str.cmxa hello_world.ml # 在顶层交互环境,则使用 #load "str.cma" 来打开处部库 # 当然使用 ocamlfind 编译时会有更简洁的参数, 可以用 -linkpkg -package str,bigarray,...
-
和其它语言不一样的是, ocaml 字符串内的字符默认是可以被修改。使用
Bytes.set
注: 字符串中间即使遇
0
还将继续输出后边的用使编译参数
-safe-string
限制Bytes.set
对 string 的更改 -
值(函数也是值)与类型有不同的命名空间,因此即使名字相同也不会存在冲突。
type haha = int (* 类型, 类型必须是小字母开头, 而变体枚举则必须大写字母开头 *) let haha = 101 (* 值(还是叫变量好了,虽然它不可变), 并不会与类型 haha 冲突 *) (* "模块名"首字母必须大写, 虽然文件名都是小写。 "变体名"首字母必须大写, "类型名"首字母必须小写, "字段名"首字母必须小写, (record 的字段) "模块类型"(sig) 则没有大小写限定。 *)
-
小括号
()
其实就相当于 begin/end, 对于一些嵌套的地方(比如多个 match 表达式)你可能需要使用 begin/end 来划分let f = function | (a, 1) -> (match a with | 1 -> true | _ -> false) | _ -> true
-
由于函数的优先级最高, 应此很多时候参数需要加上括号被优先计算
let double x = x + x;; double 3 + 2 (* 这个其实是先算 double 3, 然后再把结果 + 2*) double (3+2) let neg = ref (-1) (* 当引用一个负值时, 如果不加小括号将出错 *)
-
跟大多数语言不一样提 list, array …. 等复杂类型也可以直接判断是否相等
[1;2;3] = [1;2;3] [|1;2;3|] = [|1;2;3|] (1,2,3) = (1,2,3) {a=1; b="hi"} = {a=1; b="hi"}
-
函数的参数类型应该要用括号, 如果你要添加的话
let sum (a:int) (b:int) = a + b let sum a b:int = a + b (* 注意这个 :int 指的是返回值类型,非 b 的类型 *)
-
可选参数使用
?
作前缀, 命名参数使用~
(如果一个函数有二个参数最好是只定义一个”命令参数”)let foo ~arg1 = arg1 + 5;; foo ~arg1:123;; (* Optional named parameters *) let odp ?(ftw = "OMG!!") () = print_endline ftw;; odp ~ftw:"hi there" ();; (* 但是在调用时,还是得用 ~ 来命名参数 *) odp ();; let sum ~(x:int) ~(y:int) = x + y;; let sum ~x ~y = x + y;; sum ~x:1 ~y:2 let x = 1 and y = 2 in sum ~x ~y;; (* 别名, 稍了解下就行了,因为会与类型名容易混淆不建议使用 *) let sum ~x:x1 ~y:y1 = x1 + y1;; let sum ~x:(x1:int) ~y:(y1:int) = x1 + y1;;
-
同时let多个绑定
let a, b = 1, 2 ... let a = 1 and b = 2 in ..
-
“等号”
=
,==
和 “不等号”:<>
,!=
的区别: stackoverflow…,因此除非判断二个变量是否为相同的一个变量,否则一律使用 “=” 和 “<>” 来检测相等。
(* 在 pervasives.ml 下可找到如下: *) external ( = ) : 'a -> 'a -> bool = "%equal" external ( <> ) : 'a -> 'a -> bool = "%notequal" external ( == ) : 'a -> 'a -> bool = "%eq" external ( != ) : 'a -> 'a -> bool = "%noteq" 1 = 1 (* true *) 1 == 1 (* true *) 1.0 = 1.0 (* true *) 1.0 == 1.0 (* false *) "a" = "a" (* true *) "a" == "a" (* false *) let v = "a" v = v (* true *) v == v (* true *)
-
操作符
|>
与@@
, 通过查看 pervasives.ml 可知101 |> print_int (* 相当于 print_int 101 *) let sum a b = a + b in 101 |> (fun a -> sum a 202) print_int @@ 101 (* print_int 101, 感觉好像没什么用处, 因为不用写 @@ 也没关系 *) let sum a b = a + b in (fun a -> sum a 202) @@ 101 (* 单个 @ 用来连接二个 List, 相当于 List.concat([1;2;3] [4;5;6]) *) [1; 2; 3;] @ [4; 5; 6]
-
类似于 interface 的东西, 请注意:
with
的意思类似于局部更新,修改了 IPoint 的定义,使其中的某个类型暴露给外部使用。在 ocaml 中
interface
称为Module_type
, 局部更新的语法为:<Module_type>: with type <type> = <type'> and <t2> = <t2'>
module type Bumpable = sig type t val bump: t -> t end module Float_Bumpable = struct type t = float let bump n = n +. 1.0 end module Int_Bumpable: (Bumpable with type t = int) = struct (* 修改了类型 t, 使它不再为抽象类型, 即暴露可见 *) type t = int (* 这里的 t 则是给 struct 内部用 *) let bump n = n + 1 end module Int32_Bumpble: (Bumpable with type t := int32) = struct (* 注意 :=, 能直接修改, 称为破坏性修改 *) let bump n = Int32.add n 1l end (* 而函子/仿函数则是如下, 当然这个示例弄得不太好 *) module Make_Bumpble(Bump: Bumpable) = struct type t = Bump.t let bump = Bump.bump end
-
首类函数:
(* 使用 let 将 module 与 module_type 绑定 *) let bumps = (module Int_Bumpable: Bumpable) (* 使用 val 来引用这个模块 *) module New_Bumpable = (val bumps: Bumpable) (* 当然即使指定为 Int_Bumpable 但是并没有暴露其属性给外部,因此要使用它还得: *) let int_bump = (module Int_Bumpable: Bumpable with type t = int) module New_Int_Bumpable = (val int_bump: Bumpable with type t = int) (* 感觉好像也没什么用处。。。。 *) (* 局部抽象类型, 通过 `(type a)` 声明一个伪参数, 可以创建一个新类型在构造模块时使用 *) let wrap_in_list (type a) (x:a) = [x];; (* 感觉可看成 function<T> wrap_in_list(x: T) return [x]*) module type Comparable = sig type t val compare: t -> t -> int end let create_comparable (type a) compare = (module struct type t = a let compare = compare end: Comparable with type t = a)
-
misc
- 数组和 List 不一样的是, 数组的值像 mutable record 一样可变, 即:
arr.(0) <- 100
- List 下的方法, 大多数 rec_ 为前缀的方法才是 tail-recursive 形式的
- incr/decr 用于 int ref 为增和减
- 数组和 List 不一样的是, 数组的值像 mutable record 一样可变, 即:
opam
ocaml 的包管理器。
命令行
ocaml # 不添加任何参数的话,会进入到一个交互式的 CLI(不过一点也不好用)
# 执行多个文件(需要先编译出另外的): ocaml c.cmo b.cmo a.ml
ocamlrun # 执行字节码的可移植解释器
ocamlc # 字节码编译器, 文件扩展名为 cmo, (可添加 -custom 嵌入运行时,以避免安装整个 ocaml)
# ocamlc -i xxx.ml 可以在控制台输出其文件签名,可通过控制台重定向到一个 .mli 文件.
# 例: ocamlc -o app.byte c.ml b.ml a.ml
# 看上去整个编译流程与 c 语言相似, 需要手动指定各个文件, 不过 ocamlbuild 似乎可以解决这一点
# -c 参数表示仅编译不链接,(当 ocamlmerlin 找不到模块,没有语法提示时)
ocamlopt # 原生代码编译器的前端, 生成 .o, .cmx 文件
ocamlbuild # 用于编译复杂的项目,以 .byte 结尾的会被编译成字节码,而 .native 则为原生代码.
# 并且能自动识别并且处理文件的依赖关系,即只要指定入口文件即可。
# 重要: 如果源码发生变化,只需要删除 .byte 或 .native 文件,然后重新用 ocamlbuild 构建即可。
# 不可以指定 "-o 输出名", 只能通过命令行例如 rename 来完成.
# https://github.com/ocaml/ocamlbuild/blob/master/manual/manual.adoc
ocamldebug #
ocamldoc # 将注释转换成文档, 要使用2个星号作为注释起始, 例如: (** *) "@" 为特定标记属性.
# Argot 是另一个改进的 HTML 生成器.
camlp4 # 预处理器,例如添加一些特殊的语法, 它是标准 ocaml 安装包的一部分
# 例如如果把特殊语法写在 ml 文件内, 则通用 -pp 参数编译, 例:
# ocamlc -pp camlp4o -c -o _build/hello.cmo hello.ml
# 上边的示例在添加 -dsource 将会显示预处理后的源码。
# 假如将特殊语法写在别的文件扩展名内, 则预处理输出, 例:
# camlp4o lexer.mll -o lexer.ml
# camlp4o parser.mly -o parser.ml
# 注意: cygwin 下使用 bash 才会输出正常的文本, 而用 mini bash 会产生一个二进制的输出文件
camlp5 # 预处理器, 同 camlp4, 但它并非由标准安装包提供,需要另行下载
# 它的关系和现在的 camlp4 非常混乱, 因为它以前就叫做 camlp4, 而现在 camlp4 在之前并不叫做 camlp4
ocamldeps # 依赖
#########
# opam
#########
ocp-indent # 格式化源码
ocamlmerlin # (重要): IDE 需要它来提供智能提示
ocamlfind # 常用工具 https://ocaml.org/learn/tutorials/compiling_ocaml_projects.html
dune # ocaml 专用的编译工具
dune
目前 ocaml 最流行的编译工具
dune-project
作为整个项目的”根”
misc
-
ocamlmerlin: 用于产生语法提示
-
oasis: 用于编译 ocaml 项目
-
ocaml 4.02 之后官方宣传使用
-ppx
来代替 camlp4 的语法扩展, a-guide-to-extension-points-in-ocaml因为 camlp4 的语法扩展不但缺乏文档, 而且使用非常复杂,
- lexer
- genlex: (标准库) 一个简单快速的 lexer 生成器, 自带了几个简单的 token。 依赖 camlp4
- ulex: lexer generator for Unicode, 使用的是 camlp4 语法扩展, sedlex 也是这个作者写的
- parser
- ocamlyacc: LALR, ocaml 自带的命令行工具, , 通常处理
.mly => .ml
- camlp4: LL(1), 使用了 camlp4 流扩展语法:
[< >]
- ocamlyacc: LALR, ocaml 自带的命令行工具, , 通常处理
- 标准库的 genlex 是一个非常简单的 lexer 生成器, 依赖 camlp4。