Lisp 方言有什么特点?

Zac*_*nta 2 lisp

我在问什么

哪些特征将 Lisp 方言定义为方言(而不是完全其他语言)?

我不问什么

哪种方言最好?--或者- 我应该学习哪种方言?——或者—— Lisp 与另一种语言。

小智 5

我应该拒绝添加答案,但我不能。正如我在评论中所说:语言是标准组织的方言——没有真正有用的技术定义。话虽如此,这里有一些我认为 Lisp 应该是 Lisp 的属性。注意我不是说“Lisp 的方言”。

要求强

  • 它一定是一种表达语言。 这意味着只有一类事物:具有值的表达式,而不是具有值的表达式和没有值的语句。表达式的值可以是未定义的(显然在某些 Lisp 中它们可以有多个值)。这就排除了 C、Python 等。除了显然是一个理想的特性之外,宏系统对于非表达语言来说也是一个痛苦。
  • 它应该有一个无缝的宏系统,其中可以使用完整的语言。我所说的“无缝”是指宏应该是自然而然的编写方式,而不是一些可能涉及单独预处理步骤的神秘附加组件,并且宏应该与语言中的任何其他构造具有相同的状态。通过“完整语言”,我的意思是应该可以使用整个语言(包括宏)来计算宏的扩展,并且这应该与正在扩展宏的语言相同可能还有一个更严格的限制层。
  • 它应该具有最小承诺的语法。 这意味着语言的语法,包括从宏所作用的表面语法派生的解析树,不应该对事物的含义过于具体。因此,举例来说,内置构造(例如if&其他条件的语法)和函数调用之间不应该有很大的语法区别。原因是宏:在存在宏系统的情况下,语法的含义是由程序员定义的,而在语言的语法中拥有特殊特权的东西是它的敌人。这也意味着宏所处理的解析树应该相当通用,并且不附加任何语义。
  • 应该是无限制的lambda 它不必被调用lambda(可以是fn或其他任何东西),但不应该以任何方式限制它。特别是应该清楚的是,函数定义可以根据lambda一些宏观学来完成。
  • 它应该有缺点和由它们组成的列表。(a . b)cons 也是 如此,(a b c)与 相同(a . (b . (c . ())))。注释和列表不必这样写,但符号应该很简单,如果它们是简单的,可能会有所帮助。特别是列表应该是单链接的,而不是经过修饰的数组。
  • 它应该有符号。 不是你可以假装是符号的字符串,而是符号。

要求较弱

我认为这些并不是绝对必要的,但有帮助。

  • 来源的括号前缀表示法。 如果是这种情况,那么 cons 和列表也应该以相同的表示法编写。
  • 类型声明应该是可选的。 应该可以编写没有类型声明的代码。
  • 动态、强打字。 这并不是真正的要求:我可以想象一个 Lisp 到处都进行了英勇的类型推断,并且实际上本质上是静态类型的。但是,如果您可以省略类型声明(上一点),那么除了性能和英雄主义之外,这实际上与动态类型没有什么区别。

品味的问题

这些根本不是要求,但我不会认真使用没有这些功能的 Lisp。

  • 对话式和增量式开发风格。 这并不意味着“花哨的 IDE”,它意味着您与语言实现进行对话,在其中定义和重新定义事物,向它询问有关事物的问题并为您执行任务,然后它与您对话。这也意味着实现应该是健壮的:与每次犯一个差一错误就会死掉的实现进行对话,就像与每次你说一些有争议的事情时头发就会着火的人交谈一样。
  • 仔细算术。 这意味着应该可以在相当容易的情况下获得数字正确的答案(因此应该有适当的整数和有理数,但是当您需要诉诸实数时机器浮点数是可以的)以及方便的答案。如果只提供一个选项,那么它应该是正确的。

笔记

有一些评论认为应该放弃其中一些要求。

古代口齿不清

大约在 1963 年之前,Lisp 没有宏:关于 Lisp 的原始论文没有提及它们,例如:我想,第一次讨论它们的论文就在这里。因此,在 1963 年之前,根据我的定义,称为 LISP 的语言并不是 Lisp。根据这里给出的定义,允许这种语言成为 Lisp 的唯一方法就是说,今天要成为 Lisp,你不需要宏系统,我认为这显然是荒谬的。

因此,您要么需要接受 Lisp 的定义随着时间的推移而发生变化,1963 年之前的 Lisp 可能是“旧 Lisp”,要么您可以说它是Lisp 的一部分。两者都可以:删除宏则不然。

清单应该由缺点组成

