laurent.orseau
2021-2-2 12:59:20

laurent.orseau
2021-2-2 12:59:30

There are some strange things happening with check syntax. Take this code above, and try this:


laurent.orseau
2021-2-2 12:59:46

laurent.orseau
2021-2-2 13:00:03

The arrow from the first bar to the second is wrong. Worse:


laurent.orseau
2021-2-2 13:00:39

This time the last highlighted bar seems to be defined at two different places!


laurent.orseau
2021-2-2 13:00:52

~Also the very last bar of the file has no binding information at all.~


sorawee
2021-2-2 13:05:54

Haha


sorawee
2021-2-2 13:05:58

That’s actually correct


sorawee
2021-2-2 13:06:10

Let me try to come with with a fix first


sorawee
2021-2-2 13:08:44

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 ...)))))


sorawee
2021-2-2 13:10:07

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


sorawee
2021-2-2 13:11:01

The last bar has no binding information because it’s in the body ... position


sorawee
2021-2-2 13:11:08

and you haven’t used it in the template


sorawee
2021-2-2 13:11:38

I guess your last arg is supposed to be body ...


laurent.orseau
2021-2-2 13:12:38

(yes for body ... , ok for last bar)


sorawee
2021-2-2 13:15:38

It’s also a good idea in general to (let ([VAL val]) ...) to prevent extraneous evaluation.


laurent.orseau
2021-2-2 13:17:31

I don’t understand the issue with the previous-to-last bar


sorawee
2021-2-2 13:17:45

What do you mean?


laurent.orseau
2021-2-2 13:17:49

(ok for extra eval)


sorawee
2021-2-2 13:18:05

Even if it has a side effect?


laurent.orseau
2021-2-2 13:18:35

I mean I’m ok with your answer re. extra eval :slightly_smiling_face:


sorawee
2021-2-2 13:18:45

ah


sorawee
2021-2-2 13:19:21

Actually, I just realized my answer is not quite right either, but probably an OK solution for you nonetheless


sorawee
2021-2-2 13:19:31

I just noticed that:

(define (foo a [b a]) 1)


sorawee
2021-2-2 13:19:33

This works


laurent.orseau
2021-2-2 13:20:14

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


sorawee
2021-2-2 13:21:51

So, there are two occurrences of val in the body of macro def, yes?


laurent.orseau
2021-2-2 13:22:36

yes


sorawee
2021-2-2 13:22:40

Consider the second occurrence. It’s under the lambda body


sorawee
2021-2-2 13:23:26

A similar situation is:

#lang racket (require syntax/parse/define) (define-simple-macro (test foo bar) (λ (foo) bar)) (test baz baz)


sorawee
2021-2-2 13:23:49

Do you agree that there should be an arrow from baz to baz?


laurent.orseau
2021-2-2 13:24:49

Actually I don’t :slightly_smiling_face:


sorawee
2021-2-2 13:25:19

This has always been the expected behavior


sorawee
2021-2-2 13:25:37

Consider creating your new-lambda that just expands to lambda


laurent.orseau
2021-2-2 13:25:47

(i’ll be back in 30mn, sorry)


sorawee
2021-2-2 13:25:59

If it doesn’t work like this, new-lambda wouldn’t have any arrow


laurent.orseau
2021-2-2 14:00:07

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.


laurent.orseau
2021-2-2 14:01:31

Just like (test baz fizz) fails


sorawee
2021-2-2 14:01:58

So, using the argument above, I want to write:

(define-simple-macro (new-lambda (arg) body ...) (lambda (arg) body ...)) (new-lambda (x) x)


sorawee
2021-2-2 14:02:26

Because arg and body are two different things, you expect that there shouldn’t be an arrow from x to x?


laurent.orseau
2021-2-2 14:03:06

ah right. argh, cognitive conflict, let me think


sorawee
2021-2-2 14:04:31

(naming can be very powerful cognitively :slightly_smiling_face:)


laurent.orseau
2021-2-2 14:17:58

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 baris bound to the bar just before it, then it should shadow any prior bar . So why is there an arrow from the top bar?


laurent.orseau
2021-2-2 14:20:08

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 ?


sorawee
2021-2-2 14:20:12

Yes


laurent.orseau
2021-2-2 14:20:18

Shouldn’t it detect instead that something is wrong?


sorawee
2021-2-2 14:21:11

I don’t see why this is wrong. If you truly mean it to have this behavior, I would consider it to be correct


laurent.orseau
2021-2-2 14:21:52

Seems to me that a template id that is bound from 2 sides can only be wrong


