On this page:
3.1 Use cases for macros
3.1.1 Environment manipulation
«use-case-bindings»
3.1.2 Control flow
«use-case-order»
3.1.3 Syntactic sugar
«use-case-syntactic-sugar»
3.1.4 Optimisations
«use-case-optimisations»
3.1.5 Code analysis
«use-case-annotations»
3.2 Overview of the semantics
«promise»
«variables»1
3.3 First-class solutions
3.3.1 Environment manipulation
«my-let»
«use-return+where»
«return+where»
3.3.2 Control flow
«my-if»
3.3.3 Syntactic sugar
3.3.3.1 Identifiers with different meanings
«variables»2
«let-in-usage»
«let-in»
«for-in-usage»
«for-in»
3.3.3.2 Extra parentheses
«use-let-paren»
«let-paren»
3.3.3.3 Infix
«example-infix»
3.3.3.4 Manipulating identifiers
«example-postfix-ids»
«postfix-ids»
3.4 Compile-time transformations
«compile-time-proposal»
«compile-time-proposal-equivalent»
3.5 Code analysis
3.5.1 Type checking
«row-type-example»
3.5.2 Implemented within the language
3.6 Example use
«program»
«env+program»
«*»

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>