人们有时想知道 BQN 中的原语集是如何选择的。数组编程的局外人可能会认为 APL 的“大想法”只是处理最常见的任务,并用符号而不是名称来编写它们——甚至 Dijkstra 也说过类似的话,称 APL 为“一袋技巧”!我不认为这是完全正确的,所以我想解释一下我个人的观点,为什么将一些特殊操作称为“原语”并赋予它们专用符号是有意义的。虽然我认为这与其他阵列设计者的想法有些重叠,但我在这里只代表我自己。这篇文章的大部分内容都是关于为什么不应该将各种函数视为原语,因此从讨论符号的缺点以及为什么我们不希望所有函数都使用它们开始是有意义的。一个广泛的主题是单词——即语言而不是符号——提供了更多的可能性和含义。如果有两个相关的概念,通常可以给它们命名,使关系和区别都清楚(例如,KeepBefore 和 KeepUpTo)。对于符号,这更加困难:只有少数广泛的关系,如镜面反射(这很容易出错!)和并列是可用的。但细微的言语也可能是一种负担。如果真的只有一种可能性,那么精心挑选的符号可能会更好地表明它并且更容易记住。在 BQN 中,句法角色也是一个因素。字母大小写和下划线允许以任何角色书写任何单词,而原语则具有固定角色。如果一个值可以作为函数调用或作为参数传递,如果它被命名,这将更容易。语言设计是发现还是发明的过程?当然,两者都有,但我认为原始设计应该主要是发现。也就是说,如果一个函数感觉是发明的——或者更糟的是,设计的——它不适合 BQN 原语。发现意味着所讨论的事物在某种意义上是描述它的人的外部,因此,如果两个不同的人发现某事,那么这将是完全相同的事物。一个发明的东西总是带有它的发明者的标记,来自本可以做出不同的小决定。它并不总是那么明确,而且有点讽刺的是,语言中所有原语的集合肯定是经过设计的。虽然我认为将符号赋予更原始的事物并将单词赋予不那么原始的事物是一个好主意,但这为在 APL 中相对更多地使用符号或在 Python 中更多地使用单词留出了空间,以集中注意力提供不同的功能,或者当有几个提供类似功能的原语时选择不同的原语。我确实认为某些工程行为是可以的,如果目的只是为了使底层原始功能更易于访问。例如,Range (↕) 将两个具有不相交域的原始函数组合在一起,而 Rank (⎉) 将几个数字粘贴到一个任意排序的列表中。我仍然认为这些有点不幸,但为了增加方便而可以接受。
是否有原语等待被发现?我认为是这样:任何可以开发编程的社会都会重新发明诸如加法、映射或将一个列表连接到另一个列表等基本概念,并且具有完全相同的含义。但这是一种含糊的断言,我无意进入是否“实际”存在任何东西的问题。一种更实用的方法是从我与高质量思维工具相关联的一些属性开始:这些属性往往结合在一起:虽然它们都不一定意味着另一个,但一个函数似乎很少能只适合其中的一半。除了经验观察之外,还有一些争论为什么我们可能会期望这种情况。例如,如果一个函数的结果可以由两个独立约束集中的任何一个指定,那么定义在某些参数上不同的变体可能没有意义,因为它不会遵循那些相同的约束。通常复杂的东西都是用更简单的东西来实现的,所以一个函数可以很好地实现另一个函数,它往往会更简单。同样,某些属性似乎与上述属性有直观的联系,但对于其他属性,这种联系并不那么明显。它们是我在多年的编程和设计中观察到的广泛模式。我可以指出,已经有一类任何人都会同意符合上述描述的函数,它们是算术函数 +-×÷。它们是不可或缺的工具,有趣的是,几乎在每种语言中,它们也用符号(不一定是相同的)表示。为什么应该存在一个像加法这样广泛有用的函数?这是数学的一个例子(即使你不认为编程是数学,它当然可以用数学来描述)比直接推理可以解释的更结构化。发生这种情况是因为数学将结构强加于自身,因为关于一个数学对象的事实可以约束另一个数学对象的行为。该结构可能表现为数学家可以证明的定理,他们不能证明的定理,或者有时称为“民间定理”的模糊想法。一个很好的例子是乌拉姆螺旋,即素数遵循算术模式的趋势。有一些关于特定模式的证明,以及关于其他模式的猜想。质数将在任何简单的算术排列中显示模式的更广泛的民间定理过于模糊甚至无法证明,但有很多支持证据,并且对在该领域工作的数学家具有实际用途。定义 Exact Fuzzy Application Specific General Algorithm Primitive Design Pattern 民间定理很像编程中的设计模式,因为它们可以指导或描述一个实现,但不会直接出现在代码中。相反,原语是精确指定的操作,因此它既可用于组织,也可用于实际代码。原语遵守数学规则——例如减法取消加法。可以使用这些规则以代数方式操作原语序列,将其更改为执行相同计算的不同序列。符号非常适合代数运算,因为读取它们所需的开销较低,因此更容易识别符号组并移动它们。程序员仍然必须选择要进行哪些更改,但是原始符号可以更轻松地正确执行它们。
APL 添加了一些其他语言中不常见的算术原语,但其主要贡献是数组原语。包括 BQN 在内的一些后来的 APL 家族语言也添加了组合子——用于处理函数的默认原语(数学确实有组合 ∘;区别在于 APL 的组合子用于具有一或两个参数的函数)。算术、函数和数组这三个域的主要原因很简单,因为它们是 BQN 中用于计算的主要类型。注意到没有命名空间原语可能会很有趣。在 BQN 中,命名空间用于代码组织,而不是计算。 C 中的数组也发生了类似的事情:它们不是计算对象,因此虽然有用于处理指针(加法、引用和取消引用)的运算符,但没有用于数组的运算符。使 BQN 数组更适合在其上定义基元的一个步骤是它们是不可变的。如今,许多流行的语言都有用于处理数组的标准函数或方法,例如串联、反转和映射。但是它们通常更多地被视为实用程序而不是基元,并且在某些情况下附加了额外的功能,这使得它们更复杂且更不易组合(例如,Javascript 的映射将额外的参数传递给操作数)。 BQN 的原语集更加强调简单和一致的设计,当然每个原语都是用一个符号写的。在这样的系统中,单个原语的好处(例如更少的边缘情况和对多个域的适用性)相互加强。代数操作也成为安全重构、修改或优化代码的强大工具。我认为将“原始性”识别为某些函数的一个特征对设计 BQN 非常有帮助,并且理解单个原语及其关系可以更容易地更快地开发正确和通用的代码。