
@carl.morris.world has joined the channel

@oldsin, I had to laugh. The Scribble manual is not too long, true, but sometimes docs are Bible-length, and I was imagining someone asking a Bible-related question and then apologizing, “Sorry for not reading the full Bible before asking.” I’m sure your question helped someone who did not already know the answer. The Racket community is by far the friendliest and most helpful technology community I’ve encountered.

These days the question isn’t “can I” but “how do I”.

A quick question here and thanks in advance for possible help!
Is there any way in DrRacket that FunctionName
and InputName
could be distinguished in different colors? Like in Elixir or any other languages.
Thank you!

I don’t think so. In DrRacket find “Preferences…” in the “DrRacket” menu. Then click the tab “Colors”.

You can now see what’s possible to change.

Note also that colors change after clicking the “Check Syntax” icon (upper right corner: check mark+looking glass).

Then you can hover over an identifier and see arrows that show the binding.

I don’t think so. A common strategy (which you’ve probably already considered) is to parameterize over the language with a macro.

Hah! This is actually cool @soegaard2 Thanks a lot!

I’m curious about why struct
doesn’t support individually defined defaults for struct fields? As far as I can tell the only option is to use #:auto
and the corresponding #:auto-value
which unfortunately doesn’t quite do what I want to be able to do.
I’d like to be able to do something like
(struct a (b c [d #:default 'hi]) #:transparent)
(define test1 (a 1 2)) -> (a 1 2 'hi)
(define test2 (a 1 2 'hello)) -> (a 1 2 'hello)
Am I completely out of luck?

@abmclin you can use one of several packages that provide abstractions over struct

ok, my issue now is that I’m constrained to using just pre-base and available modules in racket/private
I’ll think of something

in that case you’re out of luck — the underlying make-struct-type
is constrained that way

I would just write a constructor that does what you want

yeah that’s what I’m thinking as well, thanks

@samth why was the #:auto
feature originally introduced? the feature seems kind of bizarre

It’s an old feature. It’s present in PLT Scheme 372: http://download.plt-scheme.org/doc/372/pdf/mzscheme.pdf (page 30)

how old is that?

It’s not present in PLT Scheme 103 (from year 2000).

It’s in PLT Scheme 200 from 2002. http://download.plt-scheme.org/doc/200/html/mzscheme/mzscheme-Z-H-4.html#%_sec_4.3


@mflatt & @robby, I think that @michael.ballantyne and I found a bug in either syntax-local-lift-expression
or the contract library: https://gist.github.com/LeifAndersen/5edd52dd2a6e4b1d5a116790ddf1add2


(It doesn’t have to be pict
, anything that uses contra t-out
seems to have the same bug.

Not sure what’s going on, but this one doesn’t do it:

(module m racket/base
(require racket/contract)
(provide (contract-out [x any/c]))
(define x 1))
(require (submod "." m))
(define-syntax (m stx)
#`(list #,(syntax-local-lift-expression #'x)))
(#%expression (m))

The x
needs to be in both the lift and directly in the macro. So this one does do it:

#lang racket
(module m racket/base
(require racket/contract)
(provide (contract-out [x any/c]))
(define x 1))
(require (submod "." m))
(define-syntax (m stx)
#`(list x #,(syntax-local-lift-expression #'x)))
(#%expression (m))

(The difference being the second to last line: #`(list x #,(syntax-local-lift-expression #'x)))
)

I see.

might be a bug in this function:


The direct output gets expanded first and lifts something to do variable-reference->module-source; the lifted output is expanded later and uses a cached reference to that same lift, but to a definition that ends up appearing lower in the module.

that does indeed sound like a bug in the linked function.

maybe

in any case, the code I linked to is what is managing the cache you mention

@robby YUp, and looks like it converts the identifiers in the source to the lifted varients, yes?

variants*

Oh, I think I get it. Maybe. But I don’t see how to fix it.

I’m not sure how to fix it without either 1. giving up on caching or 2. changing the rules for lifts. The basic assumption that lifts introduced by expressions that expand earlier will execute earlier is wrong.

I could imagine the expansion of lifts being different, such that the lifted expression is expanded immediately instead of delayed, but I don’t know how much other code that might break.

that sounds like a scary change

I think it would be okay if the macro just got a little more information and then used two different entries in the cache for these two situations (lifted vs not) somehow

The goal of this lifting is to avoid creating wrappers multiple times for some given export (say circle
) that is used many times in a single module.

So having some number of wrappers proportional to the number of times lifts are invoked inside lifts would be fine, I presume

Honestly, it seems to me like caching ‘should’ be fine. Is there any reason why syntax-local-lift-expression
can’t do a binding dependency walk to see what order things should go in?

that’d be easier for me. :wink:

lol

But it doesn’t sound like the kind of thing we might expect the macro system to do.

Not that I’m an expert here

Although honestly @robby I would much prefer to have a cache lift. Making a new lift whenever an identifier that uses a contract is used is….well seems inefficient.

Fair. I would probably defer to @michael.ballantyne or @mflatt here, as they know way more than me. :slightly_smiling_face:

I don’t think that this particular inefficiency is going to matter in practice. But if you see somewhere where people are lifting inside lifts that lift inside lifts etc etc etc and use contracts and those contracts are creating too many wrappers, then lets figure out a better way. (Or if it isn’t possible to communicate something that could be used as suitable key.)

Looks like the expansion of lifts is delayed so that local-expand/capture-lifts
can exist.

Err…I have been known to write macros that are hairy enough to do that. :confused:

(I mean, I don’t want to write lifts in lifts….But sometimes there isn’t any other way)

And I don’t see a way for the contract macros to figure out whether they’re inside a syntax-local-lift-expression or not.

The syntax-local-context is ’expression.

The syntax-local-lift-context isn’t changed.

Add syntax-local-context/including-number-of-lifts
that is like syntax-local-context
but might return something with a natural inside?

(well, the name is a bit on the short side for my taste, so maybe that’s not a good idea :wink:)

lol….also eww…..but mostly lol.

You’re right, the problem is that the name needs to be roughly 10x longer.

That is certainly an interesting idea though.

What if syntax-local-lift-context
returned an element in a partial order instead of an equivalence class, where ctx1 <= ctx2 meant that all of the lifts of ctx1 are available in ctx2?

The only use of local-expand/capture-lifts
in the standard distribution collections appears to be in Typed Racket. It could be adapted to work with a modification of the expander that expands as it lifts, instead of delaying. I don’t have an easy way of searching the rest of the universe of Racket code…

@ryanc do you see a good reason expansion of lifted expressions is delayed?

@ryanc depending what you answer to MB: the partial order thing isn’t as amenable to the cache.

@michael.ballantyne If it’s not delayed, then the context needs to be fixed up before expansion. Consider syntax-parameters, for example, and maybe other things defined in Racket that the macro expander doesn’t know about. I don’t know how much of an issue that is, but that’s what I would worry about.

What do you mean about the context needing to be fixed up? Scopes? Environment?

Ah. Environment, like syntax parameters.

I see.

It’s an algebraic effects problem! The effect of lifted expansion needs to happen outside the scope of local effects like syntax parameterize.

If only the expander didn’t install a continuation barrier. :slightly_smiling_face:

@michael.ballantyne sorry, my fault :slightly_smiling_face:

(I’ve run into other places where I really want to save off things like the current introduction scope in a continuation and restore it later)

(You could stash an introduction scope somewhere easily enough, but obviously the other things are much harder—like the binding context.)

The problem is I can’t restore the introduction scope.

Ah, right.

What are the use cases for local-expand/capture-lifts
? Aside from basically “Typed Racket”.

wondering that myself

@robby It seems like this would work with caching, it would just move some of the responsibility to the macro expander. Instead of a target meaning both “this is where lifts go” and “this is what’s available” (which this bug demonstrates is not true), it would only mean the latter. So the cache would be a Key => (listof (pair Ctx Id))
and before lifting you would have to check if the current context extended any of the existing ones. Would that work?

Typed Racket uses it to typecheck the lifted expression, I think. Other contexts where you’re re-writing fully expanded Racket might do something similar.

I can do that

I think the use case in TR is more specific/general than that — just wanting to expand something and put all the relevant pieces in one place

It seems like you could imagine situations where capturing lifts in a more local context could be theoretically useful, but I imagine a lot of macros that use syntax-local-lift-expression
assume the result is going to end up at a module top level, and I don’t know if that would be an easy invariant to maintain unless you’re doing a TR-like thing in #%module-begin
.

in general, would “local-expand
from a module context that wants to do whole-module transformations” be a good place to use it?

that isn’t really where Typed Racket uses it

oh, where does TR use it?

it’s used in require/contract
(part of the implementation of require/typed
but needed to implement require/contract
as an abstraction), in how the toplevel works, and in with-types

@ryanc I don’t see how changing syntax-local-lift-context
would help with caching in syntax-local-lift-expression
?

@leif The idea is to change syntax-local-lift-context
to return something that represents whether a previously lifted identifier is actually available here and now. Thinking about it some more, my idea of a partial order isn’t quite right, or at least it would be a little awkward—you’d have to get the context, check it, lift, and then get the new context and save that in the cache. Perhaps it would be better to have a predicate that takes a lift context and an identifier and returns true if the identifier is available (previously lifted to that context or a parent and defined above the current expansion point).

In other words, the contract library currently assumes that if an expression was previously sent to the current lift context, the resulting id is available. Your example shows that the order of sends doesn’t always correspond to the order of receives (that is, the order that lifted definitions occur in the expanded module). So hopefully a fix would be to check whether the lifted expression was previously received (that is, its definition occurs above the current expansion position).

Ah, okay, that makes sense.

Thanks for the question; writing the answer helped clarify my thoughts. I (currently?) think the new predicate on identifiers would be the easiest solution.

It’s a little like a degenerate version of the letrec/undefined analysis exposed as part of the macro API.

Mmmm…so it seems like it would technically break backwards compatibility. But not in any meaningful way, is that correct?

Sorry, forget what I said about changing syntax-local-lift-context
. Let’s say we add a new thing, like syntax-local-defined-above? : Identifier -> Boolean
. The contract cache would maintain a mapping Key => (Listof Identifier)
. Each time you lift the expression corresponding to a key, you add the resulting identifier to the key’s associated list. But before doing a new lift, you check the key and see if any of the identifiers are syntax-local-defined-above?
; if so, it’s safe to just use that identifier as a reference.

Sounds straightforward!

pop-pl
uses it because to expands into a unit and some of its lift’s bind state that should be local to each unit instantiation. (Not that I ever claim that pop-pl does anything the right way)