

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