人们很容易会说——尤其是如果你想让 Python 成为 Lisp 的话——整个缺点都是转移注意力的:带有可扩展数组的列表的语言可以是 Lisp。这也是错误的,因为用列表为可扩展数组的语言编写的程序与用列表为cons链的语言编写的程序完全不同。

考虑这个任务:给定一个列表(可能是一个长列表),从中随机选择 n 个元素。这是一个简单的 CL 函数来执行此操作:

(defun pick-n-random-elements (n l)
  (loop repeat n
        collect (nth (random (length l)) l)))
Run Code Online (Sandbox Code Playgroud)

这个函数在Lisp 中很糟糕:它在每次迭代时都会调用lengthand nth。您可以将这种函数作为示例提供给学生,看看他们是否理解列表在 Lisp 中的工作原理。

但在列表是可扩展数组的语言中,这个函数完全没问题:等效的 Python 函数

from random import randint

def pick_n_random_elements(n, l):
    r = []
    for i in range(n):
        r.append(l[randint(0, len(l) - 1)])
    return r
Run Code Online (Sandbox Code Playgroud)

虽然不是很惯用的 Python,但它并没有潜伏着同样的性能恐怖。(我认为惯用的Python可能是

from random import choice

def pick_n_random_elements(n, l):
    return [choice(l) for i in range(n)]
Run Code Online (Sandbox Code Playgroud)

虽然我不太确定。)

要在 Lisp 中执行此操作,您可以编写如下内容:

(defun pick-n-random-elements (n l)
  (labels ((pick (n tail elts accum)
             (cond ((null elts)
                    accum)
                   ((= (first elts) n)
                    (pick n tail (rest elts) (cons (first tail) accum)))
                   (t
                    (pick (1+ n) (rest tail) elts accum)))))
    (pick 0 l (sort (loop with len = (length l)
                          repeat n
                          collect (random len))
                    #'<)
          '())))
Run Code Online (Sandbox Code Playgroud)

请注意,这将返回与前一个函数为给定随机种子返回的序列相反的序列:假设您实际上想要随机元素,这并不重要。还要注意,它依赖于尾部调用消除的效率,虽然非常自然的 Lisp 风格,但你不能完全在 CL 中假设——这是一个我认为既符合 Lisp 习惯又不依赖于此的版本:

(defun pick-n-random-elements (n l)
  (let* ((len (length l))
         (indices (sort (collecting (repeat n (collect (random len))))
                        #'<))
         (tail l)
         (i 0))
    (collecting
      (while (not (null indices))
        (while (and (not (null indices))
                    (= (first indices) i))
          (collect (first tail))
          (setf indices (rest indices)))
        (setf tail (rest tail)
              i (1+ i))))))
Run Code Online (Sandbox Code Playgroud)

当然,这使用了三个不同的非标准宏:repeatwhilecollecting/ collect(前两个是微不足道的,最后一个是我想每个人都在某个时候写过的东西),从而很好地表明没有宏系统的语言也不是 Lisp。

这些例子应该让您相信,虽然具有可扩展数组列表的语言表面上看起来像 Lisp,但您用它编写的程序与您使用具有由 cons 组成的列表的语言编写的程序有很大不同:如果你尝试将这些程序转换为 Lisp,它们通常会出现灾难性的性能问题。反之亦然:用 Lisp 编写程序很容易,而用可扩展数组语言编写程序会导致重复复制大型数组,从而带来严重的性能问题。

专注于重要的事情

我在这里试图做的是集中精力于重要的事情:很容易说表面上“看起来”像 Lisp 的东西就是 Lisp,或者说与 Lisp 有某种家族相似性的语言就是 Lisp:Norvig for实例有句名言

Python 可以被视为具有“传统”语法的 Lisp 方言(Lisp 人们称之为“infix”或“m-lisp”语法)。

我相信他错了:Python 不是一种表达式语言,没有conses,没有宏系统,有一个残缺的lambda,没有符号类型,并且实际上没有最小承诺的语法。它不是 Lisp。

相反,我尝试关注用 Lisp 编写程序的感觉(以及今天而不是 1960 年用 Lisp 编写程序的感觉),因为我认为这才是重要的。我认为我对 Lisp 的强烈要求对于用 Lisp 编程的感觉至关重要:如果其中任何一个不适用于一种语言,那么用 Lisp 编程与用 Lisp 编程是一种非常不同的体验。


当然,这只是我的观点,尽管我的观点是基于 30 多年的 Lisp 编写(我刚刚意识到这一点:我不确定我是否应该为此感到沮丧)。