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:
| (⧵ 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
| (⧵ 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
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))))) |