DEV Community

Cover image for 60 PicoLisp Functions You Should Know - 5: Defining Own Functions

Posted on • Originally published at

60 PicoLisp Functions You Should Know - 5: Defining Own Functions

Welcome back to the 60 most common PicoLisp functions series.

Today we will cover functions.

What are functions?

Just like pieces of data can be assigned to a specific variable name, functions are basically a set of instructions assigned to a specific function name.

So, what is a function in PicoLisp? A function is a piece of code which happens to be executable. Since you followed the tutorial up to this point, you might have played with the REPL quite a bit. Maybe you noticed that for each statement you type, you get a return value (the part that comes after the ->).

What do we learn from that? Obviously, not only functions are executable, everything in PicoLisp is executable. Some consequences of this are:

  1. Usually, the return value doesn't need to be defined explicitly. The code inside the function evaluates per definition, and this is equivalent to the "return value".

  2. Functions can be created, modified and stored in variables.

  3. Functions can take other functions as arguments, and can give functions as return values.

In fact, PicoLisp absolutely doesn't make a formal difference between function and data. This has some interesting implications as we will see later on, especially when we get deeper into functional programming concepts.

Functions without arguments

Functions are defined by de. No tutorial without a "print Hello World" function - here it is:

: (de hello ()
   (prinl "Hello World") )

: (hello)
Hello World      
-> "Hello World"     
Enter fullscreen mode Exit fullscreen mode

The () in the first line indicates a function without arguments. The body of the function is in the second line, consisting of a single statement.

As you can see, the function first prints Hello World, then it evaluates to "Hello World". Therefore it is perfectly possible to pass this function as argument to another function, for example like this:

: (println (hello))
Hello World   
"Hello World" 
-> "Hello World"  
Enter fullscreen mode Exit fullscreen mode

As already mentioned, line breaks and indentation are just needed for better readibility, it could as well also be written in one single line:

:(de hello() (prinl "Hello world"))
Enter fullscreen mode Exit fullscreen mode

Functions with a defined number of arguments

A function with one argument might be defined this way:

: (de hello (X)
   (prinl "Hello " X) )
# hello redefined
Enter fullscreen mode Exit fullscreen mode

Since the function "hello" already existed from our previous example, PicoLisp informs you that you have just redefined the function. This might be a useful warning in case you forgot that a bound symbol with that name already existed.

Let's test it:

: (hello "World")
Hello World
-> "World"

: (hello "Mia")
Hello Mia
-> "Mia"
Enter fullscreen mode Exit fullscreen mode

Why does our new function now evaluate only to "World" instead of "Hello World"? Inside our hello function, we are calling the prinl function with two arguments. prinl takes the arguments one by one. The last one is "World", therefore it is the final evaluation of our hello function and thus it is returned.

Functions with two or more arguments are analogous to those with one argument:

: (de multiply (X Y) (* X Y))

: (multiply 3 4)
-> 12
Enter fullscreen mode Exit fullscreen mode

Functions with an arbitrary number of arguments

If you want to pass an arbitrary number of arguments to a function, PicoLisp recognizes the symbol @ as a single atomic parameter. The arguments can then be accessed sequentially with the args, next, arg and rest functions.

Let's write a function foo that prints out its value and its square value for each argument.

: (de foo @
   (while (args)                    # Check if there are some args left
      (let N (next)
         (println N (* N N)) ) ) )
Enter fullscreen mode Exit fullscreen mode

while (args) checks if there are some arguments left. let N (next)sets N to the next argument. Note that let N defines N only in a local scope. N is not accessible outside the function.

Now let's run the example with five arguments: two of them are integers, three of them are expressions that need to be evaluated first. For example, (+ 2 3) is evaluated as 5 before it is passed on to the function.

: (foo 2 (+ 2 3) (- 7 1) 1234 (* 9 9))
2 4
5 25
6 36
1234 1522756
81 6561
Enter fullscreen mode Exit fullscreen mode

This next example shows the behaviour of args and rest:

: (de foo @
   (while (args)
      (println (next) (args) (rest)) ) )
: (foo 1 2 3)
1 T (2 3)
2 T (3)
Enter fullscreen mode Exit fullscreen mode

The arg function takes an integer as argument and returns the n'th remaining argument:

: (de foo @
   (println (arg 1) (arg 2))
   (println (next))
   (println (arg 1) (arg 2)) )

: (foo 'a 'b 'c)
a b
b c
Enter fullscreen mode Exit fullscreen mode

Anonymous functions

Some of you might have expected to read now about a lambda function to create anonymous functions. However in PicoLisp there is no need for that: as we already know, the quote function (also written as ') escapes the evaluation of an expression. Using this, anonymous functions can be created:

: ((quote (X) (* X X)) 9)
-> 81

:# equivalent to:
:( '((X)(* X X)) 9)
-> 81
Enter fullscreen mode Exit fullscreen mode

If data and functions are the same, then why do we need the de keyword then after all?

Actually, it is not needed.
The setq keyword for defining variables could also be used to define functions, as this example shows:

: (setq f '((X) (* X X)))  

: f
-> ((X) (* X X))

: (f 3)
-> 9
Enter fullscreen mode Exit fullscreen mode

Note that this only works because the evaluation of '((X) (* X X)) is escaped due to the quote. Otherwise, the interpreter will try to evaluate X and complain if it's undefined:

: (setq f ((X)(*X X)))
!? (X)
X -- Undefined
Enter fullscreen mode Exit fullscreen mode

The interchangeability of data and functions is a key point in PicoLisp, but might be somehow difficult to grasp in the beginning. We will come back to that point later.

Calling external functions

External system commands can be called using call. If the command was executed successfully, T is returned. The (system dependent) exit status code of the child process is stored in the global variable @@.

: (call 'whoami)

: (when (call 'test "-r" "file.l")  # Test if file exists and is readable
    (load "file.l")  # Load it
    (call 'rm "file.l") )  # Remove it
Enter fullscreen mode Exit fullscreen mode

Congratulations, we have now almost finished our "60 most common PicoLisp functions" series! The last post will cover Lists and String functions.


Top comments (0)