Я думаю, что ни одно решение, которое обернет привязку вокруг 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
>