漫谈函数式编程:聊聊 OCaml

编程知识 行业动态 更新时间:2024-06-13 00:21:08

OCaml 最早称为 Objective Caml,是 Caml 编程语言的主要实现,开发工具包含交互式顶层解释器,字节码编译器以及最优本地代码编译器。Ocaml 有一个巨大标准库,使得可以像 Python 或者 Perl 语言一样可以方便地开发各种应用程序。

本 Chat 主要分享内容如下:

  1. OCaml 语言的来历
  2. OCaml 语言的特点
  3. OCaml 语言的发展现状
  4. OCaml 可以给我们带来哪些好处

本次 Chat 作者:王克毅,火币技术专家

聊聊 OCaml

OCaml 是一种开发效率和运行效率都比较高的多范式语言,如今在业界已经有了越来越多的应用,这次我们就来聊聊 OCaml。

OCaml 意为 Objective Caml,是 Caml 语言的一种方言,支持命令式、函数式和面向对象等多种编程范式,并且支持 IA-32、X86-64 (AMD64)、Power、SPARC、ARM、ARM64 等多种平台。

其实你很可能已经用过 OCaml 了,就像这两位同学说的:

图片来源:Twitter 网友截图

OCaml 的确影响了相当多的当红语言,比如 Facebook 内部使用的支持协程的 PHP 编译器 Hack、苹果推出的吸收了很多 OCaml 语言特点的 Swift 语言、微软 .Net 平台上的 OCaml 方言 F#。甚至如今拥趸众多的 Rust 语言,其完成自举之前的初期版本也是由 OCaml 编写的。

是什么样的原因使这些重要的软件选择了 OCaml 呢?下面我们正式开始今天的话题。

OCaml 的历史

1985 年,法国高等师范学院(ENS)发布了 Caml 语言(Categorical Abstract Machine Language,范畴论抽象机器语言)。后来一段时期主要被法国国立计算机及自动化研究院(INRIA)负责维护。最初的 Caml 语言被暱称为 Heavy Caml,因为其使用 Lisp 实现,使用了过多的 CPU 和内存。后来 Xavier Leroy 和 Damien Doligez 使用 C 重新编写了 Caml 的编译器,称为 Caml Light。

1996 年,OCaml 发布了第一个版本,其在 Caml 的基础上支持了面向对象编程。(为什么一门函数式语言要加入面向对象的特性呢?要知道 Java 是在 1995 年发布的,当时 Caml 加入 OO 特性可能是为了蹭热点。谁知现在 FP 这么火,几成显学。)

图片来源:网络

特点

OCaml 是一种多范式的编程语言

首先,使用 OCaml 可以无痛的进行命令式编程,这对新手非常是友好的。想想学习 Haskell 的时候,首先要无脑的接受把一切 IO 塞进 do 语句,想真正搞明白发生了什么还要学习 Monad Law,接下来如果好奇心爆棚决定一口气搞明白 Monad 是什么可能会收获非常大的挫败感。而 OCaml 中通常只是将 IO 等改变状态的方法设计成 Unit 类型,然后很直白的拼接在一起,初学者可以快速进入角色编写可用的程序。

OCaml 也支持 OOP。在 Scala 语言出现之前,通常的观点认为函数式编程与面向对象是不相容的。但是最先在同一种语言中融合了带类型推导函数式编程和面向对象编程的其实是 OCaml。OCaml 支持多重继承和匿名对象,且为类和对象建立了实用的类型系统。如果用户熟悉 OOP 的风格,在掌握语法后可以快速上手。OCaml 的面向对象机制在其编译器中的某些部分得到了应用,这些部分"用 OO 的方式实现比较自然",有一定数量库也使用了 OOP 的风格。

然而 OCaml 最提倡的还是函数式编程。其实现了代数类型系统、类型推导、高阶函数、尾递归、模式匹配、词法作用域、参数化模块等特性,自诞生之日起一直是主流的函数式编程语言。

OCaml 默认是严格求值的(也支持惰性求值),这与 Haskell 默认惰性的求值策略恰恰相反,但是与用户的习惯接近,使得程序的行为更容易预测。

OCaml 中的参数化模块系统(Functor)非常实用,严谨灵活,且门槛较低。

合理使用 OCaml 提供的基础设施,可以得到非常简洁有效的代码。

下面的代码是 OCaml 和 Java 分别实现的红黑树添加节点操作:

图片来源:网络

可以看出,OCaml 使用模式匹配,直观的对应了红黑树重平衡时面临的 4 种形态,替代了 if else 分支判断,使得代码更为精简。

速度快

OCaml 的编译速度和运行速度都很快,编译器的开发者追求在可行的范围内将速度提升到极致。

通常 OCaml 编译单个文件的速度是毫秒级的。BuckleScript 是 OCaml 的一个方言,可以将 OCaml 代码编译到对应的 Javascript 代码,它的作者张宏波在一次访谈中提到:“使用 OCaml 编译 BuckleScript 的编译器、标准库、接下来跑完全部测试,整个流程只要两秒钟”。

OCaml 运行时比较薄,值都是 unbox 的,且有分代和增量 GC,所以其 GC 速度非常快,而且行为较可预测。

