计划或任何计划的方言有一种“自我”运算符,以便匿名lambda可以自己重复发生,而不需要做像Y-组合器或在letrec中命名等。直接提及自己的匿名lambdas
喜欢的东西:
(lambda (n)
(cond
((= n 0) 1)
(else (* n (self (- n 1)))))))
计划或任何计划的方言有一种“自我”运算符,以便匿名lambda可以自己重复发生,而不需要做像Y-组合器或在letrec中命名等。直接提及自己的匿名lambdas
喜欢的东西:
(lambda (n)
(cond
((= n 0) 1)
(else (* n (self (- n 1)))))))
号与“当前拉姆达”的做法麻烦的是,方案有很多隐藏 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
大多数人避免指代操作符,因为它们使得代码的结构不易识别。另外,重构可以相当容易地引入问题。 (考虑当你在另一个alambda
表格中将let/alambda
表格中的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
你的'llambda'与[SRFI 31](http://srfi.schemers.org/srfi-31/srfi-31.html)的'rec'几乎相同,后者是构造的通常名称。 :-) –
@ ChrisJester-Young啊,有趣。我应该更频繁地浏览SRFI列表。 ;)'rec'甚至似乎更普遍;实际上,它看起来像是古代LISP的“标签”操作符的泛化(当我阅读OP的问题时,这实际上是首先想到的)。 –
两件事。首先,特别是在球拍上下文中,有一个[更好的方法]来实现这一点(http://blog.racket-lang.org/2008/02/dirty-looking-hygiene.html)。其次,你提到的指代宏指令的问题(隐式名称的嵌套范围)并不是最大的问题。 –
我发现使用延续到具有匿名lambda表达式自称,然后用球拍宏伪装的语法所以办法匿名lambda似乎有一个“自我”操作符。我不知道这个解决方案是否可以在其他版本的Scheme中使用,因为它依赖于球拍的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会这样做)。
然后,我们用一个宏来掩盖底层窗体。球拍语法参数允许的(自我ARGS ...)改造(KK ARGS ...)
所以我们可以有:
((lambda-with-self (n)
(cond
[(= 0 n) 0]
[(= 1 n) 1]
[else (* n (self (- n 1)))])) 5)
完整的球拍程序要做到这一点:
#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)
这也回答了我之前关于不同类型延续之间差异的问题。 Different kinds of continuations in Racket
这只能用,因为与call-with-current-continuation不同,call-with-composable-continuation不会中止回到继续提示符,而是在被调用的地方调用继续。
嗯,但这与你的原始问题无关... –
回顾一下原来的问题你是对的,但我认为这是一种模拟或添加一个“自我”操作符到lambda的方式,它看起来很酷这样做的方式。 –
听到,听到。请注意,这对于“返回”也是一个问题,因为它出现在大多数语言中(Java,C等)。 –
我确实相信在技术上,你可以在lambda表单中使'self'标识符词法化。如果你有'syntax-case',你甚至可以自己做。无论如何,我同意。明确的名称通常比照应用的更好。 –
@Matthias:的确,“can”和“should”是完全不同的,特别是在这种情况下。 ;-)我在谈论为什么默认提供'self'是一个可怕的想法。 –