There are some strange things happening with check syntax. Take this code above, and try this:
The arrow from the first bar to the second is wrong. Worse:
This time the last highlighted bar
seems to be defined at two different places!
~Also the very last bar
of the file has no binding information at all.~
Haha
That’s actually correct
Let me try to come with with a fix first
Here’s a fix:
(define-simple-macro (def (name [arg val]) body ...)
(define name
(let ([VAL val])
(λ (name [arg VAL])
(let ([arg (if (eq? arg 'NO-VALUE) VAL arg)])
body ...)))))
The problem is that, the second bar
also appears in the body of the lambda, so there should be an arrow from it to its binder, which is the first bar
The last bar
has no binding information because it’s in the body ...
position
and you haven’t used it in the template
I guess your last arg
is supposed to be body ...
(yes for body ...
, ok for last bar
)
It’s also a good idea in general to (let ([VAL val]) ...)
to prevent extraneous evaluation.
I don’t understand the issue with the previous-to-last bar
What do you mean?
(ok for extra eval)
Even if it has a side effect?
I mean I’m ok with your answer re. extra eval :slightly_smiling_face:
ah
Actually, I just realized my answer is not quite right either, but probably an OK solution for you nonetheless
I just noticed that:
(define (foo a [b a])
1)
This works
But I don’t understand why the pictures are correct. When you hover on the arrows in the macro definition, it does show the correct arrows
So, there are two occurrences of val
in the body of macro def
, yes?
yes
Consider the second occurrence. It’s under the lambda body
A similar situation is:
#lang racket
(require syntax/parse/define)
(define-simple-macro (test foo bar)
(λ (foo)
bar))
(test baz baz)
Do you agree that there should be an arrow from baz
to baz
?
Actually I don’t :slightly_smiling_face:
This has always been the expected behavior
Consider creating your new-lambda
that just expands to lambda
(i’ll be back in 30mn, sorry)
If it doesn’t work like this, new-lambda
wouldn’t have any arrow
I don’t see why. What I see in your macro above, is that foo and bar are two different things. To me (test baz baz)
should fail.
Just like (test baz fizz)
fails
So, using the argument above, I want to write:
(define-simple-macro (new-lambda (arg) body ...)
(lambda (arg) body ...))
(new-lambda (x) x)
Because arg
and body
are two different things, you expect that there shouldn’t be an arrow from x
to x
?
ah right. argh, cognitive conflict, let me think
(naming can be very powerful cognitively :slightly_smiling_face:)
Ok, so I’m on the same page for the first picture, but now I’m still struggling with the second one: if the rhs bar
is bound to the bar
just before it, then it should shadow any prior bar
. So why is there an arrow from the top bar
?
Oh, is the first arrow due to the (define (foo [bar bar]))
where bar
is bound to the top one, but the second arrow refers to the second use of the template bar
on the let
line, and checksyntax collapses the two uses ?
Yes
Shouldn’t it detect instead that something is wrong?
I don’t see why this is wrong. If you truly mean it to have this behavior, I would consider it to be correct
Seems to me that a template id that is bound from 2 sides can only be wrong
or a bad idea
at least in nested scopes
in particular if one is unbound in the macro, and the other is bound in the macro
Bad idea, perhaps, but Racket does allow the situation above to happen. So Check Syntax must do something about it. And I think this is an OK behavior for the situation.
thanks a ton for your help!
So a better fix would be: (define-simple-macro (def (name [arg val]) body ...)
(define (name [arg2 val])
(let ([arg (if (eq? arg2 'NO-VALUE) val arg2)])
body ...)))
which suggest that in my actual case I will need to use generate-temporaries
I guess
Well, you might still want to avoid code duplication (and thus double evaluation)
sure, but I also can’t evaluated the expression unconditionally before the lambda
Why not? Because of (lambda (a [b a]) ...)
?
if val contains side effects, they will always be evaluated
even if the default value is not used
Ah, makes sense
There’s a simple workaround. Conjure up yet another gensymed value. Initialize all of your arguments to it. If it’s eq to this gensymed value, set it to the default value.
So you have two levels of missing values
One local to the function. One is global for your library
@343519265 has joined the channel
Also, I need to think harder about it, but at least currently my side effects tests all pass :slightly_smiling_face:
So I probably don’t even need to do that
(the actual code is more intricate than the example above obviously)
Here’s a test case:
(def (name [arg (begin (println 42) 'NO-VALUE)]) 1)
yes, I have such a case (with set!
instead)
and it’s not evaluated twice?
no
Huh… how come?
This all pass: (let ()
(define c 0)
(define2 (foo #:? [bar (set! c (+ c 1))])
(list bar bar))
(check-equal? c 0)
(foo #:bar 'a)
(check-equal? c 0)
(foo)
(check-equal? c 1)
(foo #:bar 'a)
(check-equal? c 1)
(foo)
(check-equal? c 2)
)
Well, it needs to evaluate to 'NO-VALUE
set!
evaluates to #<void>
oh
That’s not exactly a worry
Can anyone get the value of 'NO-VALUE
?
If it’s private, then maybe it’s fine
sure you can, but what would you expect to mess up with default values?
:woman-shrugging:
(define2 (foo #:? bar) bar)
(foo) ; -> no-value
Right
Couldn’t check-syntax check that the keywords at call sites match the keywords given by procedure-keywords
?
I don’t think it makes sense for Check Syntax to do that, but the macro itself could warn about wrong keywords.
you mean #%app
?
no, (define (f #:x [x 1]) x)
expands to a definition of macro named f
, which could do this check and warn
oh ok, yes. That should be standard, I always mess up keyword names :slightly_smiling_face:
Yes please
That’s a start :slightly_smiling_face:
Also:
but this requires mouse hovering
Can you get it to check number of positional arguments as well?
Yes
Haha that’s a bug in my example above. I thought i had written #:d
This is excellent and I’d love for it to be part of racket/base
(though maybe it has to be opt-in somehow, to prevent breaking existing code)
Maybe we can do even better: add a syntax property that carries around the signature, and display it in the bottom bar (or somewhere else) while typing. That would require more cooperation from the editor, and will still fail if doesn’t compile
Or maybe just reuse the tooltip info
at that point you’re going down the road of a type system, and going a little further down that road seems worthwhile, but just the bare basics of getting arity checking for functions that were created with define
is already a massive improvement
contract-out
presents a wrinkle
since it wraps the contracted identifiers