$NAME is a dynamic reflective language. It aims to run on as many platforms as possible.
To us acceptable performance is when using $NAME does not have to incur overhead compared to not using $NAME. For example it is acceptable to have to write assembly or javascript in $NAME to get "native" performance. By platform we mean the combination of some software and hardware, such as Javascript in a browser or a process running on an operating system and x86_64 hardware. We do this by late binding and partial evaluation.
The prelude definitions.
($define! $quote ($vau (x) () x)) ($define! $quote* ($vau x () x)) ($define! id (wrap $quote)) ($define! list (wrap $quote*)) ($define! $lambda ($vau (p b) e (wrap (eval (list $vau p nil b) e)))) ($define! $true ($vau (x y) e (eval x e))) ($define! $false ($vau (x y) e (eval y e))) ($define! $or ($vau (x y) e ((eval x e) $true ((eval y e) $true $false)))) ($define! or ($lambda (x y) (x $true (y $true $false)))) ($define! $and ($vau (x y) e ((eval x e) ((eval y e) $true $false) $false))) ($define! and ($lambda (x y) (x (y $true $false) $false))) ($define! $if ($vau (c t f) e ((eval c e) (eval t e) (eval f e)))) ($define! $let/1 ($vau (s a b) e (eval b (bind s (eval a e) e)))) ($define! apply (wrap ($vau (applicative arguments) env (eval (cons (unwrap applicative) arguments) env)))) ($define! Ycombinator ($lambda (f) (($lambda (x) (x x)) ($lambda (g) (f ($lambda a (apply (g g) a))))))) ($define! $fold-right (Ycombinator ($lambda (r) ($lambda (binary-combiner initial elements) ($if (empty? elements) initial (binary-combiner (first elements) (r binary-combiner initial (rest elements))))))))
The ground environment containing the primitive combiners and constants.
first
rest
cons
nil
bind
$true
$false
cons?
same?
environment?
error?
operative?
applicative?
wrap
unwrap
lift
run
The following rules describe the reduction rules for the language kernel. Briefly explained the rules should be read as follows:
[ ]
represents reduction to be performed.
...
is used for values where the inner content of the value is not
important to the reduction rule. Typically when matching only on the kind of
value.
\name
is the value of a operative combiner. This is in contrast with the
symbol which is usually bound to the value. For example the symbol $first
is
typically bound to operative combiner \first
. Note that the literal syntax
for operative combiners is only used in the reduction rules, it is not a valid in $NAME
expressions.
x@{KIND ...}
is used to match a value by kind on the left hand side and
bind it to a variable for use in the right hand side. This may only be used on the left
hand side with values.
{ }
represents a value. If it is on the left hand side of ~
it
is a pattern match. If it is on the right is a construction.
[OPERATION ...] ~ {RESULT ...}
is a form of reduction rule where [OPERATION ...]
reduces to the value {RESULT ...}
{sym _}
a symbol.
{cons _}
a cons pair.
{nil _}
an end of list marker value.
{env _}
an environment value.
{clo _}
an operative closure.
{wrp _}
a wrapped combiner.
{err _}
an error value.
{code _ _}
a later stage expression.
\_
a primitive operative combiner.
Change \_
syntax to use the {FOO ...}
syntax, for
consistency with the rest.
Add the {tag _ _}
value kind and the relevant combiners.
RCC01: [combine {clo p s b e0} o e1] ~ [eval b [bind s env1 [bind p o env0]]]
\bind
BND001: [combine \bind {cons S@{sym _} {cons V {cons E {nil}}}} _] ~ TODO BND002: [combine \bind _ _] ~ {err}
\eval
REV01: [combine \eval {cons a {cons b {nil}}} e] ~ [eval a b] REV02: [combine \eval _ _] ~ {error}
Describe how to differentiate between errors during evaluation of a
and
errors in the combination of \eval
.
Explicit check that b
is an environment! The [eval ...]
rules
do not currently handle the case of non-environments in the second component.
\run
)We introduce code. What is the effect on combine? Is there an affect on operatives? No, because operatives do not evaluate their operands. When combining applicatives we may need to do things. Because we are evaluating arguments. When the operator evaluates to a code value we cannot do anything with the operands, because we do not know if the operator will be an operative or applicative, so we just preserve the operands in the returned code value. When the operator evaluates to an combiner we can proceed with further work. If it is an operative we can perform the combination as in Kernel. If it is an applicative we will proceed with evaluating the operand expressions to argument values. If the argument values are code values we shall once again emit a code value. If we wish to preserve evaluation ordering whe have to do let insertion here (by placing them in the returned code, as a separate part from the expression). If we do not care, we do not have to?
We add the reduction rule combine-app
to perform let insertion if the operands
evaluate to arguments that are code values. The combine-app
is only used with
operative that where wrapped. So combine-app
is always given arguments, not operands.
The combine-app
does not perform the actual combination of the combiner and
arguments. It delegates that back to combine
once combine-app
has
performed let insertion if it was necessary.
If the argument tree given to combine-app contains a code element we are in the process of generating code for a future stage. So we should not actually run the computation now, in this stage. Rather we should return a code value, which may need to include suspended let-insertions. A suspended let-insertion is a binding to be done before evaluating the expression wrapped in the code value. This is done to preserve the order of side effects.
If the argument tree has mixed code and other values things get interesting. In Lambda-Up-Down the only cases allowed are either current stage values or future stage code, the arguments must all belong to one group. No mixing. What does it mean to mix?
Actually delecate combination to combine
. Right now
combination is partially and incorrectly duplicate it into combine-app
to
make it easier to experiment.
combine
list for applicatives and applicatives.RW01: [combine {wrap comb} operands env] ~ [combine-app comb [eval-list operands env] env]
The combine
reduction rule contains the Kernel-based entries for every
operative combiner. It also contains the unwrapping case from Kernel. Finally it adds
cases for code values. Unlike previous attempts that handled code values in the
unwrap and operand evaluation step this attempt does it in the individual rules for combiners.
The reason is that not all combiners actually handle code values in the same way.
Cruically the \run
combiner accepts either two code values or one
non-code value and one code-value. Previous attempts always expected the operands
of wrapped combiners to either all be non-code values or all be code values and let
insertion was done in one place, during unwrapping and operand evaluation. The current approach
will lead to a lot of duplication of let-insertions. Perhaps it will be possible to eliminate the
duplication later and finish with a simpler rule system.
????: [combine \first {cons {code Expr0 Lets0} {nil}} Env] ~ {code [list {wrap \first} Var0] [let-ins Var0 Expr0 Lets0]} ????: [combine \first {cons {cons A _} {nil} Env} Env] ~ A
\run
The \run
combiner either takes a code value and runs it now or it
suspends itself and returns a new code value, to be executed at a later stage. Which behaviour is
is controlled by the first argument.
The \run
combiner is tricky, because it is going to be wrapped
yet needs to receive mixed code and non-code values. The initial approach was to
handle all code values passed to wrapped combiners in a dedicated rule named combine-app
. However \run
does not appear to fit into that design, since it will
receive both code and non-code values.
Specify rule for other values in the first operand to \run
. Everything
but the code value kind should cause evaluation of the second argument in this stage.
How do we do let insertion? Lifting and reflection inserts lets-of-expressions
that will be applied later during \run
. Do we also need the environment from where
we did the lifting?
[combine \run {cons {symbol _} {cons {code Expr Lets} {nil}}} Env] ~
[combine \run {cons {code _ _} {cons {code Expr Lets} {nil} }} Env] ~ TODO
\lift
[combine \lift {cons Sym@{sym _} {nil}} Env] ~ {code Sym {nil}} [combine \lift {cons {cons {code E1 L1} {code E2 L2}} {nil}} Env] ~
????: [eval-list {cons a l} env] ~ {cons [eval a env] [eval-list l env]} ????: [eval-list {nil} env] ~ {nil}
RV01: [eval s0@{sym _} {env s1 v0 e}] ~ if s0 == s1 then v0 else [eval s0 e] RV02: [eval s0@{sym _} {env}] ~ {error}
RC01: [eval {cons opr ops} env] ~ [combine [eval opr env] ops env]
\first
RF001 [combine \first {cons {cons x _} {nil}} env] ~ x RF002 [combine \first {cons {code A B} {nil}} env] ~ {code RF003 [combine \first _ env] ~ {error}
\rest
RR001 [combine \rest {cons {cons _ x} {nil}} env] ~ x RR002 [combine \rest {cons {code A B} {nil}} env] ~ TODO RR003 [combine \rest _ env] ~ {error}
\wrap
RWR01 [combine \wrap {cons c@{clo ...} {nil}} env] ~ {wrap c} RWR03 [combine \wrap _ env] ~ {error}
\unwrap
RUR01 [combine \unwrap {cons {wrap c} {nil}} env] ~ c RUR03 [combine \unwrap _ env] ~ {error}
Add rules for primitive operative combiners, \vau
and friends.
Because {clo ...}
is currently only for compound combiners, or closures.
\symbol?
RP001: [combine \symbol? {cons {sym _} {nil}} env] ~ \true RP002: [combine \symbol? {cons {cons _} {nil}} env] ~ \false RP003: [combine \symbol? {cons {nil} {nil}} env] ~ \false RP004: [combine \symbol? {cons {env _} {nil}} env] ~ \false RP005: [combine \symbol? {cons {clo _} {nil}} env] ~ \false RP006: [combine \symbol? {cons {wrp _} {nil}} env] ~ \false RP008: [combine \symbol? {cons {err _} {nil}} env] ~ \false RP008: [combine \symbol? {cons {code _ _} {nil}} env] ~ \false RP007: [combine \symbol? {cons \_ {nil}} env] ~ \false RP009: [combine \symbol? _ env] ~ {error}
\cons?
RP011: [combine \cons? {cons {sym _} {nil}} env] ~ \false RP012: [combine \cons? {cons {cons _} {nil}} env] ~ \true RP013: [combine \cons? {cons {nil} {nil}} env] ~ \false RP014: [combine \cons? {cons {env _} {nil}} env] ~ \false RP015: [combine \cons? {cons {clo _} {nil}} env] ~ \false RP016: [combine \cons? {cons {wrp _} {nil}} env] ~ \false RP017: [combine \cons? {cons {err _} {nil}} env] ~ \false RP017: [combine \cons? {cons {code _ _} {nil}} env] ~ \false RP017: [combine \cons? {cons \_ {nil}} env] ~ \false RP018: [combine \cons? _ env] ~ {error}
\null?
RP021: [combine \null? {cons {sym _} {nil}} env] ~ \false RP022: [combine \null? {cons {cons _} {nil}} env] ~ \false RP023: [combine \null? {cons {nil} {nil}} env] ~ \true RP024: [combine \null? {cons {env _} {nil}} env] ~ \false RP025: [combine \null? {cons {clo _} {nil}} env] ~ \false RP026: [combine \null? {cons {wrp _} {nil}} env] ~ \false RP027: [combine \null? {cons {err _} {nil}} env] ~ \false RP027: [combine \null? {cons {code _ _} {nil}} env] ~ \false RP027: [combine \null? {cons \_ {nil}} env] ~ \false RP028: [combine \null? _ env] ~ {error}
\env?
RP031: [combine \env? {cons {sym _} {nil}} env] ~ \false RP032: [combine \env? {cons {cons _} {nil}} env] ~ \false RP033: [combine \env? {cons {nil} {nil}} env] ~ \false RP034: [combine \env? {cons {env _} {nil}} env] ~ \true RP035: [combine \env? {cons {clo _} {nil}} env] ~ \false RP036: [combine \env? {cons {wrp _} {nil}} env] ~ \false RP037: [combine \env? {cons {err _} {nil}} env] ~ \false RP037: [combine \env? {cons {code _ _} {nil}} env] ~ \false RP037: [combine \env? {cons \_ {nil}} env] ~ \false RP038: [combine \env? _ env] ~ {error}
\op?
RP041: [combine \op? {cons {sym _} {nil}} env] ~ \false RP042: [combine \op? {cons {cons _} {nil}} env] ~ \false RP043: [combine \op? {cons {nil} {nil}} env] ~ \false RP044: [combine \op? {cons {env _} {nil}} env] ~ \false RP045: [combine \op? {cons {clo _} {nil}} env] ~ \true RP046: [combine \op? {cons {wrp _} {nil}} env] ~ \false RP047: [combine \op? {cons {err _} {nil}} env] ~ \false RP047: [combine \op? {cons {code _ _} {nil}} env] ~ \false RP047: [combine \op? {cons \_ {nil}} env] ~ \true RP048: [combine \op? _ env] ~ {error}
\ap?
RP051: [combine \ap? {cons {sym _} {nil}} env] ~ \false RP052: [combine \ap? {cons {cons _} {nil}} env] ~ \false RP053: [combine \ap? {cons {nil} {nil}} env] ~ \false RP054: [combine \ap? {cons {env _} {nil}} env] ~ \false RP055: [combine \ap? {cons {clo _} {nil}} env] ~ \false RP056: [combine \ap? {cons {wrp _} {nil}} env] ~ \true RP057: [combine \ap? {cons {err _} {nil}} env] ~ \false RP057: [combine \ap? {cons {code _} {nil}} env] ~ \false RP057: [combine \ap? {cons \_ {nil}} env] ~ \false RP058: [combine \ap? _ env] ~ {error}
\err?
RP061: [combine \err? {cons {sym _} {nil}} env] ~ \false RP062: [combine \err? {cons {cons _} {nil}} env] ~ \false RP063: [combine \err? {cons {nil} {nil}} env] ~ \false RP064: [combine \err? {cons {env _} {nil}} env] ~ \false RP065: [combine \err? {cons {clo _} {nil}} env] ~ \false RP066: [combine \err? {cons {wrp _} {nil}} env] ~ \false RP067: [combine \err? {cons {err _} {nil}} env] ~ \true RP068: [combine \err? _ env] ~ {error}
TODO: Add \eq?
(first (lift nil))
reducing to an error value
[eval (first (lift nil)) StdEnv]
symbol?
in empty environmentWe start with the expression symbol?
, which is just a symbol, in the reduction
symbol we write literal symbols as {sym symbol?}
. So our starting state is [eval {sym symbol?} {env}]
[eval {sym symbol?} {env}] RV02 ~ {error}
(symbol? $vau)
in standard environment[eval (symbol? $vau) stdenv] = [eval {cons {symbol symbol?} {cons {symbol $vau} {nil}}} stdenv] RC01 ~ [combine [\eval symbol? stdenv] {cons {symbol $vau} {nil}} stdenv] RV01 ~ [combine {wrp \symbol?} {cons {symbol $vau} {nil}} stdenv] RW01 ~ [combine \symbol? [eval-list {cons {symbol $vau} {nil}} stdenv] stdenv] ... ; TODO eval-list RP007 ~ [combine \symbol? {cons \vau {nil}} stdenv]
EXPRESSION ::= LIST | SYMBOL | NUMBER | TEXT LIST ::= '(' OWSPACE ')' | '(' OWSPACE ELEMENTS OWSPACE ')' ELEMENTS ::= EXPRESSION WSPACE ELEMENTS | EXPRESSION SYMBOL ::= SCHAR SCHARS NUMBER ::= DIGIT SCHARS TEXT ::= '"' CHARS '"' | '#' SCHARS '"' CHARS '"' SCHARS SCHAR ::= LOWERCASE | UPPERCASE | SPUNCTUATION CHARS ::= CHAR | CHAR CHARS CHAR ::= UPPERCASE LOWERCASE SPUNCTUATION SPUNCTUATION ::= '!' | '"' | '$' | '%' | '&' | "'" | '*' | ',' | '-' | '.' | '/' | ':' | ';' | '<' | '=' | '>' | '?' | '@' | '[' | '\' | ']' | '^' | '_' | '{' | '|' | '}' | '~' UPPERCASE ::= 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z' LOWERCASE ::= 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z'
The TEXT grammar is required to accurately represent data that does not conform to the syntax of $NAME. Such as data that contains unbalanced parenthesises, uses separators other than parenthesis and whitespace, etc.
In the future full Unicode will be supported. The changes to the grammar is expected to be minimal. Unicode specifies additional whitespace and those will be added as separators. Other Unicode graphme clusters will be added to the SYMBOL and TEXT rules.
Example Pink session using the Scheme implementation and augmented with a simple repl.
(lift 42) (code 42) (quote a) a (lift (quote a)) (code a) (lift (quote 42)) (code 42) (lift (quote (a))) *exception* (lift (lambda f x x)) (code (var 0)) stBlock: ((lambda (var 1))) stFresh: 1 stFun: ((0 () (var 1))) (lift ((lambda f x x) 42)) (code 42) stBlock: () stFresh: 0 stFun: () (run 0 (lift 42)) 42 stblock: () stFresh: 0 stFun: () (run (lift 1) (lift 42)) (code (var 0)) stBlock: ((run 1 42)) stFresh: 1 stFun: () (lift (lift 1)) (code (var 0)) stBlock: ((lift 1)) stFresh: 1 stFun: () (lift (cons (lift 1) (lift 2))) (code (var 0)) stBlock: ((cons 1 2)) stFresh: 1 stFun: () (lift (lambda f x (car x))) (code (var 0)) stBlock: ((lambda (let (car (var 1)) (var 2)))) stFresh: 1 stFun: ((0 () (car (var 1)))) (lift (lambda f x (car f))) (code (var 0)) stBlock: ((lambda (let (car (var 0)) (var 2)))) stFresh: 1 stFun: ((0 () (car (var 0))))