Today's Qi meeting notes

Last week's notes:

Designing List Operations

Highlights:

  • Perfoming functional list operations with Qi syntax with qi/list
  • A funky case of backwards incompatibility that isn't really
  • Introducing λ syntax to Qi for great justice
  • Compiler architecture and neatly separating expansion and compilation via appropriate introduction of core language forms
  • A Qi compiler community event could be in the cards

Enjoy! (And the meeting today starts in about an hour -- join us if you're around).

@dominik.pantucek @michaelballantyne I'm not sure I understand the issue with knowing floe nonterminal positions in code generation for the new #%deforestable core form, but I've suggested a possible approach in the notes :person_shrugging:

Just a quick note: I think the Previous link on that page is broken.

I think the issue is that different deforest-able procedures might accept procedure arguments in different positions: assuming all higher-order positions are to be flow syntax, you need to know which ones to make flows for which positions (which suggests a way to mark them is needed). But I may also have misunderstood.

Yes you're right @benknoble . I think another aspect is that we need to know which ones are flow syntax positions at the expansion stage, since the expander will need to expand those positions into the core language before the deforestation comes into play. I think I was confused earlier because at the compilation stage, I felt we could just take the name of the form (which could be preserved as a field in the new core form) and manually encode into the compiler how the various positions in that form should be codegen'd to Racket (at least as a short term solution to unblock further deforestation work). But this wouldn't be enough if portions of the form haven't even been expanded from surface Qi yet (or are incorrectly expanded as wrapped Racket via esc).

Yeah there are a few broken links lately since I haven't posted notes for some meetings. I still have many of the notes in WIP so if I get around to posting them the links will get fixed, otherwise I will declare bankruptcy and connect the links to skip over the missing ones (don't worry, the "order of effects" one -- which was such a good one :smile: -- I still intend to get to it as soon as we return to discussing the topic!).

1 Like

Notes for a recent meeting on the subject of Git best practices:

Commit Issues

Highlights:

  • Small vs large commits
  • Consistent vs inconsistent commits
  • Cohesive vs hodgepodge commits
  • Complete vs partial commits
  • Normalized vs denormalized commits
  • Commits over a short vs long timeframe
  • "Death of the author"

And guiding principles for writing good commits.

We are all still learning and we will be continuing this discussion on Friday (i.e. day after tomorrow), so if you have strong opinions on Git practices that projects should follow, or would like to learn about such practices for your own project, this would be a good one to join for! All are welcome, and hope to see you there.

As a reminder, Qi meets in the old Racket gather.

Alright we're back with Qi meeting notes this week! Here are today's notes:

One Track Minds

This one was fairly in-the-weeds about next steps for Qi's deforestation of list-oriented APIs. Some highlights:

  • what to do when macros start to look a lot like functions?
  • more on achieving clean separation of hosted language from host language, and how enriching the core language is a way to do that
  • Qi 5 on the horizon?

Today's notes:

Choose Your Words Carefully

Highlights:

  • we're continuing on list deforestation, incorporating it formally in the core language and supporting Qi syntax in higher-order positions in map, filter, etc.
  • we have some ideas on making deforestation extensible to custom (e.g. user-defined) list APIs, but it'll be some time before we understand how
1 Like

Today's notes:

Much Ado About Binding

Highlights:

  • A bindings bug bash where we investigated some bugs
  • There's a new release of Syntax Spec!
  • lambda to the rescue to delay the evaluation of bindings
  • Racket's left-to-right evaluation order and mutation via set!
  • compose is very general and therefore slow
  • Should we introduce a core language form to specifically identify bindings?

Notes from two weeks ago. Hopefully, just like the videos from RacketCon, they will be worth the wait :stuck_out_tongue_closed_eyes:

:gun: Kindergarten Compiler :sunglasses:

Highlights:

  • Doug's question at RacketCon: "How is Racket attempting to appeal to a larger audience?"
  • Plans for a potential performance-themed community event :rocket:
  • A unique compiler architecture :onion:
  • How to add two numbers together (the easy way) :teacher:
  • Deforestation for climate justice :palm_tree:

Please join us for tomorrow's meeting if you're around!

1 Like

Last week's notes:

Rank Polymorphic Qi

Highlights:

  • a best practice for macro-generating macros
  • progress on a mind-bending way to extend a DSL in a "deep" way at both the expansion (like macros) and compilation level
  • designing qi/list operations to support multiple values, potentially deviating from
    the semantics of corresponding racket/list operations
  • starting simple
1 Like

Last week's notes:

An Expander for your Expander

This one was pretty in-the-weeds on developing a proof of concept for "deep extension" of the Qi compiler further, which would eventually allow us to extend deforestation to custom list operations -- essentially exposing both syntax and semantics to extension, in a limited way, via a kind of macro system.

Last week's notes:

Getting Ahead of Ourselves

Highlights:

  • a completed proof-of-concept for extending Qi deforestation to custom list operations, with syntax-parse-like syntax
  • are we embedding a DSL in a DSL?
  • looking ahead: deforestation for generic sequences and bespoke datatypes
  • S-expressions are bad!
1 Like

symex is peak; sexpr is so awkward to say in polite company.

1 Like

Today's notes:

No More Unknowns!

