PicoLisp - The Basics

A work in progress...
NOTE: This is currently a blatant rip-off of the Arc Lisp Tutorial. It will continue to evolve in it's own direction as it develops.

This is a brief tutorial on PicoLisp. It's intended for readers with little programming experience and no Lisp experience.

Expressions

PicoLisp programs consist of expressions. The simplest expressions are things like numbers, which evaluate to themselves.

  : 25
  -> 25
  : -1432
  -> -1432

Several expressions enclosed within parentheses are also an expression. These are called lists. When a list is evaluated, the elements are evaluated from left to right, and the value of the first (presumably a function) is passed the values of the rest. Whatever it returns is returned as the value of the expression.

  : (+ 1 2)
  -> 3

Here's what just happened. First +, 1, and 2 were evaluated, returning the plus function, 1, and 2 respectively. 1 and 2 were then passed to the plus function, which returned 3, which was returned as the value of the whole expression.

Since expression and evaluation are both defined recursively, programs can be as complex as you want:

  : (+ (+ 1 2) (+ 3 (+ 4 5)))
  -> 15

Putting the + before the numbers looks odd when you're used to writing "1 + 2," but it has the advantage that + can now take any number of arguments, not just two:

  : (+)
  -> NIL
  : (+ 1)
  -> 1
  : (+ 1 2)
  -> 3
  : (+ 1 2 3)
  -> 6

This turns out to be a convenient property, and I promise it won't seem weird by the end of the tutorial.

Symbols

Lisp dialects like PicoLisp have another important data type: symbols. We've already seen one: + is a symbol. Symbols don't evaluate to themselves the way numbers and strings do. They return whatever value they've been assigned.

