kellysmith12.21
2021-4-24 16:07:50

That’s funny. I tend to prefer let over local define, but I quickly realized it was a losing battle :laughing:


sschwarzer
2021-4-24 16:12:10

Same for me. I find let “more functional” (even though the result might usually be the same), but I now follow the Racket code guidelines.


rokitna
2021-4-24 18:18:42

Scheme and Racket would be be simpler languages to extend, reimplement, and redesign if they didn’t have pervasive local define.

It creates a series of design gotchas: What about side-effecting expressions in between? What about putting other declarations at a local level, like macro definitions, require forms, for-meta blocks, submodule declarations, and calls to user-defined declaration macros? What about using an expression macro or a declaration macro in the same set of declarations it’s defined in (potentially even “before” it’s defined)? What about letting macros check what variables are in the caller’s lexical scope, even partway through expanding a set of declarations?

Racket goes to impressive effort to support situations like these, from partial expansion to use-site scopes to compile-time first-class local definition contexts. Still, Racket’s approach leaves a visible distinction between using a transformer binding before or after it’s defined. One reason I prefer to use modules instead of load and pure techniques instead of side effects is because I don’t want to have to care what order my code is arranged in, but writing mutually recursive macros in Racket can still push me into the zone where the order of the code matters.


samth
2021-4-24 20:26:55

But the difference between using a transformer binding before or after its definition seems pretty fundamental. Getting rid of internal define wouldn’t make that go away. The only way to make that go away would be to eliminate macros that expand to definitions, which would be a drastic restriction on expressiveness


sschwarzer
2021-4-25 01:28:28

Another thing about define vs. let. While this works fine: (define (foo_func x) (define square (* x x)) (+ square square)) (foo_func 3) this doesn’t: (define foo_value (define square (* 3 3)) (+ square square)) foo_value This gives “define: bad syntax (multiple expressions after identifier)”. I ran into this recently and was surprised that while I had been able to use the first structure all the time, I couldn’t use the second structure.

But you can always use (define foo_value (let ([square (* 3 3)]) (+ square square))) foo_value So let seems more universal/less surprising.


samth
2021-4-25 01:29:38

I mostly think we should just make the foo_value definition work


sschwarzer
2021-4-25 01:30:39

> It creates a series of design gotchas: […] If nested defines are so complicated, does this also imply that the compiler can optimize lets more efficiently than nested defines? In other words, would be replacing a nested define by let be something worth trying to speed up code?


samth
2021-4-25 01:31:26

Any use of define that would just translate into simple lets will get translated that way


sschwarzer
2021-4-25 01:31:30

> I mostly think we should just make the foo_value definition work If it’s not too hard, this would be great.


sschwarzer
2021-4-25 01:33:32

> Any use of define that would just translate into simple lets will get translated that way I hope when the time comes, I’ll know whether a define would translate to simple lets. :slightly_smiling_face:


samth
2021-4-25 01:34:18

What I mean is that if you can just replace it all with let without breaking the program, that’s what happens


samth
2021-4-25 01:34:57

If you need letrec, then it uses letrec but perhaps not in the most minimal possible way.


rokitna
2021-4-25 01:48:46

I think the complexity I’m talking about impacts the ease of documentation as well as the system’s expressiveness in niche situations (like local definitions of local definition forms). Performance isn’t on my mind much when I’m thinking about it; that seems secondary to getting it to work.


rokitna
2021-4-25 02:00:01

Since about 2010, I’ve been pursuing an approach to mutually recursive macro definitions that uses deterministic concurrency, in which one macroexpansion can suspend itself if it depends on the result of another definition. That way, two definitions at the same level don’t happen one before the other; they happen concurrently, and they can be freely rearranged.

I’ve mainly used this kind of technique for asynchronous module loading and module-level declaration forms. A couple of years ago I decided that maybe local definitions weren’t all bad, and I started to work on a design for those too, but it’s not something I’ve been able to devote time to.

Racket’s partial expansion is somewhat like expanding partway and then suspending, but Racket doesn’t use this for mutual recursion between macros.

I think the suspendable macros in Idris 2 are a lot like what I’ve been building, so I’m excited for those.


rokitna
2021-4-25 02:07:11

I know it’s rough, but these are my personal notes on how I want to build out a system of local definitions in Cene. They’re intermingled with plans for being able to compile individual files of code (something Racket can do that I didn’t realize I’d want for Cene at first). <https://github.com/era-platform/cene-for-racket/blob/main/notes/20190731-lexical-units.md\|https://github.com/era-platform/cene-for-racket/blob/main/notes/20190731-lexical-units.md>


rokitna
2021-4-25 02:19:37

In short: This design would expand declarations using two lexical environments: One that represents the outer edge where none of the declarations are in scope yet, and one that represents the inner edge where all of them are in scope. Declarations would run concurrently, and retrieving things from the inner environment would usually block until all the declarations had committed to some particular set of variables to bind.

In order to begin expanding the declaration forms themselves, the macro name of the declaration form (e.g., define or struct) is looked up in the outer environment. (If it were looked up in the inner one, every declaration would immediately block and we’d never get anywhere.) So in this system, it takes a little extra work if you want to define a definition form and use it right away.