Chapter 8
Under the hood
You now know most of the language. This chapter shows how little of it is built in, and how the rest is written in Ether itself.
Fifty-one words
Only these 51 words are part of the interpreter. They are the operations
of the objects and of the process itself, and integer, which reads
a rom of decimal digits as a number: Ether has no numerals, so every number
in a program starts as text.
| for | words |
|---|---|
| the instruction stream | evade call |
| the values stack | dup del swap swap2 |
| blocks | ram rom size read write |
| numbers | + - * / less-than integer |
| booleans | true false |
| lists | stack tuple push pop |
| tables | tree array get set has remove keys |
| any object | type equals serial clone freeze dirty warm match |
| unique objects | function token seal unseal pointer |
| processes | process spawn queue send receive poll throw |
| output | debug |
Everything else, including {, ", if,
while, trust, = and map, is
defined in Ether from these. The interpreter carries native copies of those
definitions so that programs run fast, and every example on this site can run
either way: switch a block from native words to written in Ether
and it runs on the 51 words plus the Ether definitions instead. The results are
the same; the step count changes, and so do the serial numbers
inspect shows, because they count every object the process has
made, the definitions' own included.
[ # 1 # 2 # 3 ] { dup * } map debug
( 1 4 9 ) values: ( )
A decision without if
A table can use any object as a key, including true and
false. So a choice between two lists is a lookup: put the lists in
a table under true and false, look up the boolean, and
call what you find. That is all either is:
{ array
swap false pair swap set
swap true pair swap set
get call } => my-either
# 3 # 5 less-than { " yes " } { " no " } my-either debug
no values: ( )
Reading ahead: evade
Words like \, = and => use the word
that comes after them. They are built on evade, which takes
a list from the values stack, pops the next instruction, puts the list on the
instructions stack, and pushes the instruction it popped. With an empty list
that is exactly \:
{ tuple evade } => quote
quote debug
values: ( "debug" )
With a list that does something, it is a word that reads a name. Here is
=>, written from evade and the macro table:
{ { swap tuple push push macros set del } evade } => define
{ dup * } define square
# 6 square debug
36 values: ( )
# is built the same way: integer applied to the
next word.
{ { integer } evade } => number
number 42 debug
42 values: ( )
The real definitions are in the standard bootstrap, which builds the readers, scopes, functions and loops from the 51 words, and in the standard library on top of it. Writing your own bootstrap, a version of those words in your own style, is the best way to see the whole language at once.
Exercise 8.1
Without using if, unless or either,
define my-if, which takes a boolean and a list and runs the list
only when the boolean is true. Use it to print yes
when 2 is less than 3.
A correct answer prints:
yes values: ( )
One answer
{ array swap true pair swap set tuple false pair swap set get call } => my-if
# 3 # 2 less-than { " yes " debug } my-if
yes values: ( )
Exercise 8.2
Define twice, which takes a number and a list and runs the list
on the number two times. Use it to double 5 twice and print the result.
A correct answer prints:
20 values: ( )
One answer
{ dup swap2 swap call swap call } => twice
# 5 { # 2 * } twice debug
20 values: ( )
Exercise 8.3
Using evade, define say, which prints the word that
follows it. say hello should print hello.
A correct answer prints:
hello values: ( )
One answer
{ { debug } evade } => say
say hello
hello values: ( )