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:
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".
Functions can be created, modified and stored in variables.
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"
() 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"
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"))
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
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"
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
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
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)) ) ) )
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
This next example shows the behaviour of
: (de foo @ (while (args) (println (next) (args) (rest)) ) ) : (foo 1 2 3) 1 T (2 3) 2 T (3) 3 NIL NIL
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 a b c
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
If data and functions are the same, then why do we need the
de keyword then after all?
Actually, it is not needed.
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
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
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) mia : (when (call 'test "-r" "file.l") # Test if file exists and is readable (load "file.l") # Load it (call 'rm "file.l") ) # Remove it
Congratulations, we have now almost finished our "60 most common PicoLisp functions" series! The last post will cover Lists and String functions.