Дай по лямбде в схему?

В 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)

Другие вопросы по тегам