一种不会影响您对编程的思考方式的语言是不值得了解的。——Alan Perlis 我写这本书是为了当我开始学习 APL 时我想读的书。为使用一两种不同编程语言的有经验的从业者介绍 APL。我们都以不同的方式学习,我更喜欢先公开基本概念,然后通过实例学习。在发现了 APL 衍生产品 K 中的 Advent of Code 2015 挑战的解决方案文件后,我来到了 APL。这是大约 100 行实际代码,虽然我没有理解其中的任何内容,但我一直在查看它,试图找出这 50 个问题(好吧,49 个)中的哪一个是解决方案。对于大部分问题,我的每个 Python 解决方案通常都运行到 50-100 行以上。 APL 是一种数组语言,也是当今仍在使用的最古老的编程语言之一,仅次于 FORTRAN、Lisp 和 COBOL。 APL 使用自己的奇怪符号,例如 ⍎⌽⍕⌈*○≡⍬,而不是像 C 或 Python 等大多数其他语言一样用英语写出的保留字。作为一种语言,APL 处于非常高的抽象级别,使其非常适合算法的超简洁公式化。 APL 是一种时间才刚刚开始赶上的语言。现代处理器采用专用的面向向量的指令,而 APL 表现出高度的机械共鸣,非常适合 SIMD 指令,并且通常本质上是完全无分支的。 APL,还有它更朋克摇滚的小姐姐K,真的飞起来了。 APL 可以提供前所未有的程序员效率,以及全面的执行速度。您将有自己想要学习 APL 的动机。如果你正在寻找市场上最热门的东西,在 FAANG 找到一份工作,或者用最畅销的技能来充实你的简历,还有许多其他编程语言可以提供更好的回报:Go、Java、Rust ... APL 没有出现在 TIOBE 指数前 100 名“最流行”编程语言中。显然,在撰写本文时,COBOL、Logo 和 AWK 都比 APL 更“流行”。这并不是说对于拥有“艾弗森家族”语言(APL、J、K、Q 等)技能的工程师来说,尤其是在金融领域,没有高薪的工作。有。然而,学习 APL 会以其他方式回报你。也许关于“扩展你的思想”的老套路已经过时了,但如果你的背景是“类 C”——C、C++、Java、Python 等——你将接触到一种解决问题的新方法。事实上,学习来自不同范式的新编程语言对你的大脑来说是一种力量倍增器。
谈到 Lisp,但同样适用于 APL,Eric S. Raymond 在他的文章《如何成为一名黑客:Lisp 值得学习》中有一个不同的原因——当你最终得到它时,你将拥有深刻的启蒙经验。即使你从未真正经常使用 Lisp 本身,这种经历也会让你在接下来的日子里成为更好的程序员。关于 Lisp 的话题,我最喜欢的一篇 xkcd strips 将 Lisp 的括号称为“更文明时代的优雅武器”,意译“星球大战”。也可以说是关于 APL。 APL 被认为是一种思想符号。实际上,它甚至需要数年时间才能作为一种编程语言进行实际实现。 APL 是一种非常自然的算法表达方式。 APL 的创造者肯·艾弗森 (Ken Iverson) 因其在 APL 方面的工作而于 1979 年获得 ACM 图灵奖,而他的随附论文 Notation as a Tool of Thought 在某种程度上是计算机科学的里程碑。 Dyalog 是一家领先的商业 APL 解决方案提供商,在他们的公司标语中提到了这一点:“软件解决方案的思想工具”。如果你坚持下去,你可能会发现一个额外的好处:你用其他语言编写的代码也会突然变得更加简洁和高效。每当 APL 在 Hacker News 上被提及时(它经常发生),有人会在评论中啁啾说“APL 不可读”,所以让我们提前处理一下。关键是可读性在旁观者的眼中。对于 APL 程序员来说,上述中国剩余定理的实现是完全可读的。声称某些东西是不可读的,因为你无法阅读它在智力上是不诚实的。我不会说,也不会读日语,但这并不会使日语在任何方面都无法阅读。作为俳句大师,Bashō(松尾金作)惊呼:
更公平的观察是 APL 与您所知道的其他编程语言完全不同,是的,学习 APL 肯定会带来不同类型的挑战。如果您是一位经验丰富的(比如说)Python 程序员,并且您决定选择 Ruby,那么您可以合理地期望从第一天起就遵循其他人编写的代码,并在一两天内学习足够的语法来自己编写代码。当然,找到绕过标准库的方法并学习如何编写惯用的 Ruby 需要更长的时间,但仍然如此。如果您已经学习了几种类似的语言,那么学习另一种语言代表着已知且可量化的努力。老实说:在学习 APL 时会有更多的初始摩擦——对于初学者来说,你的键盘甚至没有波浪形符号!话虽如此,APL 是一种微小的语言 - 可以忽略的语法数量可以忽略不计,不管你信不信,一旦你克服了实际障碍,实际上在表面上很容易学习。需要更长时间的是掌握 APL 方式——如果你愿意,如何惯用地使用它。学习如何以数据并行的方式解决问题,没有循环,没有分支,是编写出色的 APL 的关键。一旦你习惯了它,APL 就比更冗长的语言更具可读性 [需要引用]。它去除了所有的绒毛,只留下算法的意图。 ok 的创建者 John Earnest 写了一篇关于这个主题的幽默博客文章,Aaron Hsu 关于 APL 模式和反模式的演讲以更严肃的方式提出了一些相同的观点。 John Earnest 的博客文章中的最后一个例子提出了一个假设性问题——它更具可读性,JavaScript 虽然 APL 可以追溯到艾弗森试图修复传统数学符号的一些缺点,但你不必是一个数学家——了解 APL 或从中受益。如果您熟悉 sums 和 product,或者如果您知道另一种编程语言,这就是您所需要的。如果您确实有数学背景,尤其是线性代数背景,那么您无疑会在此过程中认识一些概念。我们将在这里主要使用 Dyalog 的 APL 方言,它已经存在了很长时间,并以向后兼容性的形式承载了很多历史。这些年来,APL 也发生了很大的变化。 Dyalog 的 APL 实际上是两种完全不同的语言合二为一:传统风格,它是程序性的,以及较新的 dfn(发音为 DEE-fun)风格,它是功能性的(ish,但我们不要狡辩)。出于本练习的目的,我们将假装传统风格不存在。
当我们声称 APL 中没有 if 语句或循环时,怎么会写 ∇ FizzBuzz end ; i : For i : In ⍳ end : If 0 = 15 | i ⎕ ← 'FizzBuzz' : ElseIf 0 = 3 | i ⎕ ← 'Fizz' : ElseIf 0 = 5 | i ⎕ ← 'Buzz' : Else ⎕ ← i : EndIf : EndFor ∇ 这当然看起来既不确定又乱七八糟 - 我敢说即使是中等可读性,几乎是 Pascal-chic?这是一个 APL trad-function 的例子,从现在开始我们将假装它不存在。在本书的上下文中,这与其说是一种价值判断,不如说是一种实用判断。我们不会涉及传统风格,也不会涉及 Dyalog 的面向对象扩展,为了简单起见,当我们从现在开始谈论 APL 时,将其理解为“我们的 APL 子集”。撇开历史不谈,据约翰·斯科尔斯 (John Scholes) 称,“较新”的 dfn 样式于 1997 年初首次出现在 Dyalog 8.1 中。所以不是那么新。具体来说,我们不打算涵盖 tradfns、OO、逆函数、三角函数、复数、变体、工字梁、生成、线程、隔离、格式、大多数 ⎕-fns、.NET 集成以及可能更多. APL 存在的理由是消除噪音,成为思想的延伸。一个崇高的目标,当然。 APL 代码可以非常紧凑;没有其他通用编程语言(除了它的兄弟)接近。我们在这里编写的任何代码都不可能超过几行,而且根据个人经验,与 Python 相比,差异可能达到一个数量级。是不是更好?这里意见不一。 APLers 只是半开玩笑地说他们从不滚动。 K 的创造者 Arthur Whitney 据说讨厌滚动,即使在用 C 编写时也是如此:
K 二进制文件的大小约为 50Kb。有人询问了解释器源代码。来自微软的访客的脸上闪过一丝皱眉:这有什么有趣的? “目前的来源是 C 的 264 行,”亚瑟说。我以为我听到了一个低声的声音“那是不可能的”。 Arthur 向我们展示了他如何将源代码排列在五个文件中,以便他可以在不滚动的情况下编辑其中的任何一个。 “讨厌滚动,”他咕哝道。能够在单页代码上看到整个实现减少了上下文切换,至少在轶事上使错误更容易发现。如果您对 APL 的历史感兴趣,可以阅读 Kromberg 和 Hui 自 1978 年以来的重要论文 APL。Morten 和 Roger 自己做所有的特技。一个全面的,但现在有点过时的参考资料是罗格朗的门塞 Mastering Dyalog APL,共有 800 多页。它涵盖了很多我们在这里没有涉及的领域。正在更新 MDAPL 以涵盖更新版本的 Dyalog。随着您在精通 APL 的道路上走得更远,APL Cart 索引的习语集合是一个了不起的资源。 Stack Exchange 上的聊天室 APL Orchard 拥有一些阵列编程电路方面最敏锐的头脑。它也是一个欢迎新手的地方,不时举办所谓的培养——即兴互动课程。这里有一份古老的栽培清单。过来打个招呼——你可能会得到一节私人 APL 介绍课(在公共场合)。 Dyalog 自己的开发人员文档是一个参考 - 深度丰富,但不是学习新事物的最容易的地方。 Dyalog TV 主持 Dyalog 的网络研讨会和大量会议演示的后备目录,其中包含许多不同的内容。还有 Dyalog 论坛,虽然数量有点少。
Dyalog 的 dfns 工作区文档是 APL 异域风情的宝库。它是 Dyalog 的“标准库”,其中一些对日常编码很有用(如 segs 和 iotag),而其他东西可能不太有用——但你可以通过查看它的实现来了解什么“真正的APL”看起来像。说到“真正的APL”,Dyalog在他们的github上有很多公开的代码,是很好的资源。有几个选项。要立即开始,您可以前往 TryAPL,它是 Dyalog APL 最新和最出色版本的网络版本。这应该适用于我们将在这里做的大部分事情。 Dyalog APL 是商业产品,不是开源产品。幸运的是,他们在许可条款中提供了慷慨的非商业性免费使用条款,因此您可以在本地免费下载和安装 Dyalog APL,只要您不从中赚钱。 Dyalog 由 APL 解释器本身和一个 IDE 组成。您还需要安装带有 APL 字形(实际上称为“波浪线”)和键盘布局的字体,具体取决于您的平台。我在 MacOS 上使用 Dyalog 版本 18,它带有一个名为 RIDE 的 IDE。在 Windows 上,您会获得不同的 IDE。还有 GNU APL,虽然有相似之处,但它的方言有点不同。 GNU APL 是 IBM 的 APL2 的自由软件重新实现,而 Dyalog 已经从这一点向前发展了很多。我们在此展示的示例不太可能在 GNU APL 中按原样运行。其他选项是 ngn/apl 和 dzaima/apl,这两个爱好者实现都大致实现了我们感兴趣的子集。 Dyalog 也有一个优秀的 Jupyter 内核,它允许您在 Jupyter notebook 界面中使用 APL——这确实是本书的方式是写的。
假设您已经设法安装了 APL 键盘布局,那么您将需要搜索和啄食一段时间。需要几天时间才能了解最有用的字形所在的位置。 Dyalog IDE 顶部有语言栏,您也可以使用它——将鼠标悬停在字形上将指示它在键盘上的位置。下面是我的 RIDE 在键盘布局方面的显示: 我们要做的第一件事是指定我们的索引原点。在当今使用的大多数编程语言中,数组从 0 开始索引,但有一些值得注意的例外(例如 Julia 和 Lua)。在 APL 中,索引来源是一个可配置的选项。暂时搁置一下这是否是一个好主意,这是迟早会引起您注意的事情之一。我们会尽量做到明确。如果在尝试运行其他人编写的代码时遇到 INDEX ERROR,则值得更改索引原点以查看是否有所不同。 Dyalog 的默认索引原点为 1,但我们将其设置为 0 以减少意外。我们暂时不会在真正重要的地方使用任何东西,但让我们从一开始就习惯它。我们将其发音为“quad-eye-oh 变为零”。左箭头 ←,称为 Gets,是赋值的符号。 ⍝ 符号后面的绿色文本是注释。 APL 的注释符号称为 Lamp - 有点像备忘录 - 注释应该照亮代码。在我的键盘上,它存在于逗号键上,并且是一个有用的第一个 APL 键序列来记忆。它看起来像一盏灯吗?也许是LED?或者煤油灯,取决于你做 APL 的时间。无论如何 - 评论。好吧,这可能是一个惊喜。事实证明,* 是求幂的符号,而不是乘法。如果我们想乘,我们需要使用 ×(它位于 - 键上):不。斜线做了一些绝对不是除法的事情。我们稍后会回到那个。除法是÷,不是/:
负数不是用传统的减号写的,而是用一个特殊的字形,高减号: ¯ 在任何其他语言中,或者实际上在计算器上,它会给出 17。那么这里发生了什么?从第一年开始,我们就被教导乘法先于加法。我们偶然发现了促使艾弗森创建 APL 的数学符号中的一件事:在数学中,运算符优先级有点尴尬和不规则。在 APL 中,一切都严格从右到左计算。如果你想改变这个评估顺序,你需要括号。我们可以使用我们之前遇到的 Gets ( ← ) 为变量赋值: ⊢ a ← 42 ⍝ a 得到 42,请评估它以便我可以看到值。字形 ⊢ 被称为右点,是一个函数引用一样。它只是返回它的参数。像这样使用它允许我们显示表达式的结果,即使表达式本身通常不会返回结果。正确的定位可能看起来是一个毫无意义的功能,但我们稍后会在不同的上下文中很好地使用它。要“打印”变量的值,我们也可以使用 Quad gets,⎕ ←:作为助记符,在这种情况下,您可以想到代表计算机屏幕(或打印纸)的 Quad (⎕) 字形。为了避免任何疑问,Quad 字形确实是一个小矩形,而不是用于在格式错误的网页上显示字符集不匹配的符号。
APL 变量名称遵循与其他编程语言基本相似的规则——字母(大写和小写)、数字(除了第一个字符)和下划线符号 (_) 的任意组合: 538 ⎕ ← My_Variable_Name ← 'hello world' 你偶尔会看到的一个约定是 _ 变量,它有时用于表示我们不关心的值。名为 _ 的变量没有什么特别之处——它只是一个约定。 ( ab _ d ) ← 3 1 4 1 ⍝ 不要在意第三个值 abd 如果您觉得需要在其他人的生活中添加额外的趣味阅读,您可以在名称中使用两个额外的字符,⍙ 和 ∆你的代码。 APL 使用字形代替保留字的一个优点是,不可能创建与 APL 中的内部或保留字冲突的名称。您当然可以使用任何令您满意的命名约定,但来自 Dyalog 的 Adám 编写了一份非正式的风格指南,值得一读。由于 APL 倾向于简洁的风格,冗长、杂乱无章的 Java 式变量名是不受欢迎的。这里也有一些来自 Carlisle Group 的自以为是的家庭真相,供您在 APL 之旅中考虑。价(有时也称为 arity)指的是函数期望参数的方式。在 APL 中,我们讨论 monadic 和 dyadic 函数和操作符(偶尔也会讨论 niladic)。
在撰写有关编程的文章时,有一个中心原则,即每提及 monad 一词,您就会失去一半的读者。我们可以为此责怪 Haskell 团队。不要害怕——我们完全脱离了范畴论。虽然 APL 具有一元函数和运算符,但该术语在一定程度上早于 Haskell,并且实际上是指完全不同的东西。嘿,哈斯克尔,你自己说吧,好吗?开个玩笑,我们是 Haskell 的忠实粉丝。 APL 函数可以在任一侧接受参数,就像您完全习惯于使用许多其他语言(不是 Lisps)中的算术运算符一样:在 Lispy 语言中,函数应用程序始终是 monadic,即使是算术,因此您可以编写 When我们说一个函数在 APL 中是二元的,我们的意思是该函数同时接受一个左参数和一个右参数,就像上面的 + 例子一样。相反,一元函数是只接受正确参数的函数,例如,阶乘,!:一些 APL 函数可以是一元函数或二元函数,并且在许多情况下它们具有不同的含义——对于初学者。例如: 7 × 3 ⍝ Dyadic × 是乘法 × ¯7 ⍝ Monadic × 是'方向',或符号(在我们的例子中是符号)。谢谢。在 Dyalog IDE 中,如果您将鼠标悬停在语言栏中的一个字形上,它将在适用的情况下显示其一元和二元用法的简短示例。有些人声称您可以通过将鼠标悬停在此栏上来学习整个语言。