Scheme 或任何scheme 方言是否有一种“self”运算符,以便匿名 lambda 可以自行重复,而无需执行 Y 组合器或在 letrec 等中命名等操作。
类似:
(lambda (n)
(cond
((= n 0) 1)
(else (* n (self (- n 1)))))))
不。 “当前 lambda”方法的问题在于,Scheme 有许多隐藏的 lambda。例如:
let
形式(包括 let*
、letrec
和命名为 let
)do
(扩展为名为 let
)delay
、lazy
、receive
等要求程序员知道最里面的 lambda 是什么会破坏封装,因为您必须知道所有隐藏的 lambda 都在哪里,并且宏编写者不能再使用 lambda 来创建新作用域。
如果你问我的话,那就是全面失败。
有编写“照应”宏的传统,这些宏在其主体的词汇范围内定义特殊名称。使用
syntax-case
,您可以在 letrec
和 lambda
之上编写这样的宏。请注意,考虑到规范,下面的定义尽可能卫生(特别是,alambda
的隐形使用不会遮盖self
)。
;; Define a version of lambda that binds the
;; anaphoric variable “self” to the function
;; being defined.
;;
;; Note the use of datum->syntax to specify the
;; scope of the anaphoric identifier.
(define-syntax alambda
(lambda (stx)
(syntax-case stx ()
[(alambda lambda-list . body)
(with-syntax ([name (datum->syntax #'alambda 'self)])
#'(letrec ([name (lambda lambda-list . body)])
name))])))
;; We can define let in terms of alambda as usual.
(define-syntax let/alambda
(syntax-rules ()
[(_ ((var val) ...) . body)
((alambda (var ...) . body) val ...)]))
;; The let/alambda macro does not shadow the outer
;; alambda's anaphoric variable, which is lexical
;; with regard to the alambda form.
((alambda (n)
(if (zero? n)
1
(let/alambda ([n-1 (- n 1)])
(* (self n-1) n))))
10)
;=> 3628800
大多数人避免使用照应运算符,因为它们使代码的结构难以识别。此外,重构很容易引入问题。 (考虑一下当您将
let/alambda
形式包装在上面的阶乘函数中的另一个 alambda
形式时会发生什么。很容易忽视 self
的使用,特别是如果您没有通过键入来提醒它是相关的明确地。)因此,通常最好使用明确的名称。可以使用简单的 lambda
宏定义允许此操作的 syntax-rules
的“标记”版本:
;; Define a version of lambda that allows the
;; user to specifiy a name for the function
;; being defined.
(define-syntax llambda
(syntax-rules ()
[(_ name lambda-list . body)
(letrec ([name (lambda lambda-list . body)])
name)]))
;; The factorial function can be expressed
;; using llambda.
((llambda fac (n)
(if (zero? n)
1
(* (fac (- n 1)) n)))
10)
;=> 3628800
我找到了一种方法,使用延续让匿名 lambda 调用自身,然后使用 Racket 宏来伪装语法,以便匿名 lambda 看起来有一个“self”运算符。我不知道这个解决方案在其他版本的Scheme中是否可行,因为它依赖于racket的Call-with-composable-continuation函数以及隐藏语法的宏使用语法参数。
基本思想是这样的,用阶乘函数来说明。
( (lambda (n)
(call-with-values
(lambda () (call-with-composable-continuation
(lambda (k) (values k n))))
(lambda (k n)
(cond
[(= 0 n) 1]
[else (* n (k k (- n 1)))])))) 5)
延续 k 是对匿名阶乘函数的调用,该函数有两个参数,第一个是延续本身。这样,当我们在主体中执行 (k k N) 时,这相当于匿名函数调用自身(与递归命名 lambda 的方式相同)。
然后我们用宏来伪装底层形式。 Rackets 语法参数允许将 (self ARGS ...) 转换为 (k k ARGS ... )
这样我们就可以:
((lambda-with-self (n)
(cond
[(= 0 n) 0]
[(= 1 n) 1]
[else (* n (self (- n 1)))])) 5)
执行此操作的完整 Racket 程序是:
#lang racket
(require racket/stxparam) ;required for syntax-parameters
( define-syntax-parameter self (λ (stx) (raise-syntax-error #f "not in `lambda-with-self'" stx)))
(define-syntax-rule
(lambda-with-self (ARG ... ) BODY ...)
(lambda (ARG ...)
(call-with-values
(lambda ()(call/comp (lambda (k) (values k ARG ...))))
(lambda (k ARG ...)
(syntax-parameterize ([self (syntax-rules ( )[(self ARG ...) (k k ARG ...)])])
BODY ...)))))
;Example using factorial function
((lambda-with-self (n)
(cond
[(= 0 n) 0]
[(= 1 n) 1]
[else (* n (self (- n 1)))])) 5)
这也回答了我之前关于不同类型的延续之间差异的问题。 Racket 中不同种类的延续
这之所以有效,是因为与 call-with-current-continuation 不同,call-with-composable-continuation 不会中止回到继续提示,而是在调用的地方调用继续。
Racket 支持 SRFI-31,它提供了一个名为
rec
的宏,用于制作可以调用自身的 lambda。基本示例:
#lang racket/base
(require srfi/31)
((rec (self n)
(cond
((= n 0) 1)
(else (* n (self (- n 1))))))
5) ; => 120