有关lambda语法的疑问

这个tree calculus和lambda calculus有什么区别?我看了下,个人感觉似用 () 和对事物(不知道该用什么词好)进行编码,而lambda是function?

read the paper

又到了历史学魅力时刻。在CLtL(《Common Lisp the Language》,ANSI之前的Common Lisp社区标准)和CLtL2中,list是可以被视作function类型的。原文摘录:

A lambda-expression (a list whose car is the symbol lambda) may serve as a function. Depending on the implementation, it may be possible for other lists to serve as functions.

这个是从MacLisp等前辈中来的,它们说的就很模糊,显而易见地带来了诸多不便。于是1988年X3J13委员会就有了这么个提案

allowing symbols but not lists to be FUNCALLed or APPLYed

… In particular, a list may not be used to implement any FUNCTION subtype.

然后list和function就这么被区分开了。不过Emacs的出现早于X3J13,加上elisp并不是很需要区分这俩,所以这个更改就没同步到elisp上面来

2 个赞

请教下,这两句话是指:

((lambda (a) a) 2) ;;=> 2

((car '((lambda (a) a))) 2) ;; => error

吗?

还是说,不推荐下述写法:

(funcall (list 'lambda ...) ...)

这句说的模糊是指 may server as a function,and/or Depending on the implementation? 光从它对lambda表达式的定义看,我(很不可靠的)感觉很清晰。所以不知道,你说的模糊指?

ELISP> (funcall (cons 'function '((lambda (x y) (sqrt (* x y))))) 2 2)
*** Eval error ***  Invalid function: #'(lambda (x y) (sqrt (* x y)))
ELISP> (funcall #'(lambda (x y) (sqrt (* x y))) 2 2)
2.0
ELISP> (funcall (list (quote lambda) (list (quote x) (quote y)) (list (quote +) (quote x) (quote y)))
                1 2)
3

a list whose car is the symbol lambda

建议先看懂这句话

ELISP> (car (cons 'function '((lambda (x y) (sqrt (* x y))))))
function

怎么理解?

ELISP> (car `(,(make-symbol "lambda") nil t))
lambda
ELISP> (symbolp (car `(,(make-symbol "lambda") nil t)))
t
ELISP> 
ELISP> (eq (make-symbol "lambda") 'lambda)
nil

打印出来一样不代表相同

我说说,我这么写的原因,麻烦您看下我哪里存在问题。

#'xxx 等价于 (function xxx)
(cons 'function '((lambda (x y) (sqrt (* x y)))) =>
(function (lambda (x y) (sqrt (* x y))) =>
#'(lambda (x y) (sqrt (* x y)))

然后,我对 quote 的理解,和 function 一样,即:

(quote foo) 等于 'foo(function foo) 等于 #'foo。这理解是否存在错误?

ELISP> #'(lambda (x y) (sqrt (* x y)))
(closure
    (t)
    (x y)
  (sqrt
   (* x y)))
ELISP> (cons 'function '((lambda (x y) (sqrt (* x y)))))
#'(lambda
    (x y)
    (sqrt
     (* x y)))

请问哪里一样了?

这样能看懂了吧?

ELISP> #'x
x
ELISP> ''x
'x
ELISP> (cons 'quote '(x))
'x

懂了。cons那句返回了个list,打印成#'…;function那句返回了个closure对象。

也就是说,某种程度上(不用make-symbol),拼接 symbol 是可以的,需将拼接出的 list 再 eval 一次。

而上文的理解错在

(cons 'function '((lambda (x y) (sqrt (* x y)))) =X=> ;; 
(function (lambda (x y) (sqrt (* x y))) 

实际是

(cons 'function '((lambda (x y) (sqrt (* x y)))) ===>
'(function (lambda (x y) (sqrt (* x y))) 

也就是說,你重新发现了 macro

绝大多数情况下 (funcall (list 'lambda ...) ...) 是非法的——实际上并不是“写法”的问题,而是你举的这两个例子里面的表达式就是两种完全不同的对象:你把lambda表达式quote起来,或者用list表达式拼装起来,它就是未经过求值的列表;而没有被quote的lambda表达式会被求值,就像普通的表达式一样。而lambda表达式是怎么求值的呢?在CL(包括Elisp)里,lambda 其实是一个宏

Provides a shorthand notation for a function special form involving a lambda expression such that:

(lambda lambda-list [[declaration* | documentation ]] form*) 
== (function (lambda lambda-list [[declaration* | documentation ]] form*)) 
== #'(lambda lambda-list [[declaration* | documentation ]] form*)

所以其实经过求值后的(lambda () ...)#'(lambda () ...)是一样的,我一直是省着写2333

回到(funcall (list 'lambda ...) ...),鉴于目前绝大多数Common Lisp实现的*features*里都没有:cltl1:cltl2,这个行为理论上到哪都是非法的。实际中,这个表达式在SBCL和LispWorks里会报出“你call的不是一个函数”的错。但在ECL里它是可以正常执行的。这就属于implementation-dependent了。

这个已经很模糊了x。首先“Depending on the implementation”就说明它是一个未经规范的行为,作为一种基本数据类型、“first-class citizen”,function的语法都未经规范,这个想必相当糟糕(x

其次,X3J13的issue里也提到了,由于这个模糊的定义,typep这个基本的类型检查函数就无法准确地检查function,连带着typecase等语句的结果也不可靠。因为一个list它是不是function完全由funcallapply这俩函数决定,那我们总不能用“运行一下看看”来做类型检查吧,这个空间可太模糊了orz

嗯,感谢解答,有些细节我还得再想想。

((lambda (a) a) t) ; => t。 (*)

(*)让我有种一些错觉:

1)这个表达式“看”上去如同一个list,其car也是一个list。在这个层面上,它的执行结果符合我对lambda calculus的“预期”。也如同其他(foo 'bar)一样(此处foo是一个function or whatever)。

现在起,现实里开始有了function的概念(此function非数学中的function,而是某种lisp object,且此object(我目前理解)是从面向对象里边借用的概念),最初的lambda calculus开始因为实现问题分化出lisp-1、lisp-2版本。

funcall是新的认知代价。

2)我开始误认为:好吧,只要list的car的东西是function,那它就可以求值、执行、eval、funcall。于是,我开始疑惑什么是function?

对我而言,(*)至少从表现上是一种function,或者说(lambda …)是一种function。此刻的我还没有“宏”的概念,不期望去关心lambda究竟如何实现。因为关心lambda是什么似乎很怪,就像在问lambda calculus里的λ究竟是什么?

而这个lambda是“宏”的实现细节似又一次fool了我:

因为它是宏,也许恰巧eval得保持自身。所以某种程度上,它的print和read输出一致。然而,如今lambda eval后开始成为closure。说实话,我不是很想深究实现细节。因为只要这样做,很快我就会发现,lambda至少有三种:‘(lambda …),(closure …),#’(lambda …)。这对于最初只认λ演算的我来说有点懵。进而导致(#'foo 1)这种显然是“语法”错误的问题。

尽管我很难评价我究竟该听谁的“语法”。

从λ演算纯粹的list与symbol,到function等各种各样的type,又到object,print与read之分。

我开始回想:我试写lisp的初衷是因它给了我一种(也许又是错觉的)感觉——语法可以自定义、可以及其灵活地扩展。可当我拿起它的时候,好像语法规则的担子变得更加沉重且变幻莫测了。

甚至CLOS?又是一堆types, objects.

I just want to write my own type with lambda list and symbol!(抱歉,有点糟心。)

此刻,看到Y combinator,我再次认知到,要将公式译成各家lisp,还得过各家的“语法”关。

我都有λ calculus了,为什么不自己定义语法呢?

再次抱歉,绝无恶意。

再次抱歉。

只是提供一个外行人的使用体验、感觉:原本想摆脱“语法”却成了更重的负担。

1 个赞

lisp对表达式递归地求值,直到遇到quote,或者字面量。相当于给定一棵树,lisp只会按固定顺序(比如从左到右)做dfs。

按照lisp的调用约定,对一个list求值,它的第一个元素,即rfold形状的二叉树的最左子树,是一个lisp原语,或者可以进行lambda绑定的表达式。可以进行lambda绑定,并不代表它必须通过lambda这个符号去定义。

如果你想改变lisp的求值顺序,就需要引入宏,因为宏被设计为不对它的参数求值的函数。这样就可以通过扭一扭这个树改变求值顺序。这是lambda calculus之外的东西,宏需要lisp解释器实现额外的支持。

所以之前提到tree calculus那个paper,因为它很直观,而且不需要在解析器层面区分宏和函数,它的组合子逻辑包含了向下和歪扭的操作。我学尚浅,讲的不如原paper。

过谦了。

关于tree calculus,这两个comment让我对它有初步的认知(贴这留人)。

下述博文用图像更直观地解释了tree calculus的apply rules,且展示一种应用:基于类似λ的东西(符号Δ),定义出上层boolean、number、combinatory logic:

然而,我不明白它能做的相比于下述用lisp做的有何种实质上的区别:

怎么理解,进一步说:它可以通过额外引入的组合子解决求值顺序问题吗?

理解。只要试过写lisp解释器,总会碰上这件事情。(这些事情有意思的地方在于为什么设计时要这样抉择。)顺便贴个同样涉及求值顺序的博文,可以对比里头用scheme实现的Y combinator与用elisp实现的,与公式上的,看看究竟额外引入了哪些“概念”、“实体”。 https://czlwang.com/zettel/20201007005002-y_combinator.html

嗯,理解了。是我把 λ-calculus 里 apply 时可能会用到的括号 和 elisp 里 list 的括号混淆了。

我不负责任地拍脑袋想:因为 ((lambda (a) a) 2) 等于 (funcall (lambda (a) a) 2),而最初的 lambda-expression 只是一个 lambda 符号位于 car 的 list 。为了规避求值错误,出现了模糊的写法 (funcall '(lambda (a) a) 2);后来,lambda改宏,于是,我们又可以“直观”地用 (funcall (lambda (a) a) 2) 了;再后来,(我不知道)为什么改推荐全改 “#'(lambda ...)” 呢?我不知道是在 elisp manual 里还是哪里看过这样的推荐描述。

不存在这种推荐,少瞎想

建议先读 Autonomy of Lisp,了解一下啥叫编程语言的语义,不要再发拍脑袋的意见了。

编程语言表达式求值的结果是确定的,不会因为你瞎几把乱想改变

我只能说你想象力是够丰富了,但不去认真求证就乱下结论,过于有毒了。所谓思而不学。

:joy: 笑。说实话,我也不知道忘了哪里看来的,但如果后续碰到,我会回来贴这帖子里。总之,这各家的概念和 implement details 让我深感懵逼。当然,这全怪我懒得读标准。