Highlights:

  • a sketch of a macro interface for extending Qi's deforestation to user-defined list operations
  • the only thing I know is that I don't know that there isn't something I don't know
  • a clever hack is often good enough
  • why is raco setup building packages besides the one indicated using --pkgs? (we don't know -- input appreciated!)
1 Like

Yesterday's notes:

Playing Like a Grandmaster

This was pretty dense / in-the-weeds -- may be of interest to compiler enthusiasts.

Highlights:

  • producers, transformers, and consumers -- stream abstractions for deforesting any list-oriented operation
  • encoding two runtimes -- naive and optimized -- in "deep" macros
  • Michael is teaching an exciting new course on DSLs next semester
2 Likes

Last week's notes. Some good stuff!

Pulling a Rabbit Out of a Hat

Highlights:

  • we're unexpectedly in a position to do a new Qi release soon, maybe next week! :yin_yang:
  • improving Qi build times (thanks @samth and @bogdan !)
  • navigating lagomorphic singularities :rabbit:
  • phases as reflective towers of languages :tokyo_tower:
  • can we have just one kind of binding in Racket? @benknoble @hendrikboom3 This continues the discussion from before. We didn't talk about it very much but there were a lot of interesting things that came up, all the same!
1 Like

RE: bindings

I think one question that still isn't answered to me is how a macro like this would work in a world without define-syntax and with for-lang:

(define (f x) x)

(define-syntax m
  (syntax-parser
    [(_ y) #'(f y)]))

(Both intentionally kept simple.)

If f can't be defined in the same module as m, it has to be "public" in some other module[1]. But worse, it means the module declaring m has to require the module declaring f… for what? Not for-lang: we don't intend to make f a macro. But not normally, either: we don't intend to call f in this module.[2]

And we probably don't intend to burden clients of the module defining m with the demand that they also require a module defining f despite never invoking f in the source text! (Worse, would this burden mean we're back to the days of "a macro's meaning after expansion depends on the runtime environment where it was expanded"?)

The ability to make the above example work as intended is one of the properties of macro hygiene as I see it: I as macro-author control what bindings I expand to in a predictable way that can't be subverted by my clients without going through my established protocol (if any).

But this is a bit of a tangent anyway :sweat_smile:


  1. It could be one that has a module path including private, our little convention for "don't poke around in here if you like stability," but that doesn't resolve other concerns. ↩︎

  2. In the existing system, you can require f from another module (sans phase-shifting) and then expand to a use of it, so it's not that there's a problem doing this—it's more about the ability to do something else (like keep f private to anyone except require/expose). I think there are some subtleties for negative phase shifts depending on how the macro is intended to be used which can get ugly, though. ↩︎

2 Likes

I'm imagining something like this:

(define (f x) x)

(define m
  (syntax-parser
    [(_ y) #'(f y)]))

And then in a module requiring this one for-lang, f would be bound in the macro scope and would not need to be imported there.

To avoid exporting anything other than syntax transformers for-lang, we could put f in a submodule and then (require my-f-module) in the macro module, and only (provide m), something like:

(provide m)

(require 'my-f-module)

(define m
  (syntax-parser
    [(_ y) #'(f y)]))

(module my-f-module racket/base
  (define (f x) x)) 

In this way, the macro is just another, regular program, whose purpose happens to be to extend the expander via the for-lang hook, and it uses things at runtime that it need not provide just like any other program. It's not that different from today, but, in my (perhaps mistaken :sweat_smile: ) view, uses fewer concepts, since it is exactly the same as any program, and can be run in a standalone way without attaching to the expander at all.

| countvajhula
December 13 |

  • | - |

I'm imagining something like this:

(define (f x) x)

(define m
  (syntax-parser
    [(_ y) #'(f y)]))

And then in a module requiring this one for-lang, f would be bound in the macro scope and would not need to be imported there.

Maybe I misunderstood, but I thought the idea of for-lang would turn f into a macro? IOW: how does m become a macro but f doesn't? (Something to do with the provide clause? Seems like spooky action at a distance, now…)

To avoid exporting anything other than syntax transformers for-lang, we could put f in a submodule and then (require my-f-module) in the macro module, and only (provide m), something like:

Right, that could work, too. Less important if the example works without providing f at all, which would be on-par with today's module system with the following trades AIUI:

  • no define-syntax
  • require for-lang coöperates with provide to turn some things into macros
    ?

In this way, the macro is just another, regular program, whose purpose happens to be to extend the expander via the for-lang hook, and it uses things at runtime that it need not provide just like any other program. It's not that different from today, but, in my (perhaps mistaken :sweat_smile:) view, uses fewer concepts, since it is exactly the same as any program, and can be run in a standalone way without attaching to the expander at all.

Funny—I think if I wanted to do this today, I could. And then I would have a client-facing module somewhere that did

(require (prefix-in impl: defines-module))
(define-syntax m impl:m) ; …

Though there might be some oddities about what f refers to in the current system with that approach? IOW, there's a half-baked lowering of for-lang into "normal" Racket, but I don't know how it resolves f here!

Yes, functions used in the macro defining scope for the purposes of providing meaning to the macro (like f) need not be provided, just like today. Your assessment regarding the remaining differences sounds right to me.

(require (prefix-in impl: defines-module))
(define-syntax m impl:m) ; …

Neat! I tried a few permutations of this and finally this worked for resolving f:

(module mac racket/base
  (provide m)

  (require syntax/parse
           (for-template "defs.rkt")
                         racket/base))

  (define m
    (syntax-parser
      [(_ y) #'(f y)])))

(require (for-syntax
          (prefix-in lang: 'mac)))

(define-syntax m lang:m)

(m 5)

… where "defs.rkt" contains the f definition.

IOW, there's a half-baked lowering of for-lang into "normal" Racket, but I don't know how it resolves f here!

A reasonably nice half-baked for-lang would be if we could abstract this behind a (require-for-lang ...) macro! But I do feel there are deeper issues touched upon here. Might as well promote this thread out to avoid overloading here, as there are a few other things I'd like to mention.