3 Envlang @ racketfest
3.1 Use cases for macros
3.1.1 Environment manipulation
Adding bindings to the environment, getting bindings from the environment:
(let (var val) body) ;; env += {var = val} (define-struct name (field ...)) ;; env += {"$name-$field" = accessor-fn} … (aif condition if-true if-false) ;; env += {it = condition} (match v [(cons a b) body]) ;; env += {a = (car v)} {b = (cdr v)}
3.1.2 Control flow
Changing the order of execution:
(if condition if-true if-false) ;; can be expressed as: (force (if condition (λ () if-true) (λ () if-false))) (match v ([null if-null] [(cons a b) if-cons])) ;; can be expressed as: (force (if (null? v) (λ () if-null) (λ () (let ([a (car v)] [b (cdr v)]) if-cons)))) (for/list ([x (in-list l)]) body) ;; can be expressed as (map (λ (x) body) l)
3.1.3 Syntactic sugar
(1 + 2 * 3) ;; infix (let x = 3 in (+ x 1)) (for/list x in (list 1 2 3) (+ x 1)) (let x:int = 3 in (+ x 1))
3.1.4 Optimisations
Optimisations are semantics-preserving compile-time transformations of the program.
pre-calculated hash table loop unrolling …
3.1.5 Code analysis
Tracking and propagating annotations on the code:
typed/racket source locations tooltips
3.2 Overview of the semantics
(f arg ...) ;; is sugar for: (@ f env (⧵ (env) arg) ...)
x ;; is sugar for: (hash-ref env x)
3.3 First-class solutions
Adding bindings to the environment, getting bindings from the environment:
3.3.1 Environment manipulation
User-defined let:
(⧵ outer-env (var val body) ;; evaluate body in outer env + var=val (force (hash-set outer-env ;; var name (promise->string var) ;; evaluate val in outer env (force outer-env val)) body))
User-defined let with different order for the arguments:
(return (+ x 1) where x = 123)
(⧵ outer-env (body kw-where var kw-= val) (assert (string=? (promise->string kw-where) "where")) (assert (string=? (promise->string kw-=) "=")) (@ my-let outer-env var val body))
3.3.2 Control flow
(⧵ outer-env (condition if-true if-false) (force env ((force env condition) if-true if-false)))
3.3.3 Syntactic sugar
3.3.3.1 Identifiers with different meanings
Bindings in the environment point to a table associating meanings to values. See polysemy.
x ;; becomes sugar for: (hash-ref (hash-ref env x) "variable")
in keyword used in different contexts:
(let x = 3 in (+ x 1))
(⧵ outer-env (var kw-= val kw-in body) (assert (equal? (hash-ref (hash-ref env (promise->string kw-=)) "let-in keyword") let-in-=)) (assert (equal? (hash-ref (hash-ref env (promise->string kw-in)) "let-in keyword") let-in-in)) (@ my-let outer-env var val body))
(for/list x in (list 1 2 3) (+ x 1))
(⧵ outer-env (var kw-in lst body) (assert (equal? (hash-ref (hash-ref env (promise->string kw-in)) "for keyword") for-in)) (@ map outer-env var val body))
It’s easy to rename just the "let-in keyword" part without renaming the "for keyword" part.
3.3.3.2 Extra parentheses
(let [x 2] (+ x 1))
(⧵ outer-env (binding body) (let varval (force (hash-set "#%app" cons) binding) (@ my-let outer-env (car varval) (cadr varval) body)))
3.3.3.3 Infix
(1 + 2 * 3)
Needs external support in the language (or overloading #%app). WIP prototype using mixfix on repl.it and github.
3.3.3.4 Manipulating identifiers
(let x:int = 3 in (+ x 1))
(⧵ outer-env (var kw-= val kw-in body) (let ([forced-val (force outer-env val)]) (when (ends-with (promise->string var) ":int") (assert int? forced-val)) (@ my-let outer-env var val body)))
3.4 Compile-time transformations
Wrap parts to be evaluated at compile-time, the wrapper acts like unquote where the whole program is in a quasiquote.
(run-time (let ([x (compile-time (+ 1 2 3))]) (* x x)))
`(let ([x ,(+ 1 2 3)]) (* x x))
Semantics-preserving: removing the run-time and compile-time markers must give an equivalent program.
3.5 Code analysis
3.5.1 Type checking
These environment manipulations can be modeled with row types:
(λ (x : (struct [foo : int] [bar : string] . ρ)) : (struct [foo : int] [quux : int] . ρ) (x without .bar with .quux = (+ x.foo (string->int x.bar))))
3.5.2 Implemented within the language
… to be explored?
3.6 Example use
(my-let x 3 (let-paren [x 3] (let-postfix x:int = 3 in (return (for/list z in (compile-time (list 1 2 3)) (+ z y)) where y = (+ 1 x)))))
(let* ([my-let «my-let»] [return «return+where»] [my-if «my-if»] [let-paren «let-paren»] [let-postfix «postfix-ids»]) «program»)
;;<env+program>