Ir al contenido principal

Ralsina.Me — El sitio web de Roberto Alsina

Playing With Picolisp (Part 1)

I want to learn new lan­guages. But new as in "new to me", not new as in "cre­at­ed last week". So I de­cid­ed to play with the grandad­dy of all cool lan­guages, LISP. Cre­at­ed in 1958, it's even old­er than I, which is good be­cause it's ex­pe­ri­enced.

One "prob­lem" with LISP is that there are a mil­lion LISP­s. You can use Scheme or Com­mon Lisp, or Emac­s' Lisp, or a bazil­lion oth­er­s. I want­ed some­thing sim­ple so it was sup­posed to be Scheme... but a few days ago I ran in­to some­thing called Pi­col­isp and it sound­ed so cool.

  • In­ter­faces with na­tive li­braries
  • Pure­ly in­ter­pret­ed, but fast
  • RE­PL
  • Per­sis­tent dis­trib­uted DB builtin (WAT?!)

So, why not.

Well, one rea­son why not is that there is, as far as I can see, no tu­to­ri­al any­where for pi­col­isp the lan­guage. There is a "tu­to­ri­al" but it as­sumes you know pi­col­isp!

This is not a Lisp tu­to­ri­al, as it as­sumes some ba­sic knowl­edge of pro­gram­ming, Lisp, and even Pi­coL­isp.

-- The pi­col­isp "tu­to­ri­al"

It does how­ev­er have a good ref­er­ence for all the func­tions avail­able in pi­col­isp by de­fault, which is very handy, and I of­ten groked what some­thing did by us­ing it's ex­am­ples.

How­ev­er, pi­col­isper­s, it's dif­fi­cult to start with your lan­guage if you don't ex­plain your lan­guage. So, be­cause I am stub­born, I de­cid­ed to try it any­way. How­ev­er, with­out a de­cent tu­to­ri­al, ex­pect this to be less of a "let's im­ple­ment some­thing us­ing pi­col­isp" and more of a "ad­ven­tures of a python­ista be­ing frus­trat­ed by an­oth­er lan­guage".

But hey, it may work as a tu­to­ri­al up to a point! Let's go learn enough LISP to be dan­ger­ous.

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, hav­ing al­ready ex­haust­ed my knowl­edge of LISP by typ­ing val­ues in­to the in­ter­preter, I de­cid­ed to just learn some oth­er vari­ant of LISP and see what hap­pened. I found a well re­gard­ed book called Prac­ti­cal Com­mon Lisp ... main idea is to learn gener­ic syn­tax, fill in the blanks, and that should do it. I have been pro­gram­ming for 35 years, sure­ly I can do things the wrong way and get away with it, right?

So... it did not go so well. This is like, the sec­ond ex­am­ple in the book:

CL-USER> (format t "hello, world")
hello, world
NIL

And here is what hap­pens on pi­col­isp:

: (format t "Hello, world")
!? (format t "Hello, world")
"Hello, world" -- Small number expected

Hm­m­m... ok, let's try a small num­ber 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 ig­nore the weird­ness 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 pi­col­isp has ex­act­ly 3:

  • num­bers (not even float­ing point num­ber­s, just ints)
  • strings (in fact it does­n't have strings, al­though it looks like it does. It's com­pli­cat­ed)
  • lists

So, list­s?

: (list 1 2 3)
-> (1 2 3)
: (list 1 "foo" 3)
-> (1 "foo" 3)

In fac­t, pi­col­isp treats sim­ple lists as lists as long as the 1st el­e­ment is a num­ber:

: (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 as­sign 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 eval­u­ates list­s, so it makes sense for it to use pre­fix no­ta­tion. Deal with it like a de­vel­op­er.

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 paren­the­sis".

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 oth­er vari­ants of LISP, this does­n't work, but in pi­col­isp it does:

: (cons 1)
-> (1)

You can use lists as ar­gu­ments of func­tions (of course), so you can do the ex­pect­ed 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 al­ter 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 pro­gram do some­thing in one sit­u­a­tion and some­thing else in an­oth­er?

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 mi­crolisp. Hope­ful­ly I can con­tin­ue learn­ing it next sat­ur­day!

PicoLizard / 2018-08-31 04:27:

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


Contents © 2000-2023 Roberto Alsina