关于“锈”与“Ocaml”之争的思考

2020-08-20 23:31:34

我现在已经生锈两周了,所以在我得斯德哥尔摩综合症之前,我觉得这是写一篇评论文章的好时机。

我学习铁锈的主要动机是我必须维护一些黑暗的铁锈代码。最近有一次与该代码相关的中断,我必须在运行中学习,所以更好地了解我正在查看的是什么。

我也梦想重写Dark in Rust已经有很长一段时间了,这主要是因为OCaml的挫败感以及Rust社区的一些出色的营销。我正在努力评估这是不是一个好主意,如果是的话,试着找出如何以一种有意义的方式(也就是说,逐步地)来做这件事。

顺便说一句,Dark是构建后端应用程序的平台;实际上,它是一个HTTP服务器和数据库(由Dark Platform组成),它们连接到Dark编程语言的解释器,您可以使用Dark编辑器对其进行编辑。解释器是该语言的核心,它在Google Cloud的Kubernetes中运行,并在浏览器中编译成JS。今天,几乎所有的东西都是在OCaml中实现的。

到目前为止,我已经花了几个星期的时间学习“锈”,我不得不说,我喜欢“锈”的很多方面,但我绝对还不喜欢它。

当我谈论编程语言时,我使用的镜头是意外复杂性,这基本上就是Dark的全部内容(也就是去掉它)。因此,当我批评某件事时,很大程度上是基于我觉得它让我跳过的任何狗屁圈子。

我会抱怨下面的OCaml和Rust,所以如果你是这两种产品的粉丝,那就做好准备吧。记住,所有的编程语言都很烂,尤其是你最喜欢的语言。

有几件事拉斯特做得很好,而奥坎姆确实做不到。我曾希望OCaml在过去的几年里会有所进步,当然,如果进展缓慢,也会取得进展。但是,一旦你面对面地接触到一种做得很好的语言,差别就真的很明显了!

人们说Rust是一种边缘语言,但作为一个一直在用真正的边缘语言(OCaml和之前的Clojure)编写代码的人,Rust感觉很棒。学习的方法数不胜数:令人印象深刻的教程(还有很多社区教程)、多本书、StackOverflow问题、博客帖子、视频。不仅如此,还有很多是针对初学者的;通常感觉OCaml的博客帖子都是针对久经沙场的专业人士或学者的。我记得我发现它非常非常难理解,特别是很难弄清楚如何编写惯用的OCaml,这是我在Rust中根本没有发现的。

这真是令人惊叹--铁锈拥有无所不包的图书馆!!我真的一直在为OCaml图书馆的匮乏而苦苦挣扎。一两年前,我们曾与Google Cloud讨论Cloud SQL Postgres的不足之处,结果被告知Cloud SQL只是为了打勾,我们应该转而使用Spanner。只是OCaml没有Spanner库,所以我们只能使用云SQL。对50个不同的供应商和服务重复50次,你会开始感到沮丧。

工具是铁锈也是令人难以置信的。尤其值得一提的是,CARADE可能是我使用过的最好的构建系统。他们集成了包管理器、构建系统和编译器,然后一切都正常了吗?魔法!

相比之下,在OCaml中,Opam、ESy、Dune和OCaml本身都在执行部分工作。它们在某种程度上可以很好地协同工作,直到它们不能很好地工作。当你把它们连在一起时,你真的需要了解每个工具做什么,它在哪里开始和停止,以及它们是如何集成的。老实说,一点也不好玩。

前端也令人困惑:有Bucklescript的东西,ReasonML的东西,OCaml编译器的东西,Javascript的东西。但是最近有了很好的改进--他们只做了一件事!所以,至少在这方面有一些改进!

编辑器工具在Rust中也很棒。Rust language Server正好可以工作(至少在VS代码中),而我在设置OCaml编辑器集成时遇到了很多问题。我曾经让它最终工作得很好,然后我在OCaml配置中更改了一些东西,但一直无法恢复。

