匿名 lambda 直接引用自身

问题描述 投票:0回答:4

Scheme 或任何scheme 方言是否有一种“self”运算符,以便匿名 lambda 可以自行重复,而无需执行 Y 组合器或在 letrec 等中命名等操作。

类似:

(lambda (n)
   (cond
     ((= n 0) 1)
     (else (* n (self (- n 1)))))))
scheme racket
4个回答
10
投票

不。 “当前 lambda”方法的问题在于,Scheme 有许多隐藏的 lambda。例如:

  • 所有
    let
    形式(包括
    let*
    letrec
    和命名为
    let
  • do
    (扩展为名为
    let
  • delay
    lazy
    receive

要求程序员知道最里面的 lambda 是什么会破坏封装,因为您必须知道所有隐藏的 lambda 都在哪里,并且宏编写者不能再使用 lambda 来创建新作用域。

如果你问我的话,那就是全面失败。


6
投票

有编写“照应”宏的传统,这些宏在其主体的词汇范围内定义特殊名称。使用

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

0
投票

我找到了一种方法,使用延续让匿名 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 不会中止回到继续提示,而是在调用的地方调用继续。


0
投票

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
© www.soinside.com 2019 - 2024. All rights reserved.