RacketCon 2024

Table of Contents

1. Event Overview

EVENT_DATES: October 5-6, 2024
LOCATION: University of Washington, Kane Hall Room 220, 4069 Spokane Way, Seattle, WA, USA

RacketCon 2024 is the fourteenth annual conference dedicated to the Racket programming language and its ecosystem. This year's event celebrates 40 years of magic with Hal Abelson & Gerald Sussman, and features Lisp legend Gregor Kiczales.

Celebrating 40 years of magic with Hal Abelson & Gerald Sussman, and featuring Lisp legend Gregor Kiczales

2. Schedule

2.1. Saturday, October 5th

2.1.1. Doors Open

LOCATION: Kane Hall Room 220

Breakfast won't be served, so please eat before coming to the event.

2.1.2. Invited Talk: Strategies and Technology for Teaching HtDP at Scale

SPEAKER: Gregor Kiczales
LOCATION: Kane Hall Room 220

Preparation: Familiarize yourself with the "How to Design Programs" (HtDP) approach and its principles. Review Gregor Kiczales' work on aspect-oriented programming.

Here's an example that combines HtDP design principles with a simple aspect-oriented approach:

from functools import wraps

# Aspect: Logging
def logging_aspect(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__} with args: {args}, kwargs: {kwargs}")
        result = func(*args, **kwargs)
        print(f"{func.__name__} returned: {result}")
        return result
    return wrapper

# Data definition
# A Person is a dict with keys: 'name' (str) and 'age' (int)

# Function definition (following HtDP recipe)
# calculate_discount: Person -> float
# Purpose: Calculate a discount based on a person's age
# Examples:
#   calculate_discount({'name': 'Alice', 'age': 25}) => 0.1
#   calculate_discount({'name': 'Bob', 'age': 65}) => 0.2

@logging_aspect
def calculate_discount(person):
    """
    Calculate discount based on age:
    - Under 18: 5% discount
    - 18-64: 10% discount
    - 65 and over: 20% discount
    """
    if person['age'] < 18:
        return 0.05
    elif person['age'] < 65:
        return 0.1
    else:
        return 0.2

# Tests (part of the HtDP approach)
def test_calculate_discount():
    assert calculate_discount({'name': 'Charlie', 'age': 15}) == 0.05
    assert calculate_discount({'name': 'Alice', 'age': 25}) == 0.1
    assert calculate_discount({'name': 'Bob', 'age': 65}) == 0.2
    print("All tests passed!")

# Main program
if __name__ == "__main__":
    test_calculate_discount()
    
    # Example usage
    customers = [
        {'name': 'Young', 'age': 16},
        {'name': 'Adult', 'age': 35},
        {'name': 'Senior', 'age': 70}
    ]
    
    for customer in customers:
        discount = calculate_discount(customer)
        print(f"{customer['name']} gets a {discount:.0%} discount.")

This example demonstrates HtDP principles (clear design, purpose statements, examples, and tests) combined with aspect-oriented concepts (the logging aspect) to create well-structured and maintainable code. It provides a starting point for discussing how these approaches can be scaled to larger programs and effectively taught to students.

2.1.3. Keynote (Remote Presentation)

SPEAKERS: Hal Abelson and Gerald Sussman
LOCATION: Kane Hall Room 220

Preparation: Review the classic "Structure and Interpretation of Computer Programs" (SICP) book and its impact on computer science education. Here are some high-level examples from SICP:

2.1.4. Higher-Order Procedures

SICP introduces the concept of higher-order procedures, which are procedures that can take other procedures as arguments or return procedures as results.

  (define (sum term a next b)
    (if (> a b)
        0
        (+ (term a)
           (sum term (next a) next b))))

  (define (cube x) (* x x x))

  (define (sum-cubes a b)
    (sum cube a (lambda (x) (+ x 1)) b))

  (display (sum-cubes 1 3))
36

2.1.5. Data Abstraction

SICP emphasizes the importance of data abstraction, separating the way data is used from its underlying representation.

(define (make-rat n d)
  (let ((g (gcd n d)))
    (cons (/ n g) (/ d g))))

(define (numer x) (car x))
(define (denom x) (cdr x))

(define (add-rat x y)
  (make-rat (+ (* (numer x) (denom y))
               (* (numer y) (denom x)))
            (* (denom x) (denom y))))

