Каков стандартный способ определения глобальных замыканий в схеме? - PullRequest
3 голосов
/ 26 апреля 2019

Итак, я хочу знать, есть ли стандартный способ иметь такой код:

(let ((x 10))
  (define (add10 a)
     (+ x a)))

Я знаю о:

(define add10 (let ((x 10))
                 (lambda (a) (+ x a))))

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

(let ((x 10))
  (macro x))

и, например, макрос создаст список функций:

(let ((x 10))
  (define (add1)
     (+ x 1))
  (define (add2)
     (+ x 2))
  (define (add3)
     (+ x 3)))

Существует ли стандартный способ определения функций add1..add3? В схеме, которую я тестировал, функции будут локальными внутри let и недоступными снаружи.

Если вы показываете код макроса, меня интересуют только макросы lisp с define-macro и quasiquote, пожалуйста, не define-syntax, потому что это в основном для использования в моем собственном lisp (на основе схемы), где у меня есть только макросы lisp.

Если схема не дает поддержки для чего-то подобного, разрешает ли такой диалект, как Common Lisp, что-то подобное?

Ответы [ 3 ]

3 голосов
/ 26 апреля 2019

Я думаю, что ни одно решение, которое обернет привязку вокруг define, не может работать вообще переносимо или безопасно, поскольку обернутые define будут либо создавать локальные привязки (ведущие формы в теле), либо быть недопустимыми (не ведущие). формы в теле), хотя я был бы рад, если бы специалист по стандартам Схемы указал, где я ошибаюсь.

Вместо этого, как мне кажется, должно сработать что-то вроде этого мерзкого взлома.

(begin
  (define inc undefined)
  (define dec undefined)
  (let ((x 3))
    (set! inc (lambda (y)
                (set! x (+ x y))
                x))
    (set! dec (lambda (y)
                (set! x (- x y))
                x))))

Здесь я опираюсь на константу, называемую undefined, что означает «еще не определено правильно»: Ракет предоставляет это в racket/undefined, но в целом это может быть что угодно: вы могли бы просто где-нибудь сказать

(define undefined 'undefined)

например.

Хитрость заключается в том, чтобы определить вещи, которые вы хотите на верхнем уровне, с помощью значений заполнителей, а затем назначить их внутри let.

Я уверен, что можно определить макрос, который расширяется до чего-то вроде (вот почему у меня все это внутри begin): я не сделал этого, потому что это неудобно, и я использую Racket, чтобы я мог ' в него легко писать макросы Lisp старого стиля.


Обратите внимание, что очевидный подход в современной схеме заключается в использовании define-values:

(define-values (x y) (let (...) (values ...)))

Делает что хочешь. Как уже упоминалось в другом ответе, вы можете реализовать define-values как макрос, если у вас есть только несколько значений. Но если у вас совсем нет нескольких значений, вы можете использовать что-то, что определяет вещи на основе списка результатов:

(define-list (x y) (let (...) (list ...)))

Вот два грубых варианта этого макроса: первый использует собственные макросы Racket:

(require racket/undefined)

(define-syntax define-list
  (syntax-rules ()
    [(define-list () expr)
     (let ((results expr))
       (unless (zero? (length results))
         (error "need an empty list"))
       (void))]
    [(define-list (name ...) expr)
     (begin
       (define name undefined)
       ...
       (let ([results expr])
         (unless (= (length results)
                    (length '(name ...)))
           (error "wrong number of values"))
         (set! name (begin0
                      (car results)
                      (set! results (cdr results))))
         ...))]))

в то время как второй использует негигенные макросы в Racket:

(require compatibility/defmacro
         racket/undefined)

(define-macro (define-list names expr)
  `(begin
     ,@(let loop ([ntail names] [defs '()])
         (if (null? ntail)
             (reverse defs)
             (loop (cdr ntail)
                   (cons `(define ,(car ntail) undefined) defs))))
     (let ([results ,expr])
       (unless (= (length results)
                  (length ',names))
         (error "wrong number of values"))
       ,@(let loop ([ntail names] [i 0] [assignments '()])
           (if (null? ntail)
               (reverse assignments)
               (loop (cdr ntail) (+ i 1)
                     (cons `(set! ,(car ntail) (list-ref results ,i))
                           assignments)))))))

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

Но с этими:

> (define-list (inc dec)
    (let ([i 0])
      (list
       (lambda ()
         (set! i (+ i 1))
         i)
       (lambda ()
         (set! i (- i 1))
         i))))
> inc
#<procedure>
> (inc)
1
> (dec)
0
> (dec)
-1
> 
3 голосов
/ 27 апреля 2019

В R7RS, последнем отчете по Схеме, мы имеем define-values.Его можно использовать следующим образом:

(define-values (add1 add2 add3)
  (let ((x 10))
    (values (lambda () (+ x 1))
            (lambda () (+ x 2))
            (lambda () (+ x 3)))))

Конечно, для больших блоков можно задать локальное определение и вместо этого ссылаться на него в values.

В отчете R7RS вы найдете синтаксическое правило для define-values, которое будет работать для R6RS и R5RS.Он использует call-with-values, где значения передаются в list, а затем define из этого.Могу поспорить, что это также работает внутри lambdas sucn, что реализация Scheme на самом деле может преобразовать это в letrec, поэтому, хотя это и не очень элегантно, оно выполняет грязную работу.

3 голосов
/ 26 апреля 2019
(let ((x 10))
  (somemacro x))

->

(let ((x 10))
  (define (add1)
     (+ x 1))
  (define (add2)
     (+ x 2))
  (define (add3)
     (+ x 3)))

В Common Lisp:

CL-USER 43 > (defmacro somemacro (var)
               `(progn
                  (defun add1 () (+ ,var 1))
                  (defun add2 () (+ ,var 2))
                  (defun add3 () (+ ,var 3))))
SOMEMACRO

CL-USER 44 > (let ((x 10))
               (somemacro x))
ADD3

CL-USER 45 > (add1)
11

CL-USER 46 > (add3)
13

Это иногда можно увидеть.Обычно это немного плохой стиль в Common Lisp, потому что файловый компилятор не будет распознавать, что существуют глобальные объявления функций, потому что внутри LET a DEFUN не на верхний уровень .Если функция определена в файле на top-level , то во время компиляции файловый компилятор увидит, что это функция, и может делать особые вещи, такие как отметка подписи в среде времени компиляции, встраиваниеЭто.и т.д.

Обратите внимание, что когда DEFINE в Схеме определяет локальную функцию, одна из них может быть в состоянии выполнять (в зависимости от того, что реализация делает дополнительно к стандарту):

(let ((x 10))
  ()
  (define (add1) (+ x 1)))

Обратите внимание, что в Common Lisp defun определяет глобальные функции, а flet / labels определяет локальные функции.

...