Reputation: 255
I'm attempting to implement coroutines in Racket LISP for a personal learning project using the scheme implementation from this accepted answer. However, when loading my .rkt file into the racket repl I get the following error:
; 3.rkt:111:18: define: not allowed in an expression context
; in: (define (run-handler) (make-generator (lambda (yield) (send
; (get-dp-data-object key) run))))
It seems to be complaining about the define(s) in this section of the code:
108 (define-syntax (define-coroutine stx)
109 (syntax-case stx ()
110 ((_ (name . args) . body )
111 #`(define (name . args)
112 (make-generator
113 (lambda (#,(datum->syntax stx 'yield))
114 . body))))))
According to the accepted answer here this exact error is not shared by Scheme and is unique to Racket when attempting defines in an expression.
The code calling (define-coroutine) seems to be:
518 ;; Qt-esque connect macro
519 (define-syntax connect-message
520 (syntax-rules ()
521 [(register src-obj-key msg-type dst-obj-key handler)
522 (register-message-handler
523 msg-type
524 (begin
525 (define-coroutine
526 (handler-accessor)
527 (if (eqv? (get-dp-data-object dst-obj-key) #f)
528 #f
529 (send
530 (get-dp-data-object dst-obj-key)
531 handler
532 (get-field args msg))))
533 handler-accessor))]))
This is my first Racket project so I'm learning a lot as I go. The (begin) above is trying to define and return a coroutine that calls an object method. I'm sure that there's lots of problems with this code snippet, but the debugger is blocking me with the above issue, stopping me from getting later errors :)
I am not nearly skilled enough in Racket, Scheme, or LISP to fix this issue, I can hardly understand this error at the moment. Can someone break down the problem for me and hopefully correct the issue so I can get this coroutine code working in Racket?
Upvotes: 1
Views: 358
Reputation: 22332
In racket, begin
does not create a new scope.1 This means that anything you define inside of a begin
form is still in scope outside of that form. It also means that begin doesn't change the context, and so if you are in an expression context, begin
keeps that.
The define
form has (roughly) the following grammar:
(define <id> <expr>)
Where <id>
is the variable name, and <expr>
is an expression. However, the define
form itself is not an expression, so something like this is invalid:
(define x (define y 5))
And because begin doesn't change the context, this is invalid too:
(define x
(begin
(define y 5)
y))
Instead, you can use let
to create a new scope. And because let itself is an expression, you can put it in define. So you could write something like:
(define x
(let ()
(define y 5)
y))
And now, x
is bound to 5
as expected.
Taking this back to your original question, you had the code:
518 ;; Qt-esque connect macro
519 (define-syntax connect-message
520 (syntax-rules ()
521 [(register src-obj-key msg-type dst-obj-key handler)
522 (register-message-handler
523 msg-type
524 (begin
525 (define-coroutine
526 (handler-accessor)
527 (if (eqv? (get-dp-data-object dst-obj-key) #f)
528 #f
529 (send
530 (get-dp-data-object dst-obj-key)
531 handler
532 (get-field args msg))))
533 handler-accessor))]))
I'm assuming register-message-handler
is a function, and therefore requires an expression. But you have define-coroutine
, who's elaboration is a define
form. So rather than using begin
, you can use a let
to turn it into an expression, giving you something like this:
(register-message-handler
msg-type
(let ()
(define-coroutine (handler-accessor) ....)
handler-accessor))
1Bad design decision...I know. Water under the bridge from many decades back. :(
Upvotes: 4