如何在定义的函数中绑定变量?

How to bind a variable in a defined function?

上下文: 我正在尝试做这样的事情(方案“伪代码”):

(with-midi-channel 0
    (begin 
        (note-on 60 127)
        (plenty-of-other-midi-commands))

像这样定义音符:

(define (note-on note velocity)
    (send-midi-message channel note velocity))

在 haskell 中,我会使用 reader monad。 我以为“with-midi-channel”可以只是一个简单的“(let ((channel 0)) ...)”,但看起来并没有那么简单

这是一些等效的代码,尽我所能解释:

(define (test)
    (display a))

(let ((a 1))
   (test))    ;; I get "unbound variable a in (display a)

((let ((a 1))
  test))      ;; same error

((let ((a 1))
   (lambda () (display a)))) ;; it works, but i would like to be able to do the same 
                             ;; with an externally defined function

我的问题也是了解其背后工作原理的借口!

顺便说一句,Racket 甚至不接受这个定义,而其他方案实现则接受:

(define (test) (display a)) ;; a: unbound identifier in: a

我希望我的问题格式正确,因为这是我的第一个 post ^^

Scheme(和最近的 Lisps)是词法范围的,所以在类似

(define (ts)
  (display a))

引用的 a 的绑定是在定义函数时在词法上显而易见的绑定(实际上它比 top-level 定义的绑定要复杂一些,因为您想要

(define (a ...)
  ... (b ...)
  ...)

(define (b ...)
  ...
  (a ...)
  ...)

上班)。

如果没有这样的绑定,这是一个错误(或者,至少,未定义的行为)。

我认为您想要的是动态作用域变量。在球拍(您似乎正在使用)中,您可以通过 parameters 获得这些信息,这样的事情可能对您有用:

(define current-midi-channel (make-parameter 0))

(define (call/current-midi-channel f c)
  (parameterize ([current-midi-channel c])
    (f)))

(define-syntax-rule (with-current-midi-channel (c) form ...)
  (call/current-midi-channel (thunk form ...) c))

(define (note-on note velocity (channel (current-midi-channel)))
  ;; implementation here but let's just return the channel
  channel)

然后

> (with-current-midi-channel (12)
    (note-on 1 2))
12

看起来 R7RS 与 Racket 有类似的参数概念(参见第 4.2.6 节),但我不确定它何时出现在 Scheme 标准中。


我认为 R7RS 是第一个具有这种动态绑定支持的 Scheme 标准。如果您正在使用符合旧标准的实现,或者根本不符合任何标准的实现,那么该实现需要自己支持这样的东西,或者,如果您愿意处理可怕的事情后果(thread-safety 变得基本上不可能)你可以这样做:

(define current-midi-channel 0)

(define (call/horribly-shallow-binding-current-midi-channel f c)
  (let ([stashed-midi-channel current-midi-channel])
    (dynamic-wind
     (λ () (set! current-midi-channel c))
     f
     (λ () (set! current-midi-channel stashed-midi-channel)))))

现在

> current-midi-channel
0
> (call/horribly-shallow-binding-current-midi-channel
   (λ () current-midi-channel)
   16)
16

如果您的实现有 syntax-rules,那么您可以将其抽象为任意变量的宏:

(define-syntax with-horribly-shallow-bound-global-variable
  (syntax-rules ()
    ((_ (var val) form ...)
     (let ([stashed-var var])
       (dynamic-wind
        (λ () (set! var val))
        (λ () form ...)
        (λ () (set! var stashed-var)))))))

现在

> current-midi-channel
0
> (with-horribly-shallow-bound-global-variable (current-midi-channel 4)
    current-midi-channel)
4

应该清楚这个技巧是多么令人讨厌和 unsafe/limiting。

我很想知道您真正的问题是什么以及您想要达到什么目的。但是如果你真的想向对象发送多条消息而不重复它的名字,那么你应该检查 send*:

(send* edit (begin-edit-sequence)
            (insert "Hello")
            (insert #\newline)
            (end-edit-sequence))

相同
(let ([o edit])
  (send o begin-edit-sequence)
  (send o insert "Hello")
  (send o insert #\newline)
  (send o end-edit-sequence))

如果你的函数 return 那个对象,我也会尝试 threading library and ~> 宏:

#lang racket

(require threading)

(~> '(1 2 3)
    second
    (* 2))