本文作者是高级系统开发工程师 Torbear Gannholm。Torbear 有着 30 多年的开发经验,对技术很深刻的理解。对于什么是好的编程语言,他发表了一篇文章分享了自己的观点。以下是他的全文:
这篇文章改编自我在 Cygni 科技峰会上的一次演讲。
我一直认为编程行为是在一个抽象的领域中完成的,只是后来才被翻译成编程语言。编程应该比写作需要更多的思考。
我基本上仍然坚持这一点,但我在谷歌担任代码可读性审查员的多年经验让我意识到,要想很好地使用一门语言需要时间和经验。许多 C++程序员在使用 Java 时编写了功能完备且合理的面向对象代码,但是 C++的臃肿却不能很好地满足 Java 中的优雅(即使 C++程序员可能认为它除了 Java 的「缺陷」之外大多是优雅的)。
那么程序语言的选择有多重要呢?有没有一种语言可以被认为是完美的?
什么是好的语言?
在学术界,关于什么是一门好语言的话题似乎相当平静,但 Tony Hoare 在 1973 年发表了一个有趣的主题演讲,名为「Hints on Programming Language design」。
如果你不知道 Tony Hoare 是谁,你可能会记得他是几年前在公众场合为发明了「NULL」而道歉的人。他本想避免这个,但实施起来太容易了,所以他无法抗拒。他还发明了很多好东西,比如 switch 语句和通信顺序、进程的思想,它们经常出现在 Go 和 Ada 的并发范例中。他的大部分工作都花在追求无错误编程上,但他断定这是一种浪费,因为业界对此根本不感兴趣。
关于 PL 设计的提示首先提出,一种好的语言应该支持程序员需要完成的工作——主要是设计、文档和调试。因此,编程语言的设计应该引导程序员将程序分解成可管理的部分,帮助程序员使代码大部分具有可读性,当代码发生错误时,不应该太难找到并修复它们。我认为这听起来是一个很好的方法,尽管我还想补充一点,编程在某种程度上也应该很有趣。
随后,Tony Hoare 提出了一些好的编程语言应该具备的特性:
最重要的是简单性。一种语言应该足够简单,程序员应该能够知道关于它的一切。似乎有证据支持这一观点。Smalltalk 非常简单,可以在明信片上完整地描述,它可能是最有效的通用编程语言。我发现一个博客曾经总结说,完美的编程语言是你已经知道的语言。所以这就引出了一个问题,如果坚持使用你熟悉的「糟糕」的语言,而不是切换到「更好」的语言,会不会更好?好吧,首先,尽管语言在统计上有显著的可测量的差异,但是转换的代价似乎总是比我们想象的要花更长的时间来偿还。不了解一门语言全部知识的代价也可能是非常昂贵的。但这些都是无聊的答案,在这篇文章中,我们寻求完美。
安全性很重要。生成的程序应该以可预测的方式运行,最好是无错误的。当 bug 简单地给出错误的答案时,它尤其糟糕,就像在汇编程序中对一个固定的点号执行浮点操作一样。将单位附加到数字上也是可取的,但据我所知,很少有语言允许这样做。
快速编译是迭代解决方案所必需的。任何经历过 sub-second 测试运行的紧凑 TDD 周期的人都知道,不能低估此属性的重要性。Flutter 的热重载或类似 REPL 的探索性编程确实将生产率提高到了新的水平。
代码应该运行得快速高效。不管处理能力增长有多快,我们仍然需要解决更大的问题。Tony Hoare 提出,一门好的语言应该反映计算机的实际工作方式,优化步骤应该产生新的源代码。这可能不完全实用,但我认为我们至少可以思考一下,应该知道哪些代码构造的性能特征。
可读性:能够理解别人的代码在做什么。显然,尤其在你的代码被读的次数比写的次数多得多的情况下,这一点尤为重要。
市场可以保持非理性的时间比你保持偿付能力的时间长。
这是经济学家 John Maynard Keynes 的名言。它说明了这样一个观点:我们并不总是认可最好的产品,有可能完美的语言已经被创造出来,但我们并没有使用它。
第一候选语言
task body Controller is
begin
loop
My_Runway.Wait_For_Clear; -- wait until runway is available (blocking call)
select -- wait for two types of requests (whichever is runnable first)
when Request_Approach'count = 0 => -- guard - no tasks queuing on Request_Approach
accept Request_Takeoff (ID: in Airplane_ID; Takeoff: out Runway_Access)
do -- start of synchronized part
My_Runway.Assign_Aircraft (ID); -- reserve runway (potentially blocking call if protected
Takeoff := My_Runway; -- assign "out" parameter value to tell which runway
end Request_Takeoff; -- end of the synchronised part
or
accept Request_Approach (ID: in Airplane_ID; Approach: out Runway_Access) do
My_Runway.Assign_Aircraft (ID);
Approach := My_Runway;
end Request_Approach;
or -- terminate if no tasks left who could call
terminate;
end select;
end loop;
end;
上世纪 80 年代,上述观点被大肆宣传,设计师 Jean Ichbiah 认为,十年内,世界上只会有两种编程语言,一种是 Lisp,另一种是 Ada。这不是一个完全不合理的想法。Ada 是在非常坚实的需求集合和与大量团队设计竞争下产生的,这是因为当时计算机语言是一门大生意。Tony Hoare 和 Niklaus Wirth 实际上是另一支半决赛队伍的顾问,获胜的队伍的语言更加复杂,他试图简化他们的意见。Ichbiah 没有想到的是,第一个 Ada 非常复杂,甚至连编译器都有性能问题,所以它有点延迟,然后,第二个编程语言——C 和 Unix 出现了。
但是,由于美国国防部希望创造一种语言来取代他们拥有的 160 种左右的语言,因此在 1991 年 Ada 成为北约系统的强制性语言(尽管例外情况经常被允许)。事实证明,Ada 实际上是一种设计精良的语言,除了军事系统外,它在太空任务、空中交通管制和法国高速列车上也得到了证明。在危急情况下,Ada 应该是最佳选择。对于 C 程序中的每 100 个错误,对应的 Java 程序中大约有 50 个错误,而 Ada 版本中只有 4 个错误。Ada 还有一种方言 Spark,在这里你可以正式证明你的程序的正确性。在生产率方面,Ada 与 Java 差不多。所以,除了复杂性,Ada 似乎非常完美。由于 Ada 在设计上的一致性,所以即使你不知道构造的具体细节,也可以很好地了解代码所做的事情。并且,Ada 有优秀的文档,文档中包含了为什么每个特征会存在。那些用 Ada 编写代码的人似乎也很喜欢这一点。
Ada 继续发展,并在 2012 年获得了合同。不幸的是,似乎不太可能出现 Ada 的复兴。
第二候选语言
-- Type annotation (optional, same for each implementation)
factorial :: (Integral a) => a -> a
-- Using recursion (with the "ifthenelse" expression)
factorial n = if n < 2
then 1
else n * factorial (n - 1)
-- Using recursion (with pattern matching)
factorial 0 = 1
factorial n = n * factorial (n - 1)
-- Using recursion (with guards)
factorial n
| n < 2 = 1
| otherwise = n * factorial (n - 1)
-- Using a list and the "product" function
factorial n = product [1..n]
-- Using fold (implements "product")
factorial n = foldl (*) 1 [1..n]
-- Point-free style
factorial = foldr (*) 1 . enumFromTo 1
似乎每年都有至少一个博主问:今年是否终于是世界恢复理智、Haskell 终于起飞的一年?Haskell 的搜索结果似乎一致称赞这种语言。难道我们使用其它语言的程序员只是不理智吗?
当然,Haskell 是一种非常有趣的语言,但它非常抽象。你真的需要六种不同的方法来实现阶乘吗?我想说,也许 Haskell 的主要关注点在于抽象代数和对数学的兴趣上。有些人确实在生产中使用 Haskell,所以我也看了他们的说法:
创建二进制兼容的 libs 是很困难的,这意味着你基本上希望从头开始编译所有内容,这可能会在一个大项目上花费数小时。
很难预测性能,一个小的重构会导致代码运行速度减慢几个数量级,甚至有经验的程序员也很难诊断它。
你需要监控你的内存使用情况,因为它会在一些输入上突然爆炸。
尽管有人声称,当 Haskell 代码编译时,它往往是正确的,但这似乎不像支持者所希望的那样正确。
默认情况下,所有东西都是无副作用的,因此如果你确实需要改变内部代码,则必须重写上面的所有层(例如,没有调试打印语句)。
Haskell 遇到了一个「神秘元组问题」,因为尽管类型的定义非常严格,但是每个函数中的组件可以有不同的名称。
那些使用 Haskell 的用户声称对此非常满意,但是一位生产用户说,当他们为了自己私人用途编写代码时,他们更喜欢使用 Python。
除了类型安全性,Haskell 似乎真的没有踩雷,所以我们可能根本没有错过完美的语言。继续向前!
在一门语言中我想要什么样的特性?
为了回答这个问题,我看了一下我使用过的语言,并尝试指定一些我喜欢的特性。如果我设计了一种语言,我会考虑使用以下这些特性。
Cobol
PERFORM LOOP VARYING MyCounter FROM 1 BY 1 UNTIL 10 ...
RECORD
01 namn PIC A(80).
02 personnr PIC 999999-9999.
Cobol 最初是美国国防部管理行政事务的权宜之计。与所有临时解决方案一样,60 年后,它仍在强劲发展,主要是用在银行和政府部门。它稳步发展,最新的版本是 2014 年的。从 for 循环可以看出 Cobol 非常冗长。它考虑的是让非编程业务专家也能够读取代码。虽然我很欣赏这个目标,但我不会模仿这么冗长的内容。另一方面,record 的定义也很棒!你只需指定自己拥有哪些字段以及它们是以何种模式写入的,计算机就会为你处理所有的读写操作。这是一个声明性语法的例子,我特别喜欢模式中的一些可视组件。
FORTRAN
integer, dimension(10, 10, 10)::a
integer, dimension(-5:5)::b
c = a(1, 3:7, 5) + b(3:-1:-1)
我们程序员可能会嘲笑 FORTRAN 是一种过时的语言,但是一旦你进入世界上任何一个物理机构,FORTRAN 很可能就占据了至高无上的地位。事实证明,FORTRAN 与物理学家思考工作的方式非常吻合,而且它还倾向于生成最有效的可执行文件。高效率的一个原因是缺少指针,这使得编译器可以进行更积极的缓存优化。FORTRAN 当然也经过多年的发展,最新的规范是从 2018 年开始的。
另一种在物理系大量使用的语言是 Python,不幸的是它的运行速度非常慢。为了改进这些问题,2009 年开始的一项工作提出了编程语言 Julia,它的目标是像 Python 一样简单,像 FORTRAN 一样快。总的来说,它在这方面很成功,而且它也是一种非常好用的语言。
我从 FORTRAN 中得到的是数组/向量功能。默认情况下,索引开始于 1,但也可以自主定义为从任何地方开始。在上面的代码中,我们看到索引从-5 到 5 的向量 b,向量 c 是一个 5 元素向量,其中元素是 a 和 b 向量切片中相应元素的总和。
其他语言
我使用了很多其他语言,所以在这里快速浏览其中一些语言,也许有些语言有鼓舞人心的效果:
BASIC 在 Apple II 上可用,它是一个简化的 FORTRAN,pre-vectors。
Pascal 是在那之后来的。结构化编程的典范,使用起来相当愉快。我记得 REPEAT..UNTIL 结构通常更符合逻辑。其它有用的特性有记录结构和将数值限制在范围内的能力等。
Forth 玩起来很有趣,但没什么用。我记得最清楚的是,几乎没有什么是预先定义的,你在某种程度上定义了你自己的语言。
PostScript 被用在 Irix 窗口系统 NeWS 上,我用了相当多的时间来尝试和定制它,主要是为了好玩。我还是觉得很有趣。PostScript 堆栈在一些算法中非常有用,编写代码有点像做拼图。它有 postfix 符号来操作堆栈的顶部元素,所以「253 add mul」将变成 16
Tcl 在某种程度上是对 csh 逻辑异常的一种反应,因此它被设计成具有非常逻辑和统一的语法,这是一件好事。它可以用作 tclsh 的 shell,但我认为在 wish 中与优秀的窗口工具包 Tk 一起使用更为常见。我仍然随身携带着一个轻量级但功能相当不错的编辑器,它是用 427 行宽敞的愿望代码编写的。1998 年,我使用浏览器的 tclplugin 创建了一个 SPA。
我学这个计划是因为每个人都应该在某个时候尝试一下 Lisp。这很有趣,但我真的没有任何理由用它来做任何实质性的事情。括号太多了,我没有任何顿悟。现在我在探索 Shen,它有一些非常好的语法特性,嵌入式 Prolog 和一个可选的基于顺序逻辑的类型系统。
有一次,当我正努力用 AWK 处理一些文本时,一位同事建议我试试 Perl,书中的第一个例子起到了这个作用,于是我开始了一段和 Perl 的短暂恋情。当我试图理解我以前编写的一些程序时,它很快就不起作用了。在 Perl 中,如果你知道 magic 操作符,那么任何东西都是一行。因此,Perl 将提醒你不要为各种问题发明很多操作符。
C 语言—更坏也更好
如前所述,编程语言曾经是一门大生意。有人会创建一台计算机,为它设计一个操作系统,然后语言编译器是可选的附加组件。但后来 Unix 出现了,在构建计算机之后,你所要做的就是创建一个相当简单的 C 编译器,并以复制成本获得 Unix 源代码。既然你有 C 编译器,你也可以免费把它装进去。因此 C 语言成为世界上最成功的计算机病毒。
人们会不遗余力地说服自己,这简直是免费的午餐。但仅仅因为 Unix 是用 C 编写的,并不意味着用 C 编写应用程序是件好事。与当时可用的 FORTRAN、Pascal 和其他语言相比,C 语言可能是一个让你想自杀的极好的工具。
从另一方面来说,C 语言是最接近我们可能得到的通用语言的东西,回顾过去,很难想象编程语言、操作系统和许多软件在没有 C 语言的情况下是免费的。
我已经成功地避免了 C++,这是我非常庆幸的。它就像是猪身上的口红,层层叠叠的有着不必要的复杂性。有趣的是,当 GO 被显式地替换为 C++时,结果喜欢 C++的程序员发现,C++ 在复杂程度上很高,这使得他们感觉自己是宇宙的主宰,除了 Haskell 之外,他们永远不会换用任何其他语言。
DSL-解析器/生成器
在 20 世纪 90 年代末,有一些小小的活动来创建特定于领域的语言,因此有一个解析器/生成器是非常有帮助的。我使用了 yacc/lex(和 GNU 等价物 bison/flex)以及 Javacc。我对以前版本的 ANTLR 并不太感兴趣,但是最新的 ANTLR4 非常好,它只是处理你编写规则的方式,其主要思想当然是用描述性声明的方式描述语法。
structureLiteral: LeftBrace (keyValue Comma?)* RightBrace;
keyValue: Key valueProduction;
在某种程度上,我希望它将开始逐渐衰败,将所有内容编码为 XML、YAML 或 JSON,只是为了免费获得解析,并且需要创建更具表现力的语法,所以我肯定认为一种语言应该包含各种类型的解析器/生成器。
SQL-必不可少的恶魔?
with areas as (
select c.name, count(*) size from closest c
left join infinites i on c.name = i.name
where i.name is null
group by c.name
) select max(size) from areas;
每当我怀着恐惧的心情去写一些 SQL 语句时,我总是拿着一份「SQL for Dummies」,尤其是在有 joins 之类的时髦东西的时候。但是你可以用 SQL 做一些非常强大的事情,只需考虑用一种「普通」的编程语言来做同样的事情。我的灵感来自于 Cygni 的一位同事,他有时使用 SQL 作为应用程序代码,上面的代码来自 aventofcode 2018,在那里我开始使用 SQL 来解决问题。过了一会儿我就放弃了,因为 SQL 不擅长迭代,特别是我使用的 mariadb 版本,但是我也很欣赏 SQL 的优点。
有一些人喜欢批评 SQL,最显著的是「第三个宣言」,其中描述了一系列优秀的数据库语言,称为「D」,这些语言也扩展到了一般编程。「D」的一个版本是「Tutorial D」,它是为教学目的而开发的,目前正在 reldb 中使用。
我认为在语言中有一些关系概念或数据结构是一个好主意,即使在 C 语言中做一些类似 LINQ 的事情。但是,我首先要从 SQL 中获得的是空值处理的性能。
JavaScript-是爱是恨?
let parse = {
s: function(s) { return [Number(s)]; },
x: function(s) {
return s.split("/").map(Number); },
p: function(s) { return s.split("/"); }
}
let args = parse[m.charAt(0)](m.substr(1));
JavaScript 是许多人讨厌的语言,但也有很多人喜欢它。我喜欢从一个解决方案中以声明的方式创建函数图。但我讨厌当出了问题的时候,我不知道问题在哪里。
Java
class Car {
int topSpeed() { return 200; }
}
class SportsCar extends Car {
int topSpeed() { return 350; }
}
Car myCar = new SportsCar();
System.out.println(myCar.topSpeed());
我真的很喜欢 Java。它的效率比 C 高 30%-200%,错误率是 C 的一半。如果我们看一下我们在「PL 设计提示」一开始所设定的标准,我认为它覆盖了它们。
很难选择特定的特性,因为我认为是组合包实现了这一点——许多成功所必需的东西正是很多人喜欢抱怨的。我认为这是伟大的,但对于其他一些语言来说却不是这样。另一个需要考虑的是包的结构。
我认为 Java 的一个错误是它没有简单数据对象的记录或结构类型。
当我说完这番话,总有人问我为什么不提 C#,原因很简单,因为我没有充分使用 C# 语言,不理解它与 Java 的区别(除了它让我恼火的所有方面)。撇开我的偏好不谈,他们似乎有着非常相似的生产率配置文件,而且我不知道有什么足够的客观原因来选择其中一个(除了平台问题,因为反正没有人使用.NET core)。另一方面,视觉语言的效率似乎提高了 30%。
XSLT
<xsl:template match="section[name=’top’]/rule">
<ul class='{@class}'>
<li style='list-style: none'>
<xsl:apply-templates />
</li>
</ul>
</xsl:template>
XSLT 是我一直以来最喜欢的语言,它让我头脑中的各种灯都亮了起来,是引发我对编程语言进行分析的原因。我想用类似 XSLT 的风格来编程,不管这意味着什么。当然,所有的数据在任何时候都应该是 XML 格式的,但是这被整个 XMLSchema 的胡说八道以及用供应商产品替代 freedom 的其他做法扼杀了。
回到 XSLT,看看这种语言的力量。实际上,这里没有太多的代码。match 语句简洁地指出,每当我们遇到「rule」元素,当它是具有值为「top」属性的「section」元素的子元素时,我们应该从这个模板中得到一个结果。现在,你通常会编写多少代码来确定类似的内容?XSLT 几乎没有代码来产生结果,它只是直接写在那里。
我使用 XSLT 的一个「亮点」是我的视角改变了。我不是用命令编写程序,而是用机器运行输入,所以输入实际上是控制输出的程序。
XSLT 的另一个特点是它非常明显的同质化,也就是说,程序本身只是另一个程序可能输出的数据。这是一个有趣的特性,但是如果你真的用它来编写程序,可能会变得很难维护。
Go
rCh := make(chan result)
for _, n := range numbers {
go decomp(n, rCh)
}
rs := []result{<-rch code="">
Go 可能与 Haskell 完全相反,因为它缺少语言理论上必须具备的几乎所有特性。它是一种僵硬、枯燥和缺乏想象力的语言,对程序的高效开发非常有帮助。尽管缺乏特征,但对于实践中存在的每一个问题,在 Go 中都有一个优雅实用的解决方案。
Go 是为了更好地适应谷歌开发的语言类型,主要对 C++的复杂度和编译速度的反应。它编译速度非常快,具有垃圾回收功能,并利用 CSP 并发模型允许轻松、安全地使用并发。
在用 Go 编程的同事们表示,他们更喜欢使用 Go,并不再纠结于如何用不同的语言「优雅地」完成任务。
我从 Go 中得到的启发是,不去追求某种理论上的「特征完整性」是可以的。
Dart
void part1(List<Nanobot> bots) {
var largestRangeBot = bots.reduce((a,b) => a.range > b.range ? a : b);
bool inRange(Nanobot b) {
return manhattanDistance(largestRangeBot, b) <= largestRangeBot.range;
}
var numInRange = bots.where((b) => inRange(b)).length;
stdout.writeln(numInRange);
}
当使用 V8 引擎的 javascript 开发人员开始考虑如何使程序运行得更快时,他们意识到必须从 javascript 中去掉一些难以加速的垃圾。Dart 看起来并没有什么特别之处,它就好像 Java 和 Javascript 有了一个婴儿,它最终成为一种大家都已经知道的语言。
你为什么要用 Dart?好吧,事实证明,去掉 Javascript 中的垃圾,从 Java 中加入一些好的部分,最终得到了一种语言,这种语言比它的「双亲」工作起来愉快多了,而且效率更高。尽管 web 社区几乎完全放弃了它,但你可以使用 Dart 并将其转换为 Javascript,代码的效率往往比任何人手工编写的代码都要高。它在谷歌内部被大量使用,因此不会有陷入困境的风险。还有一个杀手级的移动开发环境叫做 Flutter。
到目前为止,我的主要收获是提供一个大型且功能强大的标准库的「包括电池」政策。
使用哪种线程安全模型?
每一种现代编程语言都需要一种处理并发性的好方法,我们不能寄托于运气了。那该选哪一种呢?
不可变;纯函数,如 Haskell
一种理智的、可调节的内存模型,如 Java
单线程隔离,如 Dart 和 Javascript
通信顺序进程,如 Go 和 Ada
有安全检查规则,如 Rust
事务内存,类似于许多语言的附加组件,而 Culjure 内置
我不知道我会选哪一个,它们都有各自的优势。目前我倾向于将不可变性和事务性内存结合起来。
我卑微的尝试,Tailspin
现在我已经考虑这个问题 15 年了,我想是时候尝试创造一种语言了,希望它足够有趣。下面是一些代码示例。
首先是 FizzBuzz 的实现:
templates fizz
$ mod 3 -> #
<0> 'Fizz' !
end fizz
templates buzz
$ mod 5 -> #
<0> 'Buzz' !
end buzz
[ 1..100 -> '$->fizz;$->buzz;' ] -> [i](<''> $i ! <> $ !)... -> '$;
' -> !OUT::write
我们首先定义真正的函数,但我决定改掉名字,以避免陷入先入为主的概念。因此,我们定义了一个名为「fizz」的模板部分,它简单地获取输入模 3 并发送它进行匹配。如果它是零,它输出字符串「Fizz」,否则什么也不会发生。我们对「buzz」也一样。
在最后一行中,我们首先创建一个列表/数组,其内容是通过将整数的流/范围从 1 到 100 转换为一个字符串来生成的,其中第一部分是 fizz 模板的输出,第二部分是 buzz 模板的输出。然后将整个数组发送到提供索引 i 的数组模板中,在这里我们匹配每个元素。如果是空字符串,则输出索引,否则输出字符串。然后,我们将所有数组元素流式输出,并将它们转换为一个字符串,在最后加上一个换行符,然后将其发送到 stdout。请注意,「$」在每个转换步骤的含义都会更改为表示进入该步骤的当前值。
接下来,我们有一个小程序将单词放在一行上,并按相反的顺序打印出来:
composer words
[ <word>* ]
rule word: <~WS> <ws>?
end words
$IN::lines -> '$ -> words -> $(-1..1:-1)...;
' -> !OUT::write
这里我们有一个不同类型的函数,一个 composer,它接受一个 unicode 字符流并将它们解析到第一行的产品中,一个「word」产品数组。
「word」按规则生成一个连续的非空白字符元素,后跟一个可选的连续空白字符元素。如果我们想忽略/丢弃空白,我们可以将该产品放在括号中,比如「(<ws>?)」,但是反过来,我们还是希望单词之间有空格,所以为什么不保留它呢?
在最后一行中,我们从 stdin 读取一系列行,并为每个行创建一个以 new line 结尾的新字符串,其中的内容是解析为数组的原始行,然后将其反转并流式输出。然后打印字符串。
最后一个例子是计算第 n 个斐波那契数的模板部分:
templates nthFibonacci
{ N: $, n0: 0, n1: 1 } -> #
<{ N: <0> }>
$.n0 !
<{ N: <1..>}>
{ N: $.N - 1, n0: $.n1, n1: $.n0 + $.n1} -> #
<{ N: <..-1>}>
{ N: $.N + 1, n0: $.n1 - $.n0, n1: $.n0} -> #
end nthFibonacci
8 -> nthFibonacci -> !OUT::write
在模板中,我们首先创建一个表示当前状态的对象,因此 N 是输入,n0 和 n1 是斐波那契函数的种子。此对象被发送到匹配器。
如果 N 为零,我们的工作就完成了,n0 是我们正在寻找的值。
如果 N 是 1 或更大,我们创建一个新的状态对象,其中 N 减少,斐波那契关系向前一步计算。然后这个新对象被发送回匹配器。
如果 N 是负的,我们增加 N 并反向执行斐波那契步骤,然后发送给匹配器。
这是完美的语言吗?我不知道,但它当然不止如此,但到目前为止,我真的很高兴使用它进行编码和开发。如果您感兴趣,可以查看它的 github。
via:https://cygni.se/the-perfect-programming-language/
雷锋网雷锋网雷锋网