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))) |
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))))) |