如何在定义的函数中绑定变量?
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))
上下文: 我正在尝试做这样的事情(方案“伪代码”):
(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))