(define r1 (make-rat 1 2))
(define r2 (make-rat 1 3))
(define r3 (add-rat r1 r2))

(display (list (numer r3) (denom r3)))
(5 6)

2.1.6. Metalinguistic Abstraction

SICP introduces the concept of creating new languages to solve specific problems, demonstrating this through the implementation of a simple evaluator.

(define (evaluate exp env)
  (cond ((self-evaluating? exp) exp)
        ((variable? exp) (lookup-variable-value exp env))
        ((quoted? exp) (text-of-quotation exp))
        ((assignment? exp) (eval-assignment exp env))
        ((definition? exp) (eval-definition exp env))
        ((if? exp) (eval-if exp env))
        ((lambda? exp) (make-procedure (lambda-parameters exp)
                                       (lambda-body exp)
                                       env))
        ((begin? exp) (eval-sequence (begin-actions exp) env))
        ((cond? exp) (evaluate (cond->if exp) env))
        ((application? exp)
         (apply (evaluate (operator exp) env)
                (list-of-values (operands exp) env)))
        (else (error "Unknown expression type" exp))))

;; Helper functions (not implemented here for brevity)
(define (self-evaluating? exp) #t)
(define (variable? exp) #t)
(define (lookup-variable-value exp env) exp)

(display (evaluate '(+ 1 2) '()))
<stdin>:12:10: warning: possibly unbound variable `quoted?'
<stdin>:12:24: warning: possibly unbound variable `text-of-quotation'
<stdin>:13:10: warning: possibly unbound variable `assignment?'
<stdin>:13:28: warning: possibly unbound variable `eval-assignment'
<stdin>:14:10: warning: possibly unbound variable `definition?'
<stdin>:14:28: warning: possibly unbound variable `eval-definition'
<stdin>:15:10: warning: possibly unbound variable `if?'
<stdin>:15:20: warning: possibly unbound variable `eval-if'
<stdin>:16:10: warning: possibly unbound variable `lambda?'
<stdin>:16:24: warning: possibly unbound variable `make-procedure'
<stdin>:16:40: warning: possibly unbound variable `lambda-parameters'
<stdin>:17:40: warning: possibly unbound variable `lambda-body'
<stdin>:19:10: warning: possibly unbound variable `begin?'
<stdin>:19:23: warning: possibly unbound variable `eval-sequence'
<stdin>:19:38: warning: possibly unbound variable `begin-actions'
<stdin>:20:10: warning: possibly unbound variable `cond?'
<stdin>:20:32: warning: possibly unbound variable `cond->if'
<stdin>:21:10: warning: possibly unbound variable `application?'
<stdin>:22:27: warning: possibly unbound variable `operator'
<stdin>:23:17: warning: possibly unbound variable `list-of-values'
<stdin>:23:33: warning: possibly unbound variable `operands'
(+ 1 2)

2.1.7. Streams and Lazy Evaluation

SICP introduces the concept of streams and lazy evaluation, allowing for the manipulation of potentially infinite data structures.

(define-syntax cons-stream
  (syntax-rules ()
    ((cons-stream head tail)
     (cons head (delay tail)))))

(define (stream-car stream) (car stream))
(define (stream-cdr stream) (force (cdr stream)))

(define (stream-ref s n)
  (if (= n 0)
      (stream-car s)
      (stream-ref (stream-cdr s) (- n 1))))

(define (integers-starting-from n)
  (cons-stream n (integers-starting-from (+ n 1))))

(define integers (integers-starting-from 1))

(display (stream-ref integers 9))
10

These examples showcase some of the fundamental concepts introduced in SICP, demonstrating the book's approach to teaching computer science principles through practical, hands-on coding exercises. During the keynote, Abelson and Sussman might discuss how these concepts have evolved and their continued relevance in modern computer science education.

2.1.8. A Multi-Language-Oriented Macro System

SPEAKER: Michael Ballantyne
LOCATION: Kane Hall Room 220

Racket's macros are fantastic for building DSLs that mix well with general-purpose code and inherit Racket's extensibility. But they suffer when it comes time to equip your DSL with an optimizing compiler or static analyses. There, the very closeness with Racket that makes language mixing and extension so easy gets in the way. In this talk I'll present my `syntax-spec` metalanguage. It extends Racket's macro system to allow specifications of DSLs as fragments of a multi-language: DSL and Racket code mix, but only at carefully delineated boundaries. The result is that optimizing compilation, language mixing, and extension combine constructively rather than clash.

Preparation: Study Racket's macro system and DSL creation techniques. Explore the concept of multi-language programming.

(define-syntax my-dsl
  (syntax-rules ()
    ((_ expr ...)
     (begin
       (display "Executing DSL code:\n")
       expr ...))))

(my-dsl
 (display "Hello from DSL!\n")
 (+ 1 2 3))

2.1.9. Frosthaven Manager: Built by the Community

SPEAKER: Ben Knoble
LOCATION: Kane Hall Room 220

Consisting of more than 15k lines of code and documentation, written in spare time during the last two and a half years, the Frosthaven Manager would not exist without the Racket community. Let's explore some of the community's contributions, reflect on the benefits of building in the open, and dream about what comes next.

Preparation: Explore the Frosthaven Manager project on GitHub (https://github.com/benknoble/frosthaven-manager) and familiarize yourself with open-source development practices in the Racket community.

Here's a simplified example inspired by the Frosthaven Manager project, demonstrating some Racket concepts and community-driven development practices:

#+BEGINSRC racket #lang racket

(require racket/contract)

;; Define a structure for a character (struct character (name level health) #:transparent)

;; Contract for the character creation function (define/contract (create-character name level) (-> string? (integer-in 1 9) character?) (character name level (* level 10)))

;; Community contribution: Enhanced character creation with validation (define/contract (create-character/enhanced name level #:faction [faction 'neutral]) (->* (string? (integer-in 1 9)) (#:faction (or/c 'good 'evil 'neutral)) character?) (unless (>= (string-length name) 2) (error 'create-character/enhanced "Name must be at least 2 characters long")) (character name level (* level (if (eq? faction 'good) 12 10))))

;; Community contribution: Character combat simulation (define (simulate-combat char1 char2) (let loop ([attacker char1] [defender char2] [round 1]) (define damage (max 1 (- (character-level attacker) (character-level defender)))) (define new-defender (struct-copy character defender [health (- (character-health defender) damage)])) (printf "Round ~a: ~a deals ~a damage to ~a\n" round (character-name attacker) damage (character-name defender)) (if (<= (character-health new-defender) 0) (printf "~a wins!\n" (character-name attacker)) (loop new-defender attacker (add1 round)))))

;; Example usage (module+ main (define hero (create-character/enhanced "Hero" 5 #:faction 'good)) (define villain (create-character "Villain" 4))

(printf "Hero: ~a\n" hero) (printf "Villain: ~a\n" villain)

(simulate-combat hero villain))

;; Community-driven testing (module+ test (require rackunit)

(test-case "Basic character creation" (check-equal? (create-character "Test" 3) (character "Test" 3 30)))

(test-case "Enhanced character creation" (check-equal? (create-character/enhanced "Good" 3 #:faction 'good) (character "Good" 3 36)) (check-exn exn:fail? (λ () (create-character/enhanced "A" 3))))

(test-case "Combat simulation" (let ([output (with-output-to-string (λ () (simulate-combat (character "A" 5 50) (character "B" 3 30))))]) (check-regexp-match #rx"A wins!" output))))

2.1.10. Mutation Testing: better than coverage?

SPEAKER: Charlie Ray
LOCATION: Kane Hall Room 220

Mutation testing is the idea that we can assess the completeness of a test suite by updating (or 'mutating') a single location in the code under test, and checking to make sure at least one of the existing tests fails. Building on Lukas Lazarek's mutation framework for Racket, we apply mutation testing in two very different settings—the classroom, and the open-source software project—to see what kinds of benefit mutation testing might offer above and beyond the commonly-used test case coverage metric.

#lang racket
(define (add1 x) (+ x 1))
(module+ test
(require rackunit)
(check-equal? (add1 0) 1)
(check-equal? (add1 -1) 0))
;; Mutation example: change + to -
;; (define (add1 x) (- x 1))
;; This mutation should cause the test to fail

2.1.11. Trouble with Typed Racket? Try a Boundary Profiler!

SPEAKER: Nathaniel Hejduk
LOCATION: Kane Hall Room 220

When you add types to a portion of your partially-untyped code base in Typed Racket (TR), the type soundness guarantees you gain will make you feel warm and fuzzy. Sometimes, however, doing so will cause your running time to skyrocket, turning your cute, fluffy type annotations into an unexpected tribulation. When such troubles occur, a boundary profiler can help you ease the runtime wrath of contract checking. In this talk, I'll demonstrate how to use a boundary profiler to boldly reduce the overhead of type-checking in your program, without violating the following prime directive: once a component has been typed, it must stay typed forever.

#lang typed/racket
(: add1 (-> Integer Integer))
(define (add1 x) (+ x 1))
(require/typed racket/base
[displayln (-> Any Void)])
(displayln (add1 5))
;; A boundary profiler would help identify performance issues
;; at the interface between typed and untyped code

2.1.12. Break

2.1.13. Type Tailoring: Teach an Old Type Checker New Tricks

SPEAKER: Ashton Wiersdorf
LOCATION: Kane Hall Room 220

Type checkers evolve too slowly to keep up with the libraries and DSLs programmers create. For example, compilers typically see only opaque strings where programmers see regular expressions or SQL queries with rich internal structure. Consequently, type checkers will assign overly-general types where the programmer knows more about the data at hand.

This talk will present two implementations of a type tailoring: the first in Racket, and the second in Rhombus. The Racket tailoring strengthens Typed Racket by making regular expressions legible to the type system, and the Rhombus tailoring relaxes Static Rhombus in the spirit of gradual typing—all using the macro system.

Bio: Ashton Wiersdorf is a PhD student beginning his third year at the University of Utah. He works with his advisor Ben Greenman on the intersection between types and metaprogramming. When he's not working, he enjoys taking hikes with his wife and three-year-old daughter who already knows to answer "Racket" when asked what her favorite programming language is.

2.1.14. Making Extensible Language Frameworks That Compile Quickly Too

SPEAKER: Sean Bocirnea
LOCATION: Kane Hall Room 220

Racket has many frameworks for implementing extensible languages. Unfortunately, while very extensible, they can exhibit compile-time performance problems. For example, Cur, a small dependently typed language written in the Turnstile+ framework, is 5x slower to type check a simple proof than a comparable language type-checked by traditional means. In this talk, we'll figure out why, develop a criteria for what "extensible" even means, explore the tradeoffs between extensibility and performance in existing frameworks, and determine how we can make extensible language frameworks faster without losing extensibility.

Preparation:

  • Review concepts of algebraic data types and type checking.
  • Familiarize yourself with Racket's macro system and syntax-parse.
  • Look into techniques for optimizing macro expansion and compile-time computation.

Code Example:

#lang racket

(require syntax/parse)
(require (for-syntax syntax/parse))

;; Define a simple type checking DSL
(define-syntax (define-type stx)
  (syntax-parse stx
    [(_ name:id (variant:id (field:id type:id) ...) ...)
     #'(begin
         (struct name (variant-tag payload) #:transparent)
         (define (name-type? v)
           (and (name? v)
                (match (name-variant-tag v)
                  [(== 'variant) 
                   (and (list? (name-payload v))
                        (= (length (name-payload v)) (length '(field ...)))
                        (andmap (λ (f t) (t f)) 
                                (name-payload v) 
                                (list type ...)))] ...
                  [_ #f])))
         (define (variant field ...)
           (name 'variant (list field ...))) ...)]))

;; Helper for runtime type checking
(define (type-check v t)
  (unless (t v)
    (error 'type-check "Expected ~a, got: ~a" t v)))

;; Define some basic types
(define (number? v) (number? v))
(define (string? v) (string? v))
(define (boolean? v) (boolean? v))

;; Use our DSL to define an expression type
(define-type Expr
  (Num (value number?))
  (Add (left Expr?) (right Expr?))
  (Mul (left Expr?) (right Expr?)))

;; Example usage
(define expr1 (Add (Num 5) (Mul (Num 3) (Num 4))))
(type-check expr1 Expr?)

;; Evaluation function for our expression type
(define (eval-expr expr)
  (match expr
    [(Num n) n]
    [(Add l r) (+ (eval-expr l) (eval-expr r))]
    [(Mul l r) (* (eval-expr l) (eval-expr r))]))

(printf "Result: ~a\n" (eval-expr expr1))

;; This will raise a type error
;; (define bad-expr (Add (Num "not a number") (Num 10)))
;; (type-check bad-expr Expr?)

This example demonstrates several key concepts related to extensible language frameworks and compile-time performance:

  1. DSL for Algebraic Data Types: We define a `define-type` macro that allows us to easily create algebraic data types with runtime type checking.
  2. Syntax-parse for Macro Definition: We use `syntax-parse` to define our macro, which provides better error messages and more robust macro expansion.
  3. Compile-time Type Checking: The `define-type` macro generates code for runtime type checking, but the structure of the types is determined at compile-time.
  4. Extensibility: This framework is extensible - we can easily add new types and type constructors.
  5. Performance Considerations: While this example doesn't directly address performance optimizations, it sets the stage for discussing how such a system could be optimized. For instance, we could discuss:
    • How to reduce the amount of generated code
    • Techniques for caching type information to speed up repeated checks
    • Strategies for compile-time evaluation of type checks where possible

In the context of the talk, this example could be used to:

  • Demonstrate how extensible language frameworks can be built
  • Discuss the trade-offs between expressiveness (e.g., runtime type checking) and compile-time performance
  • Explore how such a system could be optimized for faster compilation without losing its extensibility

This code provides a concrete starting point for discussing the challenges and potential solutions in creating extensible language frameworks that compile quickly.

2.1.15. First-class Prompt Engineering with llm-lang! (This is a bad idea.)

SPEAKER: William Bowman
LOCATION: Kane Hall Room 220

Introducing, llm-lang, a Racket hash-lang in which prompt engineering in first class! (Why did I do this?) By default, youre writing a prompt! If you need, you can escape into Racket to compute part of the prompt, or compute over the result of the prompt, but you probably won't since LLMs can do it all! (You're joking right?)

Preparation: Familiarize yourself with the basics of prompt engineering for LLMs and Racket's hash-lang system. Check out the llm-lang project on GitHub (https://github.com/wilbowma/llm-lang).

#lang llm-lang

Write a short poem about Racket programming.

#,{
(define theme "functional programming")
(define style "haiku")
}

Now, incorporate the theme of #,theme and use the #,style style.

2.2. Sunday, October 6th

2.2.1. Doors Open

LOCATION: Kane Hall Room 220

Breakfast won't be served, so please eat before coming to the event.

2.2.2. Sorted Collections in Rebellion

SPEAKER: Jacqueline Firth
LOCATION: Kane Hall Room 220

Computation exists in service of data. Useful data structures, therefore, make the difference between what is possible and what is easy. And of the many forms of structured data, sorted data is arguably the most useful. In this talk, we'll tour the sorted data structures in Rebellion, a second standard library for Racket I've developed over the years. We'll collect data into sorted sets, sorted maps, and range sets. We'll reduce and transduce data from one collection into another. We'll order data according to composable comparators. We'll build data up gradually, then all at once. We'll query it, mutate it, persistently update it, and concurrently access it. And we'll do it all with enough performance to wrap up by lunch.

#lang racket
(require rebellion/collection/sorted-set)
(define s (sorted-set < 3 1 4 1 5 9 2 6 5 3 5))
(sorted-set->list s) ; => '(1 2 3 4 5 6 9)

2.2.3. Racket Syntax: The Great, the Good and the Back-to-the-Drawing-Board

SPEAKER: Allan Schougaard
LOCATION: Kane Hall Room 220

In this talk I present a linguistic comparison of language choices in Racket and LISP vs. a number of other computer languages. LISP is by far the programming language with the simplest syntax: only using parenthesis as delineations. However, the Racket and LISP communities have over time added a great variety of semantics on this simple mechanism, some of which have proven great inventions, and some of which other languages may have better solutions to, and that the Racket and LISP communities may learn from. The talk will directly compare elements of yaml, SmallTalk, Ruby, Java, regular expressions, visual programming, and shell programming with Racket.

Preparation: Familiarize yourself with basic syntax in Racket, LISP, YAML, Smalltalk, Ruby, Java, and shell scripting. Consider how these languages approach common programming tasks differently.

Code Examples:

  1. Simple Loop (printing numbers 1 to 5):
(for ([i (in-range 1 6)])
  (displayln i))
;; Common Lisp
(loop for i from 1 to 5 do
  (print i))
# Ruby
(1..5).each do |i|
  puts i
end
// Java
for (int i = 1; i <= 5; i++) {
    System.out.println(i);
}
# Shell (Bash)
for i in {1..5}; do
    echo $i
done
  1. Sum Reducer (sum a list of numbers):
;; Racket
(define numbers '(1 2 3 4 5))
(foldl + 0 numbers)
;; Common Lisp
(defvar numbers '(1 2 3 4 5))
(reduce #'+ numbers)
# Ruby
numbers = [1, 2, 3, 4, 5]
numbers.reduce(:+)
// Java
import java.util.Arrays;
import java.util.List;

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream().reduce(0, Integer::sum);
# Shell (Bash)
numbers=(1 2 3 4 5)
sum=0
for num in "${numbers[@]}"; do
    ((sum += num))
done
echo $sum
  1. List Append:
;; Racket
(define list1 '(1 2 3))
(define list2 '(4 5 6))
(append list1 list2)
;; Common Lisp
(defvar list1 '(1 2 3))
(defvar list2 '(4 5 6))
(append list1 list2)
# Ruby
list1 = [1, 2, 3]
list2 = [4, 5, 6]
list1 + list2
// Java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

List<Integer> list1 = Arrays.asList(1, 2, 3);
List<Integer> list2 = Arrays.asList(4, 5, 6);
List<Integer> combined = Stream.concat(list1.stream(), list2.stream())
                               .collect(Collectors.toList());
# Shell (Bash)
list1=(1 2 3)
list2=(4 5 6)
combined=("${list1[@]}" "${list2[@]}")
echo "${combined[@]}"

These examples highlight the syntactic differences and similarities between Racket, LISP, and other languages mentioned in the talk. They provide a starting point for discussing the trade-offs between simplicity, readability, and expressiveness in different programming paradigms.

2.2.4. Break

2.2.5. The State of Racket

SPEAKER: Sam Tobin-Hochstadt
LOCATION: Kane Hall Room 220

2.2.6. Racket Town Hall

DESCRIPTION: Please come with your big questions and discussion topics.
LOCATION: Kane Hall Room 220

Please come with your big questions and discussion topics.

3. Accommodation

No official hotel has been selected, and no block(s) of rooms have been reserved. However, the University of Washington has a useful list of nearby hotel recommendations on its Getting Here page. Note that on that list of recommended hotels, Hotel Deca has been renamed to Graduate Hotel.

There will be a college football game on October 5th, so there might not be parking space for those bringing their own car. It should also be fine if you're staying somewhere in the downtown area somewhat away from the university, though public transportation will likely be crowded because of the game.

4. Code of Conduct

The proceedings of RacketCon will take place under the Racket Friendly Environment Policy.

5. Organizers

The RacketCon 2024 organizers are Jesse Alama, Matthew Flatt, Robby Findler, Siddhartha Kasivajhula, and Stephen De Gabrielle with local arrangements by Zach Tatlock and Sorawee Porncharoenwase. The organizers may be reached at con-organizers@racket-lang.org.

6. RacketCon 2024 Utilities

6.1. Generate iCal File

(defun racketcon-2024-generate-ical ()
  "Generate an iCal file for RacketCon 2024 schedule."
  (interactive)
  ;; Implementation details here
  )

6.2. Export Schedule to CSV

(defun racketcon-2024-export-csv ()
  "Export RacketCon 2024 schedule to CSV format."
  (interactive)
  ;; Implementation details here
  )

6.3. Generate HTML Schedule

(defun racketcon-2024-generate-html-schedule ()
  "Generate an HTML version of the RacketCon 2024 schedule."
  (interactive)
  ;; Implementation details here
  )

To prepare for RacketCon 2024, consider the following general recommendations:

  1. Brush up on Racket programming basics and recent developments.
  2. Explore the works of keynote speakers (Abelson, Sussman, and Kiczales).
  3. Review the SICP book and HtDP approach to programming education.
  4. Familiarize yourself with Racket's macro system and language-oriented programming concepts.
  5. Explore some of the projects mentioned in the talks, such as Frosthaven Manager and llm-lang.
  6. Prepare questions for the Racket Town Hall session.
  7. Join the Racket community forums or mailing lists to stay updated on conference-related discussions.

Author: Jason Walsh

j@wal.sh

Last Updated: 2024-09-30 16:10:33