Дай по лямбде в схему?
В Common Lisp, если я хочу, чтобы две функции имели общее состояние, я бы выполнил лямбду let следующим образом:
(let ((state 1))
(defun inc-state ()
(incf state))
(defun print-state ()
(format t "~a~%" state))
Эти функции не являются локальными для let
- это глобальные функции, которые поддерживают ссылку на переменную общего состояния, которая сама по себе не видна извне. Например, я мог бы сделать следующее в моем коде:
(print-state) => 1
(inc-state) => 2
(print-state) => 2
Однако в Scheme такая конструкция объявляет локальные функции, которые не видны извне:
(let ((state 1))
(define (print-state)
(print state))
(print-state)) => 1
(print-state) => error, no such variable print-state
Единственный способ, с помощью которого я могу достичь такой функциональности (кроме использования неэкспортированных глобалов внутри модуля), будет выглядеть примерно так:
(define print-state #f)
(define inc-state #f)
(let ((state 1))
(set! print-state (lambda () (print state)))
(set! inc-state (lambda () (inc! state))))
Есть ли способ в Схеме написать форму переброса лямбды, не прибегая к таким уродливым обходным путям? Или мне нужно написать макрос, чтобы обернуть это уродство? (Кстати, я знаю о letrec
и это не решение этой проблемы.)
Кстати, я использую Цыпленок, но мой вопрос должен относиться ко всем Схемам.
2 ответа
К сожалению, связывание верхнего уровня может быть сделано только верхним уровнем и define
внутри процедуры на самом деле просто синтаксический сахар для letrec
, В Chicken Scheme у вас есть форма, которая называется define-values
где вы можете сделать это:
(define-values (print-state inc-state)
(let ((state 1))
(values (lambda () (print state))
(lambda () (inc! state)))))
Обратите внимание, что define-values
не является частью какого-либо стандарта, хотя кажется, что это обычная форма. Было бы легко создать макрос, который использует подход, который вы использовали для его реализации. В качестве альтернативного решения вы можете вернуть диспетчера, которому вы звоните, чтобы получить доступ к процедурам:
(define dispatcher
(let ((state 1))
(lambda (msg)
(case msg
((print) (lambda () (print state)))
((inc!) (lambda () (inc! state)))))))
(define print-state (dispatcher 'print))
(define inc-state (dispatcher 'inc!))
На самом деле вам не нужно делать глобальные переменные, так как вы можете просто вызвать возврат напрямую:
((dispatcher 'inc!))
((dispatcher 'inc!))
((dispatcher 'print)) ; ==> prints 3
Вы могли бы сделать что-то вроде этого:
(define-values (inc-state print-state)
(let ((state 1))
(values
(lambda () ; inc-state
(set! state (+ 1 state)))
(lambda () ; print-state
(display state)
(newline)))))
> (print-state)
1
> (inc-state)
> (print-state)
2
> state
. . state: undefined; cannot reference an identifier before its definition
>
(вывод от Racket, но я также проверил его в Chicken Scheme)