laurent.orseau
2021-2-2 14:22:09

or a bad idea


laurent.orseau
2021-2-2 14:22:39

at least in nested scopes


laurent.orseau
2021-2-2 14:23:05

in particular if one is unbound in the macro, and the other is bound in the macro


sorawee
2021-2-2 14:23:25

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.


laurent.orseau
2021-2-2 14:30:13

thanks a ton for your help!


laurent.orseau
2021-2-2 14:36:30

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


sorawee
2021-2-2 14:38:48

Well, you might still want to avoid code duplication (and thus double evaluation)


laurent.orseau
2021-2-2 14:39:27

sure, but I also can’t evaluated the expression unconditionally before the lambda


sorawee
2021-2-2 14:40:24

Why not? Because of (lambda (a [b a]) ...)?


laurent.orseau
2021-2-2 14:41:43

if val contains side effects, they will always be evaluated


laurent.orseau
2021-2-2 14:41:51

even if the default value is not used


sorawee
2021-2-2 14:41:57

Ah, makes sense


sorawee
2021-2-2 14:41:58

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.


sorawee
2021-2-2 14:42:25

So you have two levels of missing values


sorawee
2021-2-2 14:42:41

One local to the function. One is global for your library


343519265
2021-2-2 14:42:58

@343519265 has joined the channel


laurent.orseau
2021-2-2 14:43:04

Also, I need to think harder about it, but at least currently my side effects tests all pass :slightly_smiling_face:


laurent.orseau
2021-2-2 14:43:16

So I probably don’t even need to do that


laurent.orseau
2021-2-2 14:43:34

(the actual code is more intricate than the example above obviously)


sorawee
2021-2-2 14:44:30

Here’s a test case:

(def (name [arg (begin (println 42) 'NO-VALUE)]) 1)


laurent.orseau
2021-2-2 14:44:57

yes, I have such a case (with set! instead)


sorawee
2021-2-2 14:45:20

and it’s not evaluated twice?


laurent.orseau
2021-2-2 14:46:06

no


sorawee
2021-2-2 14:46:20

Huh… how come?


laurent.orseau
2021-2-2 14:46:21

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) )


sorawee
2021-2-2 14:46:40

Well, it needs to evaluate to 'NO-VALUE


sorawee
2021-2-2 14:47:07

set! evaluates to #<void>


laurent.orseau
2021-2-2 14:47:13

oh


laurent.orseau
2021-2-2 14:47:55

That’s not exactly a worry


sorawee
2021-2-2 14:47:56

Can anyone get the value of 'NO-VALUE?


sorawee
2021-2-2 14:48:01

If it’s private, then maybe it’s fine


laurent.orseau
2021-2-2 14:48:11

sure you can, but what would you expect to mess up with default values?


sorawee
2021-2-2 14:48:27

:woman-shrugging:


laurent.orseau
2021-2-2 14:48:47

(define2 (foo #:? bar) bar) (foo) ; -> no-value


sorawee
2021-2-2 14:48:56

Right


laurent.orseau
2021-2-2 20:11:10

Couldn’t check-syntax check that the keywords at call sites match the keywords given by procedure-keywords ?


samth
2021-2-2 20:13:17

I don’t think it makes sense for Check Syntax to do that, but the macro itself could warn about wrong keywords.


laurent.orseau
2021-2-2 20:15:00

you mean #%app?


samth
2021-2-2 20:15:43

no, (define (f #:x [x 1]) x) expands to a definition of macro named f, which could do this check and warn


laurent.orseau
2021-2-2 20:17:09

oh ok, yes. That should be standard, I always mess up keyword names :slightly_smiling_face:


notjack
2021-2-2 21:25:14

Yes please


laurent.orseau
2021-2-2 22:24:10

That’s a start :slightly_smiling_face:


laurent.orseau
2021-2-2 22:27:24

Also:


laurent.orseau
2021-2-2 22:27:47

but this requires mouse hovering


notjack
2021-2-2 22:34:11

Can you get it to check number of positional arguments as well?


laurent.orseau
2021-2-2 22:35:33

Yes


laurent.orseau
2021-2-2 22:37:39

Haha that’s a bug in my example above. I thought i had written #:d


notjack
2021-2-2 22:38:13

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)


laurent.orseau
2021-2-2 22:41:12

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


laurent.orseau
2021-2-2 22:46:44

Or maybe just reuse the tooltip info


notjack
2021-2-2 22:53:08

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


notjack
2021-2-2 22:55:02

contract-out presents a wrinkle


notjack
2021-2-2 22:55:12

since it wraps the contracted identifiers