Анонимные лямбды, непосредственно ссылающиеся на себя - PullRequest
5 голосов
/ 29 октября 2011

Имеет ли Scheme или любой другой диалект схемы схему своего рода оператор "я", так что анонимные лямбды могут повторяться сами по себе, не выполняя что-то вроде Y-комбинатора, не будучи названным в letrec и т. Д.

Что-то вроде:

(lambda (n)
   (cond
     ((= n 0) 1)
     (else (* n (self (- n 1)))))))

Ответы [ 3 ]

9 голосов
/ 29 октября 2011

Нет. Проблема с подходом «текущей лямбды» заключается в том, что в Scheme есть много скрытых лямбд. Например:

  • Все let формы (включая let*, letrec и с именем let)
  • do (который расширяется до именованного let)
  • delay, lazy, receive и т. Д.

Требование от программиста знать, что внутренняя лямбда нарушит инкапсуляцию, поскольку вам нужно будет знать, где находятся все скрытые лямбды, и разработчики макросов больше не могут использовать лямбды как способ создания новой области видимости.

Всесторонний проигрыш, если вы спросите меня.

6 голосов
/ 29 октября 2011

Существует традиция написания «анафорических» макросов, которые определяют специальные имена в лексической области их тела.Используя 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 голосов
/ 11 сентября 2012

Я нашел способ использовать продолжения, чтобы анонимные лямбды вызывали самих себя, а затем использовать макросы Racket для маскировки синтаксиса, чтобы у анонимной лямбды был оператор «я».Я не знаю, возможно ли это решение в других версиях Scheme, поскольку оно зависит от функции Call-with-composable-продолжения ракетки и макроса, чтобы скрыть синтаксис, использует параметры синтаксиса.

Основная идеяэто, проиллюстрировано факториальной функцией.

( (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 - это вызов анонимной факториальной функции, которая принимает два аргумента, первый из которых - само продолжение.Так что, когда в теле мы выполняем (kk N), что эквивалентно анонимной функции, вызывающей себя (так же, как это делает рекурсивный с именем lambda).

Затем мы замаскируем базовую форму с помощью макроса,Синтаксические параметры ракеток позволяют преобразовывать (self ARGS ...) в (kk ARGS ...)

, поэтому мы можем иметь:

 ((lambda-with-self (n)
    (cond 
      [(= 0 n) 0]
      [(= 1 n) 1]
      [else (* n (self (- n 1)))])) 5)

Полная программа Racket для этогоis:

#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-продолжением, call-with-composable-продолжением не прерывается обратно к приглашению продолжения, но вызываетпродолжение в том месте, где оно было вызвано.

...