BQN 中的基础概念:究竟什么是原语
速览
本文深入探讨了 BQN 编程语言中的核心概念——“原语”(Primitive)。文章解释了原语作为语言构建块的作用,以及它们如何定义 BQN 的语法和语义基础。理解原语对于掌握 BQN 编程范式至关重要。
AI 深度解读
BQN:什么是“原语”(Primitive)?
背景
在数组编程语言(Array Programming)的社区中,BQN 的设计者经常面临一个疑问:BQN 中的“原语”(Primitives,即语言中最基础、不可再分的操作符集合)究竟是如何被选定的?
对于不熟悉数组编程的外部人士而言,他们可能会误以为 APL(BQN 的前身及灵感来源)的“核心理念”仅仅是将最常见的任务用符号而非名称来书写。甚至像 Edsger W. Dijkstra 这样的计算机科学家也曾批评 APL 是一个“技巧袋”(bag of tricks)。作者认为这种观点并不准确。
本文旨在阐述作者个人对于为何要将某些特殊操作称为“原语”并赋予其专用符号的看法。虽然这一观点与其他数组语言设计者的思想有重叠之处,但此处仅代表作者个人的思考。文章将首先讨论为何不应将某些函数视为原语,进而分析符号的局限性,最后深入探讨原语设计的哲学基础及其数学本质。
核心内容
语言与符号的权衡:为何不将所有操作都符号化?
要理解为何只有一小部分操作被定义为原语,首先需要理解符号(Symbols)的劣势。如果所有操作都使用符号,会带来以下问题:
- 表意精度:单词(Words)能更精确地传达含义。
- 组合灵活性:单词可以通过更复杂的方式进行修改或组合。
- 语义丰富度:拥有既定含义的单词数量远多于符号。
- 输入与发音:单词更容易输入和发音。
语言(即自然语言)比符号系统提供了更多的可能性和细微差别。如果存在两个相关概念,通常可以赋予它们既体现关联又体现差异的名称(例如 KeepBefore 和 KeepUpTo)。相比之下,符号难以表达这种细微差别:仅能使用少数几种广泛的关系表示法,如镜像反射(这容易出错!)或并置(juxtaposition)。
然而,细微的单词也可能成为负担。如果某种操作只有一种可能的实现方式,那么一个精心选择的符号可能比单词更能清晰地指示其含义,且更容易记忆。
此外,语法角色(Syntactic role)也是 BQN 设计中的一个关键因素。在 BQN 中,字母大小写和下划线允许任何单词以任意角色书写;而原语具有固定的角色。如果一个值既可能被调用为函数,也可能作为参数传递,使用名称会比使用符号更易于处理。
原语设计:发现而非发明
语言设计既是发现的过程,也是发明的过程,但作者认为原语的设计应主要基于发现(Discovery)。
- 发明(Invention):如果一个函数感觉像是被“发明”出来的——甚至更糟,是被“工程化”出来的——那么它就不适合成为 BQN 的原语。发明总会留下发明者的印记,包括那些本可以做出不同选择的小决定。
- 发现(Discovery):意味着被讨论的事物在某种程度上独立于描述它的人之外。如果两个不同的人“发现”了同一个事物,他们得到的将是完全相同的东西。
当然,界限并非总是如此清晰。不可否认,整个原语集合的构建本身就是一种工程行为。虽然作者认为将符号更多地用于“更像原语”的事物,将单词更多地用于“较不像原语”的事物是一个好主意,但这为不同语言风格留下了空间:有些语言(如 APL)可以更广泛地使用符号,而有些语言(如 Python)可以更广泛地使用单词,以侧重不同的功能或选择不同的原语。
作者承认某些工程行为是可以接受的,前提是目的是为了让底层原语功能更易于访问。例如:
- Range (↕):结合了两个定义域不重叠的原语函数。
- Rank (⎉):将几个数字插入到一个任意排序的列表中。
尽管作者认为这些设计略显遗憾,但为了增加便利性,它们是可接受的。
高质量思维工具的特征
是否存在等待被发现的“原语”?作者认为存在。像加法、映射或将一个列表连接到另一个列表这样的基本概念,任何能够发展出编程技术的社会都会重新发明它们,并且具有完全相同的含义。
为了更务实地筛选原语,作者提出了一些与“高质量思维工具”相关的属性:
- 简单的数学描述(或不止一种)。
- 基于约束的简单规范。
- 可用于实现其他操作。
- 很少或没有有用的变体。
这些属性往往相互关联。例如,如果一个函数的结果可以通过两组独立的约束集来指定,那么定义一个在某些参数上不同的变体通常是没有意义的,因为它不会遵循相同的约束。此外,复杂事物通常由更简单的事物实现,因此能优雅实现其他操作的函数往往本身也更简单。
原语的额外优势
符合上述标准的操作通常还具备其他意想不到的优势:
- 需要关注的边缘情况(Edge cases)更少。
- 在多个领域都相关。
- 实现效率高(理论上和在硬件层面)。
- 原语的组合能产生有用的功能。
- 原语之间的关系可用于数学证明。
作者指出,算术函数 +-×÷ 是任何人都同意符合上述描述的一类函数。它们是不可或缺的工具,有趣的是,在几乎所有语言中,它们都用符号表示(尽管符号可能不同)。
数学结构与“民间定理”
为什么像加法这样广泛有用的函数会存在?这是数学结构性的一个例子。即使你不认为编程是数学,它也可以被数学描述。数学通过事实约束行为来构建自身结构。这种结构可能表现为数学家可以证明的定理、无法证明的定理,或有时被称为“民间定理”(Folk theorems)的模糊思想。
一个很好的例子是 Ulam 螺旋(Ulam's spiral),它展示了素数倾向于遵循算术模式的趋势。虽然关于特定模式有一些证明,关于其他模式有一些猜想,但关于素数在任何简单算术排列中都会显示模式的更广泛的“民间定理”过于模糊,甚至无法证明,但它为领域内的数学家提供了大量的支持证据和实用价值。
“民间定理”类似于编程中的设计模式,它们可以指导或描述实现,但不直接出现在代码中。相反,原语是精确指定的操作,因此既可用于组织代码,也可直接用于代码中。
符号与代数操作
原语遵循数学规则——例如,减法抵消加法。原语序列可以使用这些规则进行代数操作,转换为执行相同计算的不同序列。符号非常适合代数操作,因为阅读它们所需的认知开销较低,这使得更容易识别符号组并移动它们。程序员仍然需要选择进行哪些更改,但原语符号使得正确执行这些更改变得更加容易。
BQN 的三大领域
APL 添加了一些在其他语言中不常见的算术原语,但其主要贡献在于数组原语。包括 BQN 在内的后续 APL 系列语言还添加了组合子(Combinators)——用于处理函数的隐式原语(数学中确实有组合运算 ∘;区别在于 APL 的组合子用于处理一个或两个参数的函数)。
BQN 中三个主要领域(算术、函数、数组)存在的主要原因很简单:这些是 BQN 中用于计算的主要类型。有趣的是,BQN 中没有命名空间原语。在 BQN 中,命名空间用于代码组织,而非计算。这与 C 语言中的数组类似:数组不是计算的对象,因此虽然存在用于处理指针的运算符(加法、引用和解引用),但它们本身不是计算的核心原语。
关键要点
- 原语的定义:原语是语言中最基础、不可再分的操作,具有固定的语法角色,通常用专用符号表示。
- 符号 vs 单词:单词表意更精确、组合更灵活,但符号在表达单一、确定的概念时更简洁、易记,且更利于代数操作和代码压缩。
- 发现而非发明:好的原语应当是被“发现”的客观真理(如加法),而非被“发明”的工程产物。发明总会带有设计者的主观印记,而发现具有普遍性。
- 筛选标准:高质量的原语具备简单的数学描述、基于约束的规范、可组合性以及极少变体。
- 额外优势:符合标准的原语通常边缘情况少、跨领域通用、实现