OCaml 的执行速度也比较快。

OCaml 有多种执行方式,可以解释执行,也可以编译到字节码或者者编译为本地可执行文件,还可以编译为 JavaScript。其编译为本地可执行文件后的运行速度通常会比 C 慢 3~5 倍,下面两幅图取自 Debian Benchmark Game,可以对其运行效率有一个直观的感受。

应用 & 生态

OCaml 的祖先 ML 语言,全称是 Meta Language,设计之处的种种特性都是为了便于开发其他语言的编译器。OCaml 作为继承者,在开发其他编程语言方面也是硕果累累。

首先是大名鼎鼎的 Coq,形式化证明的经典工具,在数学定理,复杂软件和硬件设备的证明方面都有丰富的应用。Coq 可能是由 OCaml 编写的最有价值的软件之一。

金融公司 Jane Street 是 OCaml 的使用大户,其主要业务是量化交易,全部代码都由 OCaml 编写。Jane Street 也向社区贡献了大量的代码,比如准标准库 Core、异步编程库 Async 等。

Facebook 对 OCaml 语言有较多的应用,除了前面提到的 PHP 编译器 Hack,还有 JavaScript 的静态类型检查工具 Flow,以及前端语言 Reason(语法对 JavaScript 用户更友好,但是语义与 OCaml 相同)。坊间传闻渣打银行好多 Haskell 工程师都被 Facebook 挖去写 OCaml。

Microsoft 实现的 OCaml 方言 F# 也是不得不提的。得益于 .Net 平台,F# 在多核设备上具有更好的表现。但 F# 更鼓励用户使用 OOP,它并没有实现 OCaml 的参数化模块系统。

Blomberg 也有实现自己的 OCaml 方言 BuckleScript,其将 OCaml 代码编译为高效易读的 JavaScript 代码,可以在任何能跑 JavaScript 的环境执行。前端的同学们如果想体验 OCaml,从 BuckleScript 入手是不错的选择。另外还有一个框架 BuckleScript-TEA,相当于 BuckleScript 版的 Elm,得益于 OCaml 强大的表达能力,其实现较 Redux/DvaJS 更为简洁。

业界使用 OCaml 开发的高质量软件非长多,其他常被例举的还有:

  • Mirage,一个 OCaml 版本的 Unikernel 实现;
  • FFTW,MIT 开源的快速傅里叶变换库,其 C 代码是有 OCaml 程序生成的;
  • MLDonkey,一个小有名气的 P2P 软件。

OCaml 开发者的质量也比较高,2017 年 Intel CPU 的熔断漏洞就是 OCaml 的开发者发现并公布的。

从 RedMonk 编程语言排名 的角度看 OCaml 用户规模:

2012 年 OCaml 社区发布了包管理软件 Opam。此后,其上的软件包数量和贡献者数量一直稳步上升。如今 Opam 已经提供了大量的高质量软件包,涵盖了日常开发所需的大部分需要。

OCaml 社区中大量好用的库可以在这里找到:Awesome OCaml (当然,各种 Awesome 列表是不同语言都适用的套路)。

OCaml 也有比较友好的 FFI 接口,对于社区中未提供的包,户可以比较方便的通过其他语言的库自行包装一个模块出来。

对于还未提供 OCaml 客户端库的网络服务,也可以使用 bitstring 这个库,自行编写处理其协议的库。bitstring 提供了类似 Erlang 中对字节数据进行匹配的能力,非常好用。具体例子可以参见:使用 bitstring 实现 memcache client 的例子。

工具链

下面介绍 OCaml 语言的常用工具。

Opam

首先,现代的编程语言都会提供一个包管理工具,OCaml 中的就是上文提到的 Opam。

我们可以通过 Opam 来管理 OCaml 的第三方库,以及 OCaml 语言本身的版本(opam switch --help)。值得一提的是,最新版本的 Opam (2.0 版本) 在安装构建第三方软件包的时候,默认的行为是在一个沙盒中进行构建,这样就在一定程度上保证了构建过程中不会被恶意软件包破坏系统。

ocp-index

ocp-index 是一个轻量的工具,可以获取 OCaml 模块的各类信息。

比如输入

ocp-index print 'Unix.create_process' '%f\n%p\n%t\n\n%d'

就可以获得 Unix.create_process 这个函数的库文件位置,引用方法,类型,以及使用文档,详细的使用方法可以使用 ocp-index --helpocp-index print --help 查看。

utop & ocaml-jupyter

OCaml 也提供了很好的 REPL 工具,可选的有 utop 和 ocaml-jupyter。utop 对 Emacs 非常友好,ocaml-jupyter 则基于 jupyter 提供了体验良好的 Web 界面。

二者对于命令行界面的支持都非常不错。

merlin & tuareg

编写 OCaml 程序最终要的编辑器插件是 Merlin。Merlin 同时支持 VS Code 和 Emacs,新潮和老派的用户都可以愉快的使用。Merlin 提供了代码补全,定义跳转,类型提示,表达式求值等常用编辑功能,可以大幅提升产出代码的速度和质量。Merlin 提供了对 OCaml 个模块中函数的 “极性查询”,在 Emacs 中输入 M-x merlin-search 可使用。比如,输入