If we give the symbol 'foo' the value 13, it will return 13 when evaluated:

  : (set 'foo 13)
  -> 13
  : foo
  -> 13



Quoting Symbols

Did you notice the single quote character in front of foo in the last expression? You can turn off evaluation by putting a single quote character before an expression. So 'foo returns the symbol foo.

  : 'foo
  -> foo



We needed the symbol itself, so we could assign a value to it. Otherwise set would have evaluated foo, found it didn't have a value (NIL), and would have tried to set the value of nothing (NIL) to something (13).

A note on error messages

Let's try it with the symbol boo, to see what happens.

  : (set boo 14)            # Ok PicoLisp, set nothing to something.
  !? (set boo 14)           # Ahh, wut? o.O
  NIL -- Protected symbol   # Sorry, NIL is special.
  ?                         # So...

PicoLisp doesn't like it. And now you know what it looks like when PicoLisp has a problem with something you've typed. Not too scary. PicoLisp tries to offer helpful (though often cryptic) hints at what went wrong. Often it will just be a little typo, like forgetting to quote an expression. For now, just hit 'enter' to get back to the normal prompt.

  ?         # Hit enter on blank line...
  :         # Good to go!



Quoting Lists

If you quote a list, you get back the list itself.

  : (+ 1 2)
  ->  3
  : '(+ 1 2)
  -> (+ 1 2)

The first expression returns the number 3. The second, because it was quoted, returns a list consisting of the symbol + and the numbers 1 and 2.

Lists are fun because you can do lots of stuff with them. You can build lists with cons, which returns a list with a new element on the front:

  : (cons 'f '(a b))
  -> (f a b)

It doesn't change the original list:

  : (setq x '(a b))
  -> (a b)
  : (cons 'f x)
  -> (f a b)
  : x
  -> (a b)

Did you notice the function setq instead of set? And also the lack of a quote on the symbol x in that expression? Can you guess what 'setq' stands for? Yep, "set quote". It will take care of quoting that first symbol, so you don't have to. Pretty nifty.

The empty list is represented by the symbol NIL, which is defined to evaluate to itself. So to make a list of one element you say:

  : (cons 'a NIL)
  -> (a)

You can take lists apart with car and cdr, which return the first element and everything but the first element respectively:

  : (car '(a b c))
  -> a
  : (cdr '(a b c))
  -> (b c)

To create a list with many elements use list, which does a series of conses:

  : (list 'a 1 "foo" '(b))
  -> (a 1 "foo" (b))
  : (cons 'a (cons 1 (cons "foo" (cons '(b) NIL))))
  -> (a 1 "foo" (b))

Notice that lists can contain elements of any type.

There are lots of things that we can do to lists! We can reverse them,

  : (reverse '(p a n))
  -> (n a p)

rotate them,

  : (rot (1 2 3 4))
  -> (4 1 2 3)

Hey! Aren't we supposed to 'quote' lists that we use as data? Shouldn't the above line have been (rot '(1 2 3 4))? Technically, yes. But PicoLisp will quote any list that begins with a number for you. This saves you from quoting a bunch of simple lists when you're playing around at the REPL like this.

We can turn two lists into one,

  : (append (1 2 3) (4 5 6))
  -> (1 2 3 4 5 6)

replace certain elements,

  : (replace '(a b b a) 'a 'A)
  -> (A b b A)
  : (replace '(a b b a) 'b 'B)
  -> (a B B a)

add stuff to differnt places,

  : (insert 3 '(a b c d e) 777)
  -> (a b 777 d c)
  : (insert 1 '(a b c d e) 777)
  -> (777 a b c d e)

delete things,

  : (delete 2 (1 2 3))
  -> (1 3)

and grab elements from here and there.

  : (head 3 '(a b c d e f))
  -> (a b c)
  : (tail 2 (1 2 3 4))
  -> (3 4)



Lists are useful in exploratory programming (learning) because they're so flexible. You don't have to commit to exactly what a list represents in advance.

The Equivalence of Code and Data

The most exciting thing lists can represent is code. The lists you build with cons are the same things programs are made out of. See?

  : (eval (cons '+ (1 2)))
  -> 6

Congratulations! You've just discovered the 'Equivalence of Code and Data'. You've probably heard Lisp people go on and on about that. Now you know the secret. I'll explain briefly. Lisp doesn't care whether your code is data, or your data is code, or your code was data that just became code, SO LONG as both you and Lisp know and agree on what's what. We'll walk through the last example. In the inner expression,

  (cons '+ (1 2))

The symbol + and the list (1 2) are data for the cons function. cons builds lists, so the above expression returns the list (+ 1 2).

  (cons '+ (1 2))
  -> (+ 1 2)

That list is a piece of Lisp code. But it's also data. It's the data that is being passed to the eval function. But it's also Lisp code that can be evaluted. And the eval function is certainly in the business of evaluating Lisp code. It takes Lisp code as data, evaluates it, and gives you back the resulting data (or code)! More on that later.

Functions

We've already seen a bunch of functions: +, cons, car, and cdr, reverse, replace, insert, append, etc. You can define new ones with de, which takes a symbol to use as the name, a list of symbols representing the parameters, and then zero or more expressions called the body. When the function is called, those expressions will be evaluated in order with the symbols in the body temporarily set ("bound") to the corresponding argument. Whatever the last expression returns will be returned as the value of the call.

Here's a function that takes two numbers and returns their average:

  : (de average (X Y) (/ (+ X Y) 2))
  -> average
  : (average 2 4)
  -> 3



Notice that 'de', like 'setq', doesn't evaluate all its arguments (you don't need to quote the name or the lists).

There's nothing special about named functions as there is in some other languages. All 'de' does is basically this:

  : (setq average '((X Y) (/ (+ X Y) 2)))
  -> average
And of course you can use a literal function wherever you could use a symbol whose value is one, e.g.

  : ('((X Y) (/ (+ X Y) 2)) 2 4)
  -> 3



This expression has three elements, '((X Y) (/ (+ X Y) 2)), which yields a function that returns averages, and the numbers 2 and 4. So when you evaluate all three expressions and pass the values of the second and third to the value of the first, you pass 2 and 4 to a function that returns averages, and the result is 3.

There is commonly used operator for establishing temporary variables, 'let'.

  : (let X 1
     (+ X (* X 2)) )
  -> 3



It can also be used with multiple variables.

  : (let (X 1 Y 2)
     (+ X (* Y 2)) )
  -> 5



So far we've only had things printed out implicity as a result of evaluating them. The standard way to print things out in the middle of evaluation is with 'prin' or 'prinl'. They take multiple arguments and print them in order; 'prinln' also prints a newline at the end. Here's a variant of average that tells us what its arguments were:

  : (de average (X Y)
     (prinl "my arguments were: " X " and " Y)
     (/ (+ X Y) 2) )
  # average redefined
  -> average
  : (average 100 200)
  my arguments were: 100 and 200
  -> 150



The standard conditional operator is 'if'. Like 'setq' and 'de', it doesn't evaluate all its arguments. When given three arguments, it evaluates the first, and if that returns true, it returns the value of the second, otherwise the value of the third:

  : (if (= 2 2) 'A 'B)
  -> A
  : (if (= 2 'two) 'A 'B)
  -> B

Returning true means returning anything except NIL. NIL is conventionally used to represent falsity as well as the empty list. The symbol T (which like NIL evaluates to itself) is often used to represent truth, but any value other than NIL would serve just as well.

  : (num? 2)
  -> T
  : (num? "two")
  -> NIL

It sometimes causes confusion to use the same thing for falsity and the empty list, but it's the right thing to do (for PicoLisp at least).

If the third argument is missing it defaults to NIL.

: (if (= 2 3) 'A)
NIL



Sometimes there's multiple conditions you want to check. For this, there is the more general 'cond'.

  : (cond
     ((num? 'two) 'A)
     ((num? 2)    'B) )

In English, I would read this as,

  : (cond                  # under the condition that,
     ((num? 'two) 'A)      # the symbol 'two is a number, return A.
     ((num? 2)    'B)      # Otherwise, if the number 2 is a number,
  -> B                     # return B.



Each argument to 'if' (or 'cond') is a single expression, so if you want to do multiple things depending on the result of a test, combine them into one expression with 'prog'.

  : (prog
     (prinl "hello")
     (+ 2 3) )
  hello
  -> 5

If you just want several expressions to be evaluated when some condition is true, you could say

  : (if A
      (prog B C) )

but this situation is so common there's a separate operator for it.

(when A
  B
  C)

The 'and' and 'or' operators are like conditionals because they don't evaluate more arguments than they have to.

  : (and NIL (pr "you'll never see this"))
  -> NIL

The negation operator is called 'not', a name that also works when talking about NIL as the empty list. Here's a function to return the length of a list:

  : (de myLength (Lst)
     (if (not Lst)
        0
        (+ 1 (mylen (cdr Lst))) ) )
-> myLength

If the list is NIL the function will immediately return 0. Otherwise it returns 1 more than the length of the cdr of the list.

  : (myLength NIL)
  -> 0
  : (myLength '(A B C))
  -> 3

I called it 'myLength' because there's already a function called 'length' for this. You're welcome to redefine PicoLisp functions, but redefining 'length' this way might break code that depended on it, because 'length' works on more than lists.

The standard comparison operator is '=', which returns true if its arguments are identical.

: (= 'A 'A)
-> T
: (= 'this 'this)
-> T
: (= 'this 'that)
-> NIL
: (let X (list 'A)
   (= X X) )
-> T



The case operator takes alternating keys and expressions and returns the value of the expression after the key that matches. You can supply a final expression 'T' as the default.

  : (de translate (Sym)
     (case Sym
        ("apple" 'mela)
        ("onion" 'cipolla)
        (T       'che?) )
  -> translate
  : (translate 'apple)
  -> mela
  : (translate 'orange)
  -> che?

PicoLisp has a variety of iteration operators. The most common (and powerful) of the bunch is 'for'. You can use it on a range of numbers,

  : (for N 5 (printsp N))   # for each number from 1 to 5,
  1 2 3 4 5 -> 5            # print it with a space afterwords



You can use it to step through a list,

  : (for L '(A B C D) (println L))   # for each letter in the list,
  A                                  # print it
  B                                  # on
  C                                  # a
  D                                  # new
  -> D                               # line.



If you want to iterate over a string, it's helpful to 'chop' that string first.

  : (for L (chop "abcd") (printsp L))   # for each letter in the string
  "a" "b" "c" "d" -> "d"                # - that I've chopped already -
                                        # print it with a space.



But that's pretty much doing the same thing as the previous example. All 'chop' does is chop a string into a list of characters.


  : (chop "string")
  -> ("s" "t" "r" "i" "n" "g")



Eventually you'll want to put your strings back together. Use 'pack' for that.

  : (let X (chop "string")
     (pack X) )
  -> "string"



To continue iterating while some condition is true, use 'while'.

   : (let N 10
      (while (> N 5)
         (setq N (- N 1))
         (print N) ) )
   9
   8
   7
   6
   5
   -> 5



If you just want to do something a bunch, use 'do'.

  : (do 5 (printsp 'la))
  la la la la la -> la

The 'mapcar' function takes a function and a list and returns the result of applying the function to each element.

  : (mapcar inc (1 2 3))   # map 'inc', the increment function, over
  -> (2 3 4)               # element in the list.



'mapcar' can actually take any number of sequences, and keeps going till the shortest runs out:

  : (mapcar + (1 2 3) (4 5 6))
  -> (5 7 9)
  : (mapcar + (1 2 3) (4 5 6) (7 8 9))
  -> (12 15 18)



Onward!

Well, that ended rather abruptly. But don't worry, I bet you know enough about PicoLisp now to continue playing around at the REPL by yourself! And I bet you also know enough PicoLisp to start trying some of the other tutorials and Rosetta Code examples. Make haste, Lisp wizardry awaits!

http://picolisp.com/wiki/?basics

08jun16    erik