我将把我的黑暗偏见用在这里,并推测所有这些神奇之处都是因为Rust基本上是一个集成的系统:相同的人正在制造所有这些工具(如果不是完全相同的人,至少是一群为相同目标工作的人)。然而,在OCaml中却完全没有这种感觉。

OCaml感觉就像是一个完全独立的营地,Facebook、Inria、Jane Street和OCaml实验室都在做自己的事情,几乎没有人相互交谈。这是一个局外人的观点(这是我以前说过的一个观点),而且可能是错误的,但如果你们真的会说话,也许可以整合几个这样的项目,降低学习所有这些东西的认知开销?如果在整个OCaml/Reason生态系统中有一个像Cargo一样简单和强大的工具,那不是很棒吗?

铁锈里的宏很棒!OCaml有PPX,它们是使用OCaml编译器工具包构建的独立二进制文件。他们有非常高的进入门槛,而我从来没有建立过门槛,甚至很难理解我使用的那些门槛。在过去的几年里,他们似乎也遇到了很多麻烦。

Rust中的宏内置于语言中。这感觉有点像在语法树的一部分上做正则表达式(老实说,有相同的缺点),也就是说,即使它们没有那么强大,也很容易上手,并且可以做相当强大的事情。(=。

实际上,它们似乎确实缺乏OCaml';PPX的一些威力。有了OCaml的PPXes,我相信你可以访问整个编译器,以及所有的优点(做任何事情!)。缺点(版本之间的兼容性)。然而,使用Rust时,我的访问权似乎要少得多-除非我弄错了,否则我实际上无法让它解析泛型的类型信息,然后我可以重新调整用途,这是令人沮丧的。

它们似乎比Clojure的宏更容易编写,虽然也没有Clojure的功能强大。鉴于Lisp/Clojure完全围绕将代码视为数据进行设计,而且语法非常少,这是有意义的。不过,还是挺不错的!

“铁锈”感觉真的设计得很好,我想这是因为他们已经大刀阔斧地删减了语言中不合情理的部分。所以我今天看到的铁锈应该和2010年的铁锈有很大的不同。与此同时,OCaml有一大堆他们真的应该修剪而不是修剪的东西。

类似地,Rust这门语言在语法方面也相当不错。同时,OCaml是如此丑陋,以至于社区为它想出了一个完全不同的语法。这应该不是什么大事,但作为一个负责让人们在几年内快速使用OCaml的人,这并不是一次有趣的经历。

很抱歉,到目前为止,OCaml的员工是我受挫的受害者;是时候谈谈铁锈了。首先,一些期望:我已经在Rust上工作了两个星期了,其中很多都是做教程、工作示例等。但是我确实有很多使用OCaml和Clojure的经验,而且在C和C++方面也有很多经验。我也从事了大约20年的专业编程,主要是在编译器和编程语言实现方面。所以我觉得铁锈应该是直截了当的。

就在我们谈论我的时候,这里有一则关于我第一次接触铁锈的趣闻轶事:2010年,我开始在Mozilla工作。第一天,他们让我乘飞机去惠斯勒参加他们的年度务虚会,我看到格雷登·霍尔(Graydon Hoare)秘密展示了他已经研究了4年的东西:一种名为铁锈(Rust)的新编程语言。我兴奋地跳上前去帮助他:我认识编译器,我热爱语言,我能帮上忙!!不幸的是,尽管我试过了,但我没有提供太多帮助,因为我真的不能理解当时实现铁锈的语言,那是一种深奥的学术语言,叫做OCaml;!

实际上,我很惊讶实际的内存管理对我的困扰是如此之少。我对垃圾收集深信不疑,而且不用考虑内存问题,所以我本以为会讨厌“锈”的这一部分,但事实证明这还可以。您将所有内容都放在Box::New(常规堆内存)或rc::new(引用计数内存)或Arc::New(适合在不同线程中并发使用的引用计数内存)中,然后当它们超出作用域时,它们将被清除。

这实际上看起来还不错,特别是当我将其与当年编写C++进行比较时。到目前为止,我只构建了解释器的一小部分,但我希望如果我妥善处理作用域,内存就会自动管理?没有国际海事组织的垃圾收集那么好,但也没有我听说的那么糟糕。

起初我很兴奋Rust有模式匹配,因为这是每种语言都应该有的东西(也许很快就会有了)。然而,我很快就学会了对此不那么兴奋。

Type t=|id的EInteger*string|id的Elet*string*t*t|id的ELambda*(analysisID*string)list*t|id的EVariable of id*string|id*string*t list*sendToRail的EFnCall[@@派生show{with_path=false},eq,order,yojson{Optional=true}。

#[派生(调试)]pub枚举expr_{let{var:string,rhs:expr,body:expr,},FnCall{name:FunctionDesc_,args:IM::Vector<;expr>;,},lambda{params:IM::Vector<;String>;,Body:expr,},变量{name:string,},IntWrital{val:I32,}。

所以我们可以在这里用两种不同的语言看到相同的类型。在OCaml中,您只需指定类型。在Rust中,您可以指定类型和垃圾收集策略-您不能仅仅将expr放在另一个expr中,因为您不知道expr的大小,所以需要将其包装在Box、RC或Arc中。在C语言中,您可以使用指针,这就是我们在这里所拥有的:OCaml和Rust都有效地使用了指向另一个expr的指针。

但是,当我们谈到模式匹配时会发生什么呢?好的,下面是我们在OCaml(完整版)中是如何做到这一点的[https://github.com/darklang/dark/blob/a5a008ec0307a199bf501cf84181ab41fee56745/backend/libexecution/liblist.ml#L801]:

(Function|STATE,[DList l;DBlock b]->;let f(dv:dval):dval=Ast.ecute_dblock~state b[dv]in Dval.to_list(List.map~f l)|args->;失败参数

(";list";,";map";,0),|args|匹配参数{[DList(List),DLambda(vars,body)]=>;DList(list.map(|val|ecute_block(vars,body,[val])args->;FAIL(Args)})。

但是,我实际上不能在Rust中像这样进行模式匹配,因为我没有DList,我有Arc;Dlist&>,而您可以通过Arc、RCS和Box进行模式匹配。(此外,我还需要处理这里基本上所有东西的所有权问题)。

然后将其封装在宏中以使其可以接受。不用说,这是相当令人沮丧的。

我记得我第一次启动Ruby的时候,我们在CircleCI的早期使用JRuby,Mongo也是如此。这两个都得到了很好的支持,很多人都在使用它们。然而,我们似乎是第一个一起使用它们的人,我很快就总结出了这条经验法则:

在拉斯特,我看到有许多替代路线,它们并不完全是人迹罕至的,但这打开了多种选择,我开始担心起来。最明显的是异步/同步和RC VS Arc。

我已经看过RC VS Arc的种子了。我正在使用im,这是一个不可变数据结构库(稍后将详细介绍)。我读到在Rust中处理可组合错误的惯用方法是使用Error-Chain,它使用Arc。我在RC中使用IM,这与错误链中的圆弧不兼容。

这似乎以前就出现过,因为im实际上有两个库:im(使用Arc)和im-rc(使用RC)。有两个相同的库具有相同的功能,除了一个使用RC,另一个使用Arc,这一事实似乎是一个巨大的危险信号,我觉得有一天会给我带来很多麻烦。

同样,同步和异步Rust似乎使用完全不同的标准库,这也是一个很大的危险信号。所有风格的IM、RC/Arc和Sync/Async是否都能很好地配合使用?不知道,但发现并不是一个令人兴奋的前景。

将IM用于数据结构是我在铁锈领域的第三件不寻常的事情。IM是一个不可变数据结构库,它对创建默认不可变的语言非常有用。

我最初以为铁锈是一成不变的,但它绝对不是。虽然它内置了一定数量的不变性,但从根本上说,您是在按照可变值行事。这是超级有用的,有能力知道你可以传递一些给一个函数,它不会改变,这是非常美妙的。

然而,当我将其与使用Clojure和OCaml等不变语言进行操作的标准方法进行比较时,它有一个很大的缺点。Clojure和OCaml都使用纯函数数据结构,允许您在不破坏旧数据结构的情况下获得新版本的数据结构。

例如,如果我有一个列表,并且我想对一个稍长一些的列表执行一些操作,我可以拥有一个值,该值可以保持旧列表不变,但也允许我与新的略有不同的列表交互,而不会造成很大的性能成本。在Rust';的默认VEC中,我必须克隆旧的列表才能进行此类操作。

我发现这非常令人沮丧,感觉管理这是Rust中大量样板/意外复杂性的原因。

我正在做的一件非常常见的事情是在Dark中创建一个新的作用域(或符号表),这是旧作用域加上一些变量。在可变语言中,这在性能方面是昂贵的,因此语言实现通常创建一种范围堆栈来管理性能影响。使用不变性,您可以免费创建旧作用域的副本,然后将新值添加到其中。完成后,您只需继续使用旧作用域,该作用域实际上并没有改变。

因此,从一种以不变性为核心的语言转变为只有一些不变性的语言真的是相当令人沮丧的。IM处理了一些复杂性,但我发现自己不断地将东西转换为VEC(正如我们上面看到的),这有点违背了目的。

我在这里的观点是,我想我希望铁锈是真正不变的。在系统语言中很难做到这一点,但有点令人沮丧。

我说我只会抱怨一下打字系统(或者借阅检查器之类的)--这些是一样的还是不同的?我还不确定)。用Rust编程让我想起了很多用C++编程的事情:你给一个函数添加一个常量,然后你必须在整个代码库中跟随这个常量,直到你最终到达你知道它实际上不可能是常量的地方,所以去你妈的。除了在“铁锈”中,这种情况经常发生。我希望等我习惯了以后,这件事就会过去,但这真的很令人沮丧。

在OCaml中,这种体验是不同的:因为基本上所有东西都是不变的,所以你永远不会得到这一点,而且你不会跟踪自己的指针,所以一切都是神奇的。尽管我要说的是,在极少数情况下,某些东西实际上是错误的(通常类型推断不会使用一个理性的人会使用的相同算法),而OCaml中的错误消息几乎毫无用处。

这里有一个很好的比喻:假设你每天都去Chipotle。在Rust Chipotle,他们对你的玉米煎饼的配料有严格的规定。先生,白米饭要加中调味汁吗?绝对不行!";。你看,中号沙拉只配糙米,而且你还需要吃豆子,否则什么都不能吃。在任何情况下,他们都不会允许你制作你认为你想要的玉米煎饼,无论你认为你有多想要它。

与此同时,在OCaml Chipotle,你可以吃到你喜欢的任何东西,结果总是很棒。但是每个月你去吃一次午饭,他们会拒绝给你做碗,拒绝告诉你为什么,也不会让你离开。当你被困在商店后试图从路人那里寻求帮助时,你会意识到附近没有你可以求助的人。

宏也在“生锈的伟大之处”一节中。然而,我如此频繁地求助于宏,感觉就像是代码的味道。这里有一吨的样板,我发现很难用函数来编写可重用的代码,有了所有这些所有权的东西,我就很难写出可重用的代码。我想我会弄清楚这件事的,这样我就不需要那么依赖宏了。

公平地说,这可能是一个有点不公平的比较。您看,OCaml是一种语言,它的理由是构建解释器和编译器。我听说Rust在编译器方面也很出色,所以我希望它也一样好。也许我的大脑被不变性扭曲了--在Clojure、OCaml和Dark之间,我十年来几乎没有真正用一种可变的语言编写过代码。

总体而言,Rust这门语言令人沮丧,但并不可怕,而工具、社区和生态系统则是真正优秀的。同时,OCaml的工具很差,生态系统很差,没有社区,而语言本身却非常优秀(当然,除了可怕的语法)。

既然我真的与OCaml陷入了死胡同(甚至多次),我仍然希望自己会患上斯德哥尔摩铁锈综合症。似乎真的没有其他地方可去了。

你可以在这里注册Dark,并在我们的贡献者松弛中查看我们在这些功能上的进展,或者通过观看我们的GitHub Repo来查看我们的进展。