Thanks for all the input @greg. Food for thought.
maybe @robby has some opinion about this? (see top post)
I don’t have anything useful to add I don’t think. Greg is right as far as I can tell. (I wouldn’t have thought of suggesting a runtime test actually)
The problem with the runtime check is that the macro will need to be expanded to its full length before checking which branch to take. This is slow but more importantly it can make the code significantly larger (since it’s for each function definition). One option I see is to expand based on a environment variable, but I’ve found this path to be rather annoying in the past.
At macro expansion time it hasn’t been yet decided if errortrace instrumentation will happen or not.
Errortrace happens later.
After expansion is all done.
okay, then I think I won’t use that route :confused:
So I think you need to design a new api somehow.
Greg suggests that the check for where to put the zos (compiled vs compiled/drracket) would also be a runtime check, do you confirm @robby?
It sounds like a bad idea to do that check.
I don’t think there are any promises you can rely on like that.
Then maybe it’s possible to detect the value of the debugging
radio button at compile time?
(probably in the Preferences somewhere?)
I don’t think this is a good idea.
I’ll stick with my suggestion that you design a new API that captures exactly what you want via some documented predicate.
But that puts the burden on the user :confused:
The. You can implement the predicate either in some tricky hacky way or not but you leave a path open for not having horrible backwards compatibility constraints.
I don’t see why.
You are designing and publishing an API that you can rely on instead of relying on things that are best left as internal details of drr
But then the user has to know about this API and switch themselves something to turn on/off my debugging mode, when there’s already something like that happening in DrRacket
But at least I can have an API to let the user choose what they want, plus a default automatic mode that uses the DrRacket debugging value maybe, and this can be deactivated
I don’t think so?
You design, implement, and document an API with a predicate that captures exactly what you want to capture. The documentation makes it clear when this function should return #t
or what. So now we know what the predicate should do and we’ve divorced the meaning of the predicate form the state of various internals of DrRacket.
Then you implemetn the API today, perhaps using the internals of DrRacket or perhaps via a pull request to DrRacket.
Then you call the API.
I don’t see how there are any users involved here.
It is all about being clear about what this new predicate is supposed to mean
The predicate means “more debugging info, but larger generated code”
Compare with the situation where you document that your macros depend on the value of the parameter that determines where zo files go and if it contains the word “drracket” in it.
As long as the predicate doesn’t mean “drracket’s internal state looks like X not Y” that seems like an improvement to me.
I really would appreciate you not tying my hands if I want to fix bugs in drracket but am not able to because it breaks this thing you’re contemplating doing now.
oh yeah, that does make a lot of sense :slightly_smiling_face:
The burden that this could put on your shoulders completely skipped my mind I must say, sorry
I also think you probably have something slightly more refined and helpful than just what you quoted above. I guess that that’s the essence of it, but some examples of how it might be useful, etc., will go a long way in the API docs.
Oh, it’s okay!
I also think that some api docs with some examsples and whatnot plus a request for others comments might go a long way too.
Once people can see clearly what you have in mind, some good suggestions might be forthcoming.
Basically I’m writing a variant of define
that can do compile-time error checking on arguments (so syntax-check plays an essential role in this):
But this requires define
to have a significantly larger expanded code.
it would still be useful also with raco make
, so you’re definitely right that I shouldn’t tie it to a drr value in any case.
What happens if you write (define x (if (read) foo goo)) where one of them has a keyword and the other doesnt?
But once everything compiles, the error checking expanded code is useless and should be thrown away
@mflatt is it feasible to get (for ([x xs]) ...)
to be as efficient as (for ([x (in-list xs)]) ...)
when cptypes
can figure that xs
is a list?
The longer expansion is only in application position like (foo ...)
So this code should live only at compile time?
yes
Then I don’t think you want to condition on the presence of errortrace.
Offhand, I don’t know how to make that work.
Just check always
check what?
If the appropriate keywords are present.
hm… right. Plus I think I can largely mitigate the issue with a syntax-class to reduce the expanded code. Will have to try though.
I haven’t fully read the whole thread, but for @robby’s comment:
> At macro expansion time it hasn’t been yet decided if errortrace instrumentation will happen or not. I think in a sense it does decide already: the macro expansion is invoked by expand-syntax
here: https://github.com/racket/errortrace/blob/master/errortrace-lib/errortrace/stacktrace.rkt#L693. So if we parameterize
some parameters over that expand-syntax
, I think macros would be able to query for the information.
I think the answer is “yes, in principle, but it would be hard in Chez currently”. Right now, Typed Racket optimizes the former by replacing (make-sequence '(x) xs)
with (values unsafe-car unsafe-cdr values ...)
. That’s effectively what you’d get from inlining make-sequence
and then seeing that you need to inline :list-gen
and removing all the other parts of make-sequence
that are irrelevant. That doesn’t get you to unsafe-car
etc, though, but just the pair tests should accomplish that already. However, that kind of type-influenced inlining (it’s a bad idea to inline make-sequence
unless this works out) isn’t there in Chez right now, plus I don’t know if Chez-level inlining will ever do something for make-sequence
because it’s in a different module.
I agree that we could set something up where a promise to invoke errortrace or not after expansion would be communicated.
Oh, I should also note that what Typed Racket does is sufficient — the results after cp0 are the same as for using in-list
.
I was trying to say that errortrace works by processing fully expanded code.
But I do agree that, in practice we don’t seem to be saving fully expanded code and then either adding instrumentation or not.
We certainly could, however. (Unless something changes).
But maybe @laurent.orseau ’s plans don’t require us to constrain errortrace after all? Let’s see.
I’d certainly prefer to avoid having to write any PR :slightly_smiling_face:
The Racket Foreign Interface reference doesn’t completely explain how _cvector works.
the mode argument is the same _ptr has?
Yes — same as _list
, so that part is meant to be covered by “The longer form behaves similarly to the https://docs.racket-lang.org/foreign/foreign_procedures.html#%28form._%28%28lib._ffi%2Funsafe..rkt%29.__list%29%29\|_list and https://docs.racket-lang.org/foreign/foreign_procedures.html#%28form._%28%28lib._ffi%2Funsafe..rkt%29.__vector%29%29\|_vector custom type…“, but it could certainly be clearer.
Thanks!
Does anyone have experience with the ideas and API in the paper, Macros for Domain-Specific Languages? I have a couple of questions about how to correctly use the API for my project.
A macro question: how do I write a macro bump
such that the following program works as intended?
#lang racket
(define x 1)
(let ()
(bump x 2)
(println x) ;=> 3
)
(println x) ;=> 1
The difficult part for me is referencing previous x
while defining it at the same time. I thought of using (splicing-let ([previous-x x]) (define x (+ previous-x val)))
, but that obviously doesn’t actually work.
It’s OK to change (define x 1)
to (define-syntax x ...)
, btw, as long as all print
still gives the desired results.
OK, turns out that for my use case, I can do all of this at compile-time, so this works for me:
#lang racket
(require syntax/parse/define)
(define-syntax-parser bump
[(_ x v)
#`(define-syntax x (+ #,(syntax-local-value #'x) v))])
(define-syntax-parser query
[(_ x)
#`#,(syntax-local-value #'x)])
(define-syntax x 1)
(let ()
(bump x 2)
(query x)) ;=> 3
(query x) ;=> 1
You could do this by trampolining using local expansion
(define x 1)
(bump-allowing-block
(bump x 2)
(println x))
(println x)
You’d have to make bump-allowing-block
use local-expand
with bump
in the stop list, to translate its body into this: (let ()
(let ([x (+ x 2)])
(println x)))
This is what I did for my guarded-block
and define/guard
macros in rebellion’s internals. Ryan gave me the idea on the users list, see this thread https://groups.google.com/g/racket-users/c/H-EppBmQ7oU
Implementation here https://github.com/jackfirth/rebellion/blob/master/private/guarded-block.rkt
Yeah, with bump-allowing-block
that would be easily possible, but I’m restricted to (let () ...)
drat :(
@notjack idea: support or/c
flattening: (or/c (or/c a b) c)
= (or/c a b c)
This occurs when your existing refactoring replaces symbols
with or/c
, but there’s another or/c
outside.
@sorawee and
, or
, and and/c
are similar cases. Maybe a helper for defining rewrite rules for flattenable constructs?
yeah
And they are associative, even in presence of shortcircuiting