什么是好的编程语言?

作者 | Torbear Gannholm

编译 | skura

本文作者是高级系统开发工程师 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 = 1factorial 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" functionfactorial n = product [1..n]-- Using fold (implements "product")factorial n = foldl (*) 1 [1..n]-- Point-free stylefactorial = 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 ...RECORD01 namn PIC A(80).02 personnr PIC 999999-9999.

Cobol 最初是美国国防部管理行政事务的权宜之计。与所有临时解决方案一样,60 年后,它仍在强劲发展,主要是用在银行和政府部门。它稳步发展,最新的版本是 2014 年的。从 for 循环可以看出 Cobol 非常冗长。它考虑的是让非编程业务专家也能够读取代码。虽然我很欣赏这个目标,但我不会模仿这么冗长的内容。另一方面,record 的定义也很棒!你只需指定自己拥有哪些字段以及它们是以何种模式写入的,计算机就会为你处理所有的读写操作。这是一个声明性语法的例子,我特别喜欢模式中的一些可视组件。

FORTRAN

integer, dimension(10, 10, 10)::ainteger, dimension(-5:5)::bc = 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 fizztemplates 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 :https://github.com/tobega/tailspin-v0。

via:https://cygni.se/the-perfect-programming-language/

本文分享自微信公众号 - AI研习社(okweiwu)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-11-21

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏码匠的流水账

聊聊rocketmq的retryAnotherBrokerWhenNotStoreOK

本文主要研究一下rocketmq的retryAnotherBrokerWhenNotStoreOK

5300
来自专栏java思维导图

Java中堆和栈的区别在什么地方?来看些实例分析!

(1)Java的堆是一个运行时数据区,类的对象从堆中分配空间。这些对象通过new等指令建立,通过垃圾回收器来销毁。

7020
来自专栏Java技术栈

20 亿的 URL 集合,如何快速判断其中一个?

假设遇到这样一个问题:一个网站有 20 亿 url 存在一个黑名单中,这个黑名单要怎么存?若此时随便输入一个 url,你如何快速判断该 url 是否在这个黑名单...

6920
来自专栏喵君手记

JavaScript引擎相关名词

8330
来自专栏泰斗贤若如

匹马行天下之思维决定高度篇——道道道,学习Java之道

你们好!我是来自地球的一名Java程序员,首先我代表地球人对贵星球的高司令来到地球传授Java语言,造福了全人类,造福了整个地球表示由衷的感谢!

7740
来自专栏JAVA同学会

JAVA中锁的解决方案

在上一节中,我们给大家介绍了什么是锁,以及锁的使用场景,我相信大家对锁的定义,以及锁的重要性都有了比较清晰的认识。在这一节中,我们会给大家继续做深入的介绍,介绍...

9320
来自专栏java思维导图

Java并发编程73道面试题及答案 —— 面试稳了

最近后台和微信理有很多读者让我整理一些面试题,我就把这事放在心上了,于是在各大网站和其他公众号里面搜索面试相关的高质量文章或者信息,今天主要整理一下 Java ...

6920
来自专栏Java3y

Java必考面试题「版本特性」

Java 8用默认方法与静态方法这两个新概念来扩展接口的声明。与传统的接口又有些不一样,它允许在已有的接口中添加新方法,而同时又保持了与旧版本代码的兼容性。

13120
来自专栏江湖安得便相忘

遍历数组,我翻车了-Rust

其他的多数语言中的, 数组直接就是可迭代的,无论是下标遍历还是迭代器迭代,都可以运行,所以刚开始用Rust的时候就翻车了。

14700
来自专栏从零开始学自动化测试

jmeter压测学习2-linux运行jmeter环境

使用jmeter做压测的时候,在windows上不太稳定,所有一直在linux服务器上使用jmeter做压力测试。 本篇记录下linux上搭建jmeter环境,...

6710

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励