Qi Meeting Oct 17 2025 - drym-org/qi GitHub Wiki
Qi Meeting October 17 2025
Adjacent meetings: Previous | Up | Next
We celebrated Eutro's beginning grad school by playing "PLT Pictionary." We also specified an initial semantics for Qi in Redex.
Recently, we celebrated Dominik's entry into grad school by playing trivia. We wanted to do something fun to celebrate Eutro's first grad school forays, too.
Not long ago, we started to formalize Qi semantics on pen and paper, and identified writing a tangible specification in Redex as the next step.
It was time to celebrate Eutro's initiation into grad school!
Short on time to find good trivia questions, and also desirous of actually participating in the game himself this time, Sid asked Gemini to come up with some words for "PLT/CS pictionary" before the meeting. He asked Gemini to generate a Python script that contained the secret words encoded using ROT13, so that we could all exchange the script and see it without getting any spoilers. It came up with:
import random
import time
import codecs
# --- CS Pictionary Word List (ROT13 Encoded) ---
# The list is obfuscated so the person running the script can't read it.
# It will be decoded at runtime.
encoded_word_list = [
'Qrsberfgngvba',
'Gur L Pbzovangbe',
'Gnvy Pnyy Bcgvzvmngvba',
'N YVFC Znpeb',
'Ubzbgbl Glcr Gurbel',
'Glcr Vasrerapr',
'Pbagvahngvba',
'Ynzoqn Pnyphyhf',
'Zbanq',
'Pheelvat',
'F-rkerffvba',
'ERCY (Ernq-Riny-Cevag Ybbc)',
'Fvqr Rssrpg',
'Cher Shavgvba',
'Tneontr Pbyyrpgvba',
'Dhvar',
'Nofgenpg Flagnk Gerr (NFG)',
'Sbezny Irevsvpngvba',
'N Flavgurfmre Zbqhyr',
'Sybj-Onfrq Cebtenzzvat',
'Rznpf',
'N pbagvahngvba-cnffvat fglyr genafsbez'
]
def pick_random_word(word_list):
"""Selects and returns a single random word from the provided list."""
return random.choice(word_list)
def main():
"""Main function to run the word picker."""
print("Picking a random word for CS Pictionary...")
# A small delay to build suspense!
time.sleep(1.5)
# Pick a random word from the *encoded* list
chosen_encoded_word = pick_random_word(encoded_word_list)
# Decode the word from ROT13 to plain text
decoded_word = codecs.decode(chosen_encoded_word, 'rot_13')
print("\n------------------------------------")
print(f" Your word is: {decoded_word}")
print("------------------------------------")
print("\n(Don't let the others see this screen!)")
if __name__ == "__main__":
main()
Sid shared this with the group.
Now, you'd think people would appreciate this gesture and say, "oh, what a nice idea Sid, this is really very thoughtful of you." But noooo, instead, everyone was all up in arms about it being Python instead of Racket! And there were complaints about it being generated by an LLM and, come on, we can't just run code from the internet like that, we need to audit it! And why this whole script? Can't we just write it as a bash one-liner?
And so began a whole sidetrack to the meeting with many parallel efforts to implement this pictionary game properly 🤦
Did someone mention Bash oneliner? 😭 Of course, Dominik couldn't resist, and got to work on it right away.
Eutro began a thorough audit of the script.
In the meantime, to put us on the holy path without delay, Sam had an LLM translate the code into Racket to give us a more worthy starting point. But this version didn't do the ROT13 encoding (since Sid hadn't had that brilliant idea yet in the initial version of the script that he shared), and Racket doesn't have a built-in ROT13 encoder the way python does, so we would still have to add that before we could use it.
#lang racket
;; --- CS Pictionary Word List ---
;; A tailored list for a PL/Racket/Formal Systems group.
;; These are designed to be hilariously difficult to draw. Good luck!
(define word-list
'("abc" "def"))
(require racket/base)
;; Function: pick-random-word
;; Selects and returns a single random word from the provided list.
;; (list-ref list index) is used to access an element by index.
;; (random N) returns a random non-negative exact integer less than N.
(define (pick-random-word lst)
(let ((list-length (length lst)))
(if (= list-length 0)
#f ; Return false for an empty list
(list-ref lst (random list-length)))))
;; Function: main
;; Main function to run the word picker.
(define (main)
(displayln "Picking a random word for CS Pictionary...")
;; A small delay to build suspense!
(sleep 1.5) ; sleep is in seconds, just like time.sleep()
(let ((chosen-word (pick-random-word word-list)))
(displayln "\n------------------------------------")
;; Racket uses 'format' or 'string-append' for string construction.
;; ~a is the format specifier for a general value.
(displayln (format " Your word is: ~a" chosen-word))
(displayln "------------------------------------")
(displayln "\n(Don't let the others see this screen!)")))
(main)
But, but! We already have a working script, here, fellas! Yes yes, it's python, written by an LLM, I get it. But it already works! Whadayasay we just use it? 😃
Dominik finished crafting his one-liner and shared it, in bash + Ruby:
ruby -e 'puts(proc{|lst| lst[rand*lst.size]}[$stdin.readlines])'
But this didn't handle ROT13 encoding, either. Wait, wait, now:
ruby -e 'puts(proc{|lst| lst[rand*lst.size]}[$stdin.readlines].downcase.tr("abcdefghijklmnopqrstuvwxyz","nopqrstuvwxyzabcdefghijklm"))'
But this doesn't handle capital letters yet.
Um, so, should we play pictionary, or ...?
Just then, Stephen remembered that Cassie had written a ROT13 encoder in Qi a while ago that she had shared on Discord, so he dug that up and tacked it onto the Racket script. That should do the trick:
#lang racket
;; qi rot13 by cassie https://discord.com/channels/571040468092321801/812451168194002984/964453849295355906
(require qi)
{define string-map
(flow (~> (==* _ string->list) map list->string))}
{define rot13
(flow (switch [(char<=? #\a _ #\z) (~> char->integer (- 97) (+ 13) (modulo 26) (+ 97) integer->char)]
[(char<=? #\A _ #\Z) (~> char->integer (- 65) (+ 13) (modulo 26) (+ 65) integer->char)]
[else _]))}
(string-map rot13 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ #$&*+-!?")
;; --- CS Pictionary Word List ---
;; A tailored list for a PL/Racket/Formal Systems group.
;; These are designed to be hilariously difficult to draw. Good luck!
(define word-list '[
"Qrsberfgngvba"
"Gur L Pbzovangbe"
"Gnvy Pnyy Bcgvzvmngvba"
"N YVFC Znpeb"
"Ubzbgbl Glcr Gurbel"
"Glcr Vasrerapr"
"Pbagvahngvba"
"Ynzoqn Pnyphyhf"
"Zbanq"
"Pheelvat"
"F-rkerffvba"
"ERCY (Ernq-Riny-Cevag Ybbc)"
"Fvqr Rssrpg"
"Cher Shavgvba"
"Tneontr Pbyyrpgvba"
"Dhvar"
"Nofgenpg Flagnk Gerr (NFG)"
"Sbezny Irevsvpngvba"
"N Flavgurfmre Zbqhyr"
"Sybj-Onfrq Cebtenzzvat"
"Rznpf"
"N pbagvahngvba-cnffvat fglyr genafsbez"
])
;; Function: pick-random-word
;; Selects and returns a single random word from the provided list.
;; (list-ref list index) is used to access an element by index.
;; (random N) returns a random non-negative exact integer less than N.
(define (pick-random-word lst)
(let ((list-length (length lst)))
(if (= list-length 0)
#f ; Return false for an empty list
(list-ref lst (random list-length)))))
;; Function: main
;; Main function to run the word picker.
(define (main)
(displayln "Picking a random word for CS Pictionary...")
;; A small delay to build suspense!
(sleep 1.5) ; sleep is in seconds, just like time.sleep()
(let ((chosen-word (pick-random-word word-list)))
(displayln "\n------------------------------------")
;; Racket uses 'format' or 'string-append' for string construction.
;; ~a is the format specifier for a general value.
(displayln (format " Your word is: ~a ~a" chosen-word (string-map rot13 chosen-word) ))
(displayln "------------------------------------")
(displayln "\n(Don't let the others see this screen!)")))
(main)
Oh, but it has some rendering issue:
"nopqrstuvwxyz{bcdefghijklmNOPQRSTUVWXYZ[BCDEFGHIJKLM #$&*+-!?"
Picking a random word for CS Pictionary...
------------------------------------
Your word is: Pbagvahngvba Continu{tion
------------------------------------
(Don't let the others see this screen!)
>
Amidst all these parallel efforts to implement the code correctly, everyone finally agreed, grudingly, to run the now-thoroughly-audited original script and just play the game. After all, Ruby had to be almost as evil as Python, right?
Gemini promised that the words would be perfect for this group, and you know what, they weren't too bad (some of them, anyway).
There was some ambiguity about the rules and whether we were allowed to write text, but since we are talking about programming languages, we felt that some text is perhaps unavoidable as long as it is used more pictorially than literally. We also agreed on a 3 minute time limit.
The words were (image with canvas below):
-
"Abstract Syntax Tree." Drawn by Eutro. Guessed by Sid!
-
"Continuation." Drawn by Sam. Guessed by Eutro!
Sam simply wrote "k" for this one, and Eutro guessed it immediately! But upon further deliberation, we felt that "k" is effectively, in the Racket community, a symbol designating the word "continuation." And so, although it was clever, Sam was disqualified!
-
"Continuation passing style transform." (whatever this is!) Drawn by Dominik. Nobody guessed this one, obviously. Is it even a real thing??
-
"Tail call." Drawn by Stephen. Guessed by Sam!
-
"S-expression." Drawn by Sid. Guessed by Sam!
The final shared canvas looked like this:
In the end, the results were tied at Sam and Sid for 2 points each.
Well, if you thought that this nice and fun game of pictionary would provide sufficient amusement and deter all those competing efforts to implement the game properly, then guess again!
Dominik had by this time written a version using Qi and Qi/list:
#lang racket/base
(require qi qi/list)
(~> ("Qrsberfgngvba"
"Gur L Pbzovangbe"
"Gnvy Pnyy Bcgvzvmngvba"
"N YVFC Znpeb"
"Ubzbgbl Glcr Gurbel"
"Glcr Vasrerapr"
"Pbagvahngvba"
"Ynzoqn Pnyphyhf"
"Zbanq"
"Pheelvat"
"F-rkerffvba"
"ERCY (Ernq-Riny-Cevag Ybbc)"
"Fvqr Rssrpg"
"Cher Shavgvba"
"Tneontr Pbyyrpgvba"
"Dhvar"
"Nofgenpg Flagnk Gerr (NFG)"
"Sbezny Irevsvpngvba"
"N Flavgurfmre Zbqhyr"
"Sybj-Onfrq Cebtenzzvat"
"Rznpf"
"N pbagvahngvba-cnffvat fglyr genafsbez")
list
(tee _ (~> length random))
list-ref
string-downcase
string->list
(map (~> (if (and (char>=? _ #\a) (char<=? _ #\z)) (~> char->integer (- (char->integer #\a)) (- 13) (modulo 26) (+ (char->integer #\a)) integer->char) _ )))
list->string)
Stephen submitted this to the Racket Discord's R16 trick bot. Unfortunately, it looked like R16 bot had an old version of Qi without qi/list, so Dominik updated it to older vanilla Qi code, and that worked.
#lang racket/base
(require qi)
(~> ("Qrsberfgngvba"
"Gur L Pbzovangbe"
"Gnvy Pnyy Bcgvzvmngvba"
"N YVFC Znpeb"
"Ubzbgbl Glcr Gurbel"
"Glcr Vasrerapr"
"Pbagvahngvba"
"Ynzoqn Pnyphyhf"
"Zbanq"
"Pheelvat"
"F-rkerffvba"
"ERCY (Ernq-Riny-Cevag Ybbc)"
"Fvqr Rssrpg"
"Cher Shavgvba"
"Tneontr Pbyyrpgvba"
"Dhvar"
"Nofgenpg Flagnk Gerr (NFG)"
"Sbezny Irevsvpngvba"
"N Flavgurfmre Zbqhyr"
"Sybj-Onfrq Cebtenzzvat"
"Rznpf"
"N pbagvahngvba-cnffvat fglyr genafsbez")
list
(tee _ (~> length random))
list-ref
string-downcase
string->list
(map (flow (~> (if (and (char>=? _ #\a) (char<=? _ #\z)) (~> char->integer (- (char->integer #\a)) (- 13) (modulo 26) (+ (char->integer #\a)) integer->char) _ ))) _)
list->string)
Stephen made a few further edits to get everything hooked up correctly, and we were finally all set with an officially approved PLT pictionary script that you can now play interactively on Discord!
Stephen — 10/17/25, 1:16 PM
!!cspictionary
r16
APP
— 10/17/25, 1:16 PM
"nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM #$&*+-!?"
Picking a random word for CS Pictionary...
------------------------------------
Your word is: The Y Combinator
------------------------------------
(Don't let the others see this screen!)
That's pretty darn cool, actually.
Stephen posted about this game on Discourse shortly after the meeting, and that inspired yet another implementation by Damien, in Scheme+'s curly infix syntax:
#lang reader SRFI-105 ; SRFI-105 Curly-infix-expressions
(module repl racket
(provide (all-defined-out))
(require qi)
{("Qrsberfgngvba"
"Gur L Pbzovangbe"
"Gnvy Pnyy Bcgvzvmngvba"
"N YVFC Znpeb"
"Ubzbgbl Glcr Gurbel"
"Glcr Vasrerapr"
"Pbagvahngvba"
"Ynzoqn Pnyphyhf"
"Zbanq"
"Pheelvat"
"F-rkerffvba"
"ERCY (Ernq-Riny-Cevag Ybbc)"
"Fvqr Rssrpg"
"Cher Shavgvba"
"Tneontr Pbyyrpgvba"
"Dhvar"
"Nofgenpg Flagnk Gerr (NFG)"
"Sbezny Irevsvpngvba"
"N Flavgurfmre Zbqhyr"
"Sybj-Onfrq Cebtenzzvat"
"Rznpf"
"N pbagvahngvba-cnffvat fglyr genafsbez")
~> list ~> (tee _ {length ~> random}) ~> list-ref ~> string-downcase ~> string->list
~> (map (flow (~> (if ((char>=? _ #\a) and (char<=? _ #\z))
(~> char->integer (- (char->integer #\a)) (- 13) (modulo 26) (+ (char->integer #\a)) integer->char)
_ )))
_)
~> list->string}
)
And on and on it goes!
When we last left off on formalizing Qi semantics, we had started to refine a basic pen-and-paper model into a dual specification in terms of reduction rules and congruence rules.
We discussed that the reason for having a formal semantics for Qi is to have a reference that captures the intended design rather than any idiosyncracies of various particular implementations that we may explore, such as Qi where flows are functions vs Qi where flows are futures or something else.
Stephen asked whether there were any such idiosyncracies in the standard, function, implementation, as, otherwise, this implementation could itself serve as the reference.
But indeed, there are already some idiosyncracies and deviations from the design here, such as the fact that bindings defined in one tine of a tee junction currently bind all subsequent tines (due to scoping constraints in Syntax Spec), whereas the design expects the tines of the tee junction to be truly parallel, with bindings in one tine completely invisible to another. This has practical consequences, for instance, binding across tines implies coupling across these tines that would constrain parallelizability. This aspect would also complicate alternative implementations if it is treated as a reference to mimic.
Eutro developed the approach further, still on paper, until we reached a point where the general approach was clear.
She then began the translation into Redex. The main difference from the pen and paper approach was that Redex's semantic model is specified in terms of "contexts" with "holes," which are analogous to continuations, instead of using congruence relations. Therefore, each such congruence relation that we had developed needed to be translated into a specification in terms of contexts, which proved to be a fairly straightforward translation.
Once the semantics was complete with respect to the basic core forms, we discussed where we should house these semantics. As they are intended to be Qi's reference semantics, we felt it should either be committed into the Qi repo, or hosted on the Qi wiki. We agreed that maintaining a new qi-redex package in the Qi repo would be the best strategy, so Eutro started a PR to add it.
The current working implementation of the Redex semantics uses a version of gen that allows multiple values in a single position, e.g., (gen (values 1 2 3)). This is itself a deviation from the design (where multiple values need to be passed in separate single-value positions as (gen 1 2 3), and passing them in one position would not cause them to be concatenated), but we felt that this is just a minor formal detail as the purpose of these semantics isn't to have a formally verified compiler but to provide a reference for the semantics of the many forms of the language. Otherwise, if we do not take this version of gen as a given, it might require formalizing more of Racket semantics in Qi's semantics than we'd like. So we felt that adopting some theoretical conveniences in this one very basic form could be OK, as it's the only handwavy part, which can be treated as a black box for the purposes of the rest of the semantics.
Now that we have this basic semantics available, we still need to encode Qi's binding rules and effect semantics. For bindings, we currently don't know how they can be notated using Redex, as Qi's variable scope isn't textually contained as it is for Racket, and we don't know if Redex provides a straightforward way to handle this. As for effects, Sid still needs to finish writing up the meeting notes from our very productive discussion on the topic last year, and also incorporate it into the corresponding docs PR. So we can cross this bridge on effects once we have something tangible there.
We briefly discussed that it appeared plausible, based on past observations and recent explorations, that the tee junction together with the ground form could potentially be "universal" in some way, capable of expressing all of the other forms of the language. This was an intriguing idea that we did not explore further. But formalizing semantics could help reveal some of these fundamental properties of the language. We've already seen how gen is foundational, which makes sense as it's the core primitive for a flow producing values. Sam also recently noticed some nontrivial equivalences while writing Chai, such as expressing fanout with tee. And Noah has also observed a number of abstract and essential relationships between Qi forms from a category theoretic standpoint. Likewise, it seems, we may discover other interesting and foundational things as we proceed with formalizing Qi semantics, and there may be applications to it that we haven't yet thought of.
Dominik recently purchased a new drone to replace his old drone that unfortunately drowned in the sea. A fitting end!
Eutro is working on a way to formalize variable substitution using set-of-scopes, for the purpose of making proofs in lambda calculus more intuitive.
We realized that there were at least eight cats attending the Qi meeting!
(Some of these are carried over from last time)
- Add bindings and effects to the Redex semantics.
- Incorporate Chai into Qi.
- Implement a compile-time table of known arities and add an entry to it as part of
define-flow. - Ready the inlining PR to be merged and tag for code review.
- Write phase 1 unit tests for inlining.
- Define the
define-producer,define-transformer, anddefine-consumerinterface for extending deforestation (also encapsulating both naive and stream semantics in the definition), and re-implement existing operations using it. - Implement the remaining producers and transformers in
racket/listforqi/list. - Attach a
deforestedsyntax property in the deforestation pass, and use it in compiler rules tests (instead of string matching). - Improve
qi/listand deforestation testing by writing a macro to simultaneously test the expansion and the semantics. - Investigate whether the deforested operations could be expressed using a small number of core forms like
#%producer,#%transformer,#%consumer, and#%stream. - Decide on whether there will be any deforested operations provided in
(require qi)(without(require qi/list)) - Review which
racket/listforms are actually needed inqi/list - Come up with a good way to validate the syntactic arguments to
rangeusing contracts. - Start organizing
qi-libintoqiandqi/basecollections - Publish
qi/classin some form. - Implement DAG-like binding rules for branching forms [Syntax Spec parallel binding-spec PR]
- Incorporate effects and bindings into Qi's pen-and-paper semantic model.
- Return to developing Qi's theory of effects, including accounting for binding rules.
- Write a proof-of-concept for implementing code generation from abstractions of "flow" and "connective tissue" that are set by a context parameter.
- Why is
range-map-carslower against Racket following the Qi 5 release? - Decide on appropriate reference implementations to use for comparison in the new benchmarks report and add them.
- Add reader flow syntax in
#lang qi - Develop a backwards-incompatibility migration tool using Resyntax, to be used in the next Qi release.
Dominik, Eutro, Sam, Sid, Stephen