Playing With Picolisp (Part 1)
I want to learn new languages. But new as in "new to me", not new as in "created last week". So I decided to play with the grandaddy of all cool languages, LISP. Created in 1958, it's even older than I, which is good because it's experienced.
One "problem" with LISP is that there are a million LISPs. You can use Scheme or Common Lisp, or Emacs' Lisp, or a bazillion others. I wanted something simple so it was supposed to be Scheme... but a few days ago I ran into something called Picolisp and it sounded so cool.
- Interfaces with native libraries
- Purely interpreted, but fast
- REPL
- Persistent distributed DB builtin (WAT?!)
So, why not.
Well, one reason why not is that there is, as far as I can see, no tutorial anywhere for picolisp the language. There is a "tutorial" but it assumes you know picolisp!
This is not a Lisp tutorial, as it assumes some basic knowledge of programming, Lisp, and even PicoLisp.
-- The picolisp "tutorial"
It does however have a good reference for all the functions available in picolisp by default, which is very handy, and I often groked what something did by using it's examples.
However, picolispers, it's difficult to start with your language if you don't explain your language. So, because I am stubborn, I decided to try it anyway. However, without a decent tutorial, expect this to be less of a "let's implement something using picolisp" and more of a "adventures of a pythonista being frustrated by another language".
But hey, it may work as a tutorial up to a point! Let's go learn enough LISP to be dangerous.
Let's try the REPL:
$ pil
: 10
-> 10
: "hello world"
-> "hello world"
Good!
One bad thing: the REPL has no history and line edition is basic. No problem!
Just use good old rlwrap and you are good to go (Note: it turns out it has editing and history, but you need to start it as pil +
).
So, having already exhausted my knowledge of LISP by typing values into the interpreter, I decided to just learn some other variant of LISP and see what happened. I found a well regarded book called Practical Common Lisp ... main idea is to learn generic syntax, fill in the blanks, and that should do it. I have been programming for 35 years, surely I can do things the wrong way and get away with it, right?
So... it did not go so well. This is like, the second example in the book:
CL-USER> (format t "hello, world")
hello, world
NIL
And here is what happens on picolisp:
: (format t "Hello, world")
!? (format t "Hello, world")
"Hello, world" -- Small number expected
Hmmm... ok, let's try a small number then?
? (format t 2)
-> "2671.89"
That is ... unexpected. So, I am clearly missing something here. To the google!
In picolisp, format
is used only to format numbers:
: (format 1234567890 2 "." ",")
-> "12,345,678.90"
What I wanted is maybe prin
:
? (prin "hello world")
hello world-> "hello world"
There you can see the side effect (the first hello world
) and then the way picolisp shows the result of the function: -> "hello world"
Ok, let's ignore the weirdness and go on.
Creating a function
? (de hello () (print "hello world"))
-> hello
? (hello)
"hello world"-> "hello world"
Hey, nice. So, de
means what comes next is function name and a list of args,
then the function "body". Sure, as expected, the syntax is a bit alien, but I
can work with this.
Of course the CL book says de
should be defun
and picolisp has no idea
what defun
is but that's ok, I can buffer that.
Loading into the REPL things defined in a separate .lisp
file?
: (load "hello.lisp")
-> hello
: (hello)
"hello world"-> "hello world"
Good.
Data structures
It turns out picolisp has exactly 3:
- numbers (not even floating point numbers, just ints)
- strings (in fact it doesn't have strings, although it looks like it does. It's complicated)
- lists
So, lists?
: (list 1 2 3)
-> (1 2 3)
: (list 1 "foo" 3)
-> (1 "foo" 3)
In fact, picolisp treats simple lists as lists as long as the 1st element is a number:
: (1 2 3)
-> (1 2 3)
: ("foo" 2 3)
!? ("foo" 2 3)
"foo" -- Undefined
Symbols
: (set 'foo '1234)
-> 1234
: foo
-> 1234
That says "set the value of the symbol foo
to 1234
. It has a quote in
'foo
, because we don't want to evaluate foo
. If we did, then we would
assign 1234
to the result of evaluating foo
, which is surely not what you
want?
So, if you want something to be what you typed, use the quote, just in case.
Since you 99.999% of the time don't want to evaluate the name of the variable
you are setting, you can use setq
(set quoted) which lets you avoid that
quote:
: (setq foo '1234)
-> 1234
: foo
-> 1234
In these specific examples, the quote is not needed before '1234
, since 1234
evaluates to 1234
but I guess I better make it a habit.
You can assign more than once at a time:
: (setq foo '1234 bar '5678)
-> 5678
: (prin foo bar)
12345678-> 5678
So, I can then use the foo
symbol:
: (+ 3 foo)
-> 1237
Yes, Lisp evaluates lists, so it makes sense for it to use prefix notation. Deal with it like a developer.
What can we do with lists?
That could very well be the name of all LISP books, since 99% of programming
lisp is doing things to lists, with lists and about lists. But let's focus.
Here we will find the second thing people who don't know LISP (such as myself)
know about LISP: that there are weird things called car
, cdr
, and cons
.
Yes, the first thing those who don't know LISP know about LISP is "OMG parenthesis".
So, what are car
, cdr
, and cons
? Mostly the problem with them is their
name. If car
was called first
everyone would know what it does. But hey,
it was the 50s and people did not give a damn about clarity. I blame alcohol.
: (car (1 2 3))
-> 1
So, car
is a function that takes a list and returns the first element in it.
And if car
is first
, then cdr
is rest
:
: (cdr (1 2 3))
-> (2 3)
And cons
means glue
:
: (cons 1 '(2 3))
-> (1 2 3)
In other variants of LISP, this doesn't work, but in picolisp it does:
: (cons 1)
-> (1)
You can use lists as arguments of functions (of course), so you can do the expected things:
? (length '(a b c)) # How long is that list?
-> 3
? (length "foo") # How long is "foo"?
-> 3
: (index 1 (3 2 1)) # Where in the list is the 1?
-> 3
And how do you access the "nth" element of a list, like l[3]
in python?
? (nth 2 (1 2 3 4))
-> 2
What nth
does is repeatedly call car
so it turns out lists are 1-indexed :-)
There are a number of other functions to access lists:
caar
cdar
caaar
c-a-few-a's-and-d's-r
What they do is call car
and cdr
repeatedly. So (cadar 'foo)
means (car
(cdr (car 'foo))
:
? (setq foo '(1 2 3 4 5))
-> (1 2 3 4 5)
? (cadar 'foo)
-> 2
Why is it 2
?
? (setq foo '(1 2 3 4 5))
-> (1 2 3 4 5)
? (car (cdr (car 'foo)))
-> 2
And how do you alter a list?
? (setq foo '(1 2 3 4 5))
-> (1 2 3 4 5)
: (insert 2 foo "x") # Insert "x" in position 2 in list foo
-> (1 "x" 2 3 4 5)
: (remove 2 foo) # Removes element in position 2 in list foo
-> (1 3 4 5)
: (place 3 foo "x") # Place "x" as element 3 in foo
-> (1 2 "x" 4 5)
: (setq foo '(1 2 3 4 5 1 2 3 4))
-> (1 2 3 4 5 1 2 3 4)
: (delete 3 foo)
-> (1 2 4 5 1 2 3 4) # Delete the 3s from foo
: (setq foo '(1 2 3 4 5 4 3 2 1))
-> (1 2 3 4 5 4 3 2 1)
: (member 5 foo) # The tail that starts when it finds a 5
-> (5 4 3 2 1)
Mind you that is not actually altering the list, it's just returning another
list which is foo
modified.
There are destructive list operations in other LISP variants, such as
setcar
and setcdr
, but I have not found them in picolisp.
Logic
How can I make my program do something in one situation and something else in another?
Now LISP is starting to be predictable, which is good. I expect there is a
function if
that takes 3 arguments, one is evaluated, and depending on its
value, if it's "true" the second will evaluate, or else, the third will. And
voilá, that is so:
: (setq x 4)
-> 4
: (if (> x 0) (print "Greater") (print "Smaller"))
"Greater"-> "Greater"
: (setq x -3)
-> -3
: (if (> x 0) (print "Greater") (print "Smaller"))
"Smaller"-> "Smaller"
There are others, but if
is enough for now.
Loops
There is an easy do
:
: (do 4 (printsp 'OK)) # printsp prints a value and a space.
OK OK OK OK-> OK
And a more complicated do
, which I really did not understand:
: (do 4 (printsp 'OK) (T (= 3 3) (printsp 'done))) # WAT?
OK done -> done
More interesting for python people is for
which iterates over a list:
: (for X (1 2 3 4 5 4 3 2 1) (printsp X))
1 2 3 4 5 4 3 2 1 -> 1
I think this is about enough for a first taste of LISP and microlisp. Hopefully I can continue learning it next saturday!
To get the nth element of a list we actually use 'get' and not nth. try (help 'nth T) & (help 'get T) in debug mode for info.
(get (6 5 3 2) 2) # returns the 2nd list element 5
(nth 2 (5 6 7 8 2)) # returns 2 itself
(nth (range 1 9) 5) #return list starting from 5th element
(nth '(a b c d e) 2) # return list from 2nd element