PicoLisp is built to be simple from the bottom-up. The author first designed a virtual machine architecture. This allows for simplicity to be baked into the language. To emphasize simplicity, PicoLisp only has one single data structure, the cell. All higher level data types, numbers, symbols, and lists, are built from cells.
We’ll start with a simple game - represented with a cell - and use some numbers and symbols. Perhaps we can use lists when writing messages to the player. The game is… actually its a number guessing AI. You’ll think of a number, and by answering simple questions, the program will either guess your number, or know you cheated!
How can we represent this game with a cell? Well, the boundaries for this game are two numbers your number is between. We can create a cell to represent those boundaries and use a symbol to point to it with a name like this:
(set '*Bounds '(1 . 100))
Actually, I just realized we’re already using lists, this is Lisp
after all! List processing is what it is all about. Everything in
the parenthesis above is a list. The PicoLisp interpreter knows how
to read lists like that as a program. We can read it too. That list
starts with set
which is a PicoLisp function that lets you set
the value or val
of a symbol. So, following set
is the
symbol whose value you want to set. Now, there’s something special
here… the '
mark. This is a shorthand for the quote
function
which marks something as a piece of data, rather than something the
PicoLisp reader should evaluate. Without that quote, *Bounds
would
evaluate to NIL
and NIL
is a protected symbol:
: (set *Foo 1)
!? (set *Foo 1)
NIL -- Protected symbol
Bad things would happen if we could set the value of NIL
. So,
we’re setting the value of the symbol *Bounds to another quoted
list. This is our cell. The dot in between 1 and 100 is one way
to link these two numbers into a cell. We put 1 in the car
of
the cell and 100 in the cdr
(pronounced “could’r”) by using
that dot to indicate the cell. We could also construct a cell
by using the cons
function:
: (cons 1 100)
-> (1 . 100)
So now, if you type *Bounds
in the repl, it will return its
value, (1 . 100)
. There. That is all the data we need to represent
our guessing game. But we need to be able to access the lower bound
and the upper bound individually. In order to do that we will use
the car
and cdr
functions:
: (car *Bounds)
-> 1
: (cdr *Bounds)
-> 100
The names of those functions are tied to the virtual machine, and mean
“contents of the address register” and “contents of the decrement register”.
What matters to us though, is how they access parts of a cell. Since everything
in PicoLisp is made of cells, you can expect to see car
and cdr
(and their
relatives) quite often.
Since we will be using both those values throughout the guessing game program,
lets write some functions that make it easier to use them and know what they
mean. I’ll write a function called small
here, and I’ll leave it to you to
implement big
:
(de small ()
(car *Bounds) )
PicoLisp defines new functions with de
. You give it a name, its arguments
if any, and then the body of the function. What we get back is the name
as a new symbol whose VAL
is the rest of the arguments.
(set '*Bounds '(1 . 100))
(de small ()
(car *Bounds) )
(de big ()
(cdr *Bounds) )
(de guess-my-number ()
(if (> (small) (big))
(cheater)
(>> 1 (+ (small) (big))) ) )
(de smaller ()
(set '*Bounds (cons (small) (dec (guess-my-number))))
(guess-my-number) )
(de bigger ()
(set '*Bounds (cons (inc (guess-my-number)) (big)))
(guess-my-number) )
(de cheater ()
(prinl '(You " " cheated!))
(bye) )
(de go ()
(prinl (text "Think of a number between @1 and @2!" (small) (big)))
(wait 2000)
(prinl "Here we go! My first guess is... ")
(guess-my-number) )
(go)