M-x merlin-searchSearch pattern: -string +int

则,Merlin 会列出所有 输入中包含一个字符串参数,并返回类型是一个整形的函数。- 和 + 代表了参数的极性,分别表示输入和输出,搜索时的顺序也是无关的,Haskell 中的搜索工具 Hoogle,对于参数顺序就有严格的要求,这样在易用性上 Merlin 要比 Hoogle 好一些。

笔者日常的开发环境是 Emacs + merlin + tuareg

编译 & 构建

将 OCaml 代码编译为字节码运行,需要使用 ocamlc 这个命令。将 OCaml 代码编译为本地可执行文件,需要使用 ocamlopt 这个命令。对于稍大的工程,需要使用 ocamlbuild 进行构建,而严肃的大型项目,通常使用 dune 这个工具进行构建。ocamlbuild 和 dune 并不是 OCaml 发行版中的标准工具,需要使用 opam install 进行安装。

jsofocaml 可以将 OCaml 字节码编译到 Javascript 代码,Bucklescript 则将 OCaml 源码编译到 JavaScript 代码。BuckleScript 生成的代码更轻便,但 jsofocaml 也有其使用场景,这两个工具大家可以选择使用。jsofocaml 需要用 Opam 进行安装,BuckleScript 需要用 NPM 安装。

如何学习 OCaml

比对式学习

通常学习新知识时,如果有其他相关知识为比对,会提升学习速度。对于学习语言来说,有下列三个站点可以供大家参考:

  • PLEAC
  • Exercism
  • RosettaCode

这些比对式的学习站点都比较适合初学者学习参考。

其中 Exercism 会给每个用户分配一个义务的 mentor,对学习者提交的答案进行指导。

总体来说,OCaml 的学习曲线不如 Haskell 陡峭,虽然也可以有 Monad 的概念(Lwt 和 Async 就都是基于 Monad 的库),但是必须掌握的只有 Functor。且其支持多种编程范式,能够针对不同场景。

参考书籍

推荐下面三本书

  • 《Develop Application With Objective Caml》,经典的 OCaml 书籍,涵盖内容较全面,可惜略有些陈旧;
  • 《Realworld OCaml》,作者来自 Jane Street,很新且全面,不过有大量部分是关于 Core 和 Async 这些 Jane Street 编写的库。有中文版。总的来说是市面上最好的 OCaml 教材;
  • 《OCaml 语言编程基础教程》,去年的新书,作者是国人,也很不错。希望看到更多这样高质量的中文 OCaml 书籍。

OCaml 的缺点

招聘难

市面上可以找到的会用 OCaml 的开发者的确太少,有意愿和能力在工作中学习一门新语言的工程师在人群中的占比也比较少。但这不用过于担心,容易找到人的编程语言,找到优秀开发者的概率也更低一些。喜欢使用小众编程语言的人,大概率上也是对编程有真正热情的人,这些人会自驱的持续进行学习不断进步。OCaml 使用大户 Jane Street 也将小众编程语言作为一种招聘策略(Jane Street 会将 OCaml 语言的培训作为标准的入职培训流程),以此过滤能力不足的应聘者。

多核支持

OCaml 的主干版本并不真正的支持多核程序,其像 Python 等语言一样带有一个 GIL,所以尽管有线程接口,但是同时只能有一个线程真正工作。这是一个历史的设计错误,OCaml 的主要作者曾认为多核 CPU 永远不会成为主流,并未预料到现在多核架构遍地开花。

但是,我们还是有办法的。首先,对于并行程序可以使用经典的多进程架构,进程间使用消息进行通信。其次,针对高并发场景,可以使用 Lwt 和 Async 这两个异步编程库应对。

另外,OCaml 可以编译到本地可执行程序执行,执行效率通常是 Python 和 Ruby 这样的语言的数十倍,而其表达能力又不弱于这两者。

最后,OCaml 的多核版本也在开发当中,目前在进行除虫和优化(OCaml 开发者不愿意让多核编译器编译出的单进程程序受到一点性能影响)不久的将来即可合并入主干。相信带有多核支持的 OCaml 会大放异彩。

下图为多核 OCaml 编译器和单核版本编译结果的运行效率对比(取自 http://ocamllabs.io/multicore/)

结语

综上所述,无论从启发性还是实用性上看,OCaml 都是一门被低估的语言。

大括号看多了会审美疲劳,僵化思维,新年大家多学习 FP 放开眼界啊(学校只教 Java 的危害)。


本文首发于 GitChat,未经授权不得转载,转载需与 GitChat 联系。

阅读全文: http://gitbook/gitchat/activity/5c18c67a135357369db13686

您还可以下载 CSDN 旗下精品原创内容社区 GitChat App ,阅读更多 GitChat 专享技术内容哦。

更多推荐

漫谈函数式编程:聊聊 OCaml

本文发布于:2023-03-31 05:24:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/6c899b2a5eb9dda1f1b0c398d207cf0e.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:函数   OCaml

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!