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.

forwords
the instruction streamevade call
the values stackdup del swap swap2
blocksram rom size read write
numbers+ - * / less-than integer
booleanstrue false
listsstack tuple push pop
tablestree array get set has remove keys
any objecttype equals serial clone freeze dirty warm match
unique objectsfunction token seal unseal pointer
processesprocess spawn queue send receive poll throw
outputdebug

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