Defining our own Words

It’s rare these days that we’ll write a program as a single huge flat file.

As programmers we’re on the look out for ways to find code we can reuse.

Xlerb/Forth code is typically ‘point free’, meaning we tend not to reference, or really need to reference explictly named variables (since all of the state is on the stack). As such Xlerb has no real way to name values or variables, but rather we can give a name to a train of words:

Suppose we want to square the top of the stack. Since $x^2 := x \cdot x$, we can

  1. Duplicate the top of the stack
  2. Multiply the top two numbers:
xlerb[0]> 6
xlerb[1]> dup .s
6
6
xlerb[2]> *
36

The great thing about it being pointfree is that it’s super easy to just extract common logic out into reusable words. We do this with 2 special words: : (begin definition), and ; (end definition). It’s used like : <name of new word> <body of word> ; :

In this case we’ve worked out that squaring the top of the stack is just duplicating it and muliplying:

: square dup * ;

Now anywhere we write square, it gets essentially substiuted for dup *, and we get our result.

Aside: allowed symbols

Xlerb is surprisingly permissive when it comes to what characters can be used in the words we define, basically anything is valid! We can use this (…judiciously) to say define our own operators or aliases:

: ^2 square ;

Or perhaps bitwise things:

: +=+ [ Bitwise bxor 2] elixir ;

Matching the Stack

It’s very common in BEAM languages to break up a function into different function heads, depending on what the passed-in value is.

This lets us, say, cleanly separate the base case from the inductive case in a recursive function:

def factorial(0), do: 1
def factorial(x), do: factorial(x - 1) * x

Xlerb is no different! Unlike in Forth, : has a special power, where if we put values before the name of the word, it lets us match out on different top-of-stack values:

: 0 factorial drop 1 ;
: _ factorial dup 1 - * factorial ;

We can read it like:

  • If 0 is on the top of the stack when I call factorial, then:
    1. drop the top of the stack
    2. push 1
  • If anything else (_) is, then:
    • duplicate it and subtract 1
    • multiply the top 2 values of the stack
    • then call factorial

Although we typically prefer to keep our words point-free, we can use this to make some neat assertions about the stack.

For example if we have a word which concatenates two strings, we need 2 strings to concatenate!

: _ _ concat [ String join 2 ] elixir ; 

Ignore the body for the moment (we’ll get to that later!) - but note the two leading “don’t care”s. Now if the stack has less than two elements, Xlerb doesn’t even try to call the word, since we know the match won’t work.

I discovered this is actually kind of nice for doing something like [railway-oriented programming], suppose we’re getting numbers from the database:

\ imagine this pushes either :ok value, or :error error onto the stack
: get-number < ... > ;

\ take the number and push the result; discard the :error tag

: :ok    _ handle-response 2 * ;
: :error _ handle-response dup write ;

And if you’re of the Rust persuasion you might find this sort of thing interesting:

: :ok    _ unwrap! swap drop ;
: :error _ unwrap! raise ;