About me: My name is Solène Rapenne, pronouns she/her. I like learning and sharing knowledge. Hobbies: '(BSD OpenBSD Qubes OS Lisp cmdline gaming security QubesOS internet-stuff). I love percent and lambda characters. OpenBSD developer solene@. No AI is involved in this blog.

Contact me: solene at dataswamp dot org or @solene@bsd.network (mastodon).

I'm a freelance OpenBSD, FreeBSD, Linux and Qubes OS consultant, this includes DevOps, DevSecOps, technical writing or documentation work. If you enjoy this blog, you can sponsor my open source work financially so I can write this blog and contribute to Free Software as my daily job.

Showing some Common Lisp features

Written by Solène, on 05 December 2017.
Tags: #lisp

Comments on Fediverse/Mastodon

Introduction: comparing LISP to Perl and Python

We will refer to Common LISP as CL in the following article.

I wrote it to share what I like about CL. I’m using Perl to compare CL features. I am using real world cases for the average programmer. If you are a CL or perl expert, you may say that some example could be rewritten with very specific syntax to make it smaller or faster, but the point here is to show usual and readable examples for usual programmers.

This article is aimed at people with programming interest, some basis of programming knowledge are needed to understand the following. If you know how to read C, Php, Python or Perl it should be enough. Examples have been choosed to be easy.

I thank my friend killruana for his contribution as he wrote the python code.

Variables

Scope: global

Common Lisp code

(defparameter *variable* "value")

Defining a variable with defparameter on top-level (= outside of a function) will make it global. It is common to surround the name of global variables with * character in CL code. This is only for readability for the programmer, the use of * has no incidence.

Perl code

my $variable = "value";

Python code

variable = "value";

Scope: local

This is where it begins interesting in CL. Declaring a local variable with let create a new scope with parenthesis where the variable isn’t known outside of it. This prevent doing bad things with variables not set or already freed. let can define multiple variables at once, or even variables depending on previously declared variables using let*

Common Lisp code

(let ((value (http-request)))
  (when value
    (let* ((page-title (get-title value))
           (title-size (length page-title)))
      (when page-title
        (let ((first-char (subseq page-title 0 1)))
          (format t "First char of page title is ~a~%" first-char))))))

Perl code

{
    local $value = http_request;
    if($value) {
        local $page_title = get_title $value;
        local $title_size = get_size $page_title;
        if($page_title) {
            local $first_char = substr $page_title, 0, 1;
            printf "First char of page title is %s\n", $first_char;
        }
    }
}

The scope of a local value is limited to the parent curly brakets, of a if/while/for/foreach or plain brakets.

Python code

if True:
    hello = 'World'
print(hello) # displays World

There is no way to define a local variable in python, the scope of the variable is limited to the parent function.

Printing and format text

CL has a VERY powerful function to print and format text, it’s even named format. It can even manage plurals of words (in english only) !

Common Lisp code

(let ((words (list "hello" "Dave" "How are you" "today ?")))
  (format t "~{~a ~}~%" words))

format can loop over lists using ~{ as start and ~} as end.

Perl code

my @words = @{["hello", "Dave", "How are you", "today ?"]};
foreach my $element (@words) {
    printf "%s ", $element;
}
print "\n";

Python code

# Printing and format text
# Loop version
words = ["hello", "Dave", "How are you", "today ?"]
for word in words:
    print(word, end=' ')
print()
    
# list expansion version
words = ["hello", "Dave", "How are you", "today ?"]
print(*words)

Functions

function parameters: rest

Sometimes we need to pass to a function a not known number of arguments. CL supports it with &rest keyword in the function declaration, while perl supports it using the @_ sigil.

Common Lisp code

(defun my-function(parameter1 parameter2 &rest rest)
  (format t "My first and second parameters are ~a and ~a.~%Others parameters are~%~{    - ~a~%~}~%"
          parameter1 parameter2 rest))
    
(my-function "hello" "world" 1 2 3)

Perl code

sub my_function {
    my $parameter1 = shift;
    my $parameter2 = shift;
    my @rest = @_;
    
    printf "My first and second parameters are %s and %s.\nOthers parameters are\n",
        $parameter1, $parameter2;
    
    foreach my $element (@rest) {
        printf "    - %s\n", $element;
    }
}
    
my_function "hello", "world", 0, 1, 2, 3;

Python code

def my_function(parameter1, parameter2, *rest):
    print("My first and second parameters are {} and {}".format(parameter1, parameter2))
    print("Others parameters are")
    for parameter in rest:
        print(" - {}".format(parameter))
    
my_function("hello", "world", 0, 1, 2, 3)

The trick in python to handle rests arguments is the wildcard character in the function definition.

function parameters: named parameters

CL supports named parameters using a keyword to specify its name. While it’s not at all possible on perl. Using a hash has parameter can do the job in perl.

CL allow to choose a default value if a parameter isn’t set, it’s harder to do it in perl, we must check if the key is already set in the hash and give it a value in the function.

Common Lisp code

(defun my-function(&key (key1 "default") (key2 0))
  (format t "Key1 is ~a and key2 (~a) has a default of 0.~%"
          key1 key2))
    
(my-function :key1 "nice" :key2 ".Y.")

There is no way to pass named parameter to a perl function. The best way it to pass a hash variable, check the keys needed and assign a default value if they are undefined.

Perl code

sub my_function {
    my $hash = shift;
    
    if(! exists $hash->{key1}) {
        $hash->{key1} = "default";
    }
    
    if(! exists $hash->{key2}) {
        $hash->{key2} = 0;
    }
    
    printf "My key1 is %s and key2 (%s) default to 0.\n",
        $hash->{key1}, $hash->{key2};
}
    
my_function { key1 => "nice", key2 => ".Y." };

Python code

def my_function(key1="default", key2=0):
    print("My key1 is {} and key2 ({}) default to 0.".format(key1, key2))
    
my_function(key1="nice", key2=".Y.")

Loop

CL has only one loop operator, named loop, which could be seen as an entire language itself. Perl has do while, while, for and foreach.

loop: for

Common Lisp code

(loop for i from 1 to 100
   do
     (format t "Hello ~a~%" i))

Perl code

for(my $i=1; $i <= 100; $i++) {
    printf "Hello %i\n";
}

Python code

for i in range(1, 101):
   print("Hello {}".format(i))

loop: foreach

Common Lisp code

(let ((elements '(a b c d e f)))
  (loop for element in elements
     counting element into count
     do
       (format t "Element number ~s : ~s~%"
               count element)))

Perl code

# verbose and readable version
my @elements = @{['a', 'b', 'c', 'd', 'e', 'f']};
my $count = 0;
foreach my $element (@elements) {
    $count++;
    printf "Element number %i : %s\n", $count, $element;
}
    
# compact version
for(my $i=0; $i<$#elements+1;$i++) {
    printf "Element number %i : %s\n", $i+1, $elements[$i];
}

Python code

# Loop foreach
elements = ['a', 'b', 'c', 'd', 'e', 'f']
count = 0
for element in elements:
    count += 1
    print("Element number {} : {}".format(count, element))
    
# Pythonic version
elements = ['a', 'b', 'c', 'd', 'e', 'f']
for index, element in enumerate(elements):
    print("Element number {} : {}".format(index, element))

LISP only tricks

Store/restore data on disk

The simplest way to store data in LISP is to write a data structure into a file, using print function. The code output with print can be evaluated later with read.

Common Lisp code

(defun restore-data(file)
  (when (probe-file file)
    (with-open-file (x file :direction :input)
      (read x))))
    
(defun save-data(file data)
  (with-open-file (x file
                     :direction :output
                     :if-does-not-exist :create
                     :if-exists :supersede)
    (print data x)))
    
;; using the functions
(save-data "books.lisp" *books*)
(defparameter *books* (restore-data "books.lisp"))

This permit to skip the use of a data storage format like XML or JSON. Common LISP can read Common LISP, this is all it needs. It can store objets like arrays, lists or structures using plain text format. It can’t dump hash tables directly.

Creating a new syntax with a simple macro

Sometimes we have cases where we need to repeat code and there is no way to reduce it because it’s too much specific or because it’s due to the language itself. Here is an example where we can use a simple macro to reduce the written code in a succession of conditions doing the same check.

We will start from this

Common Lisp code

(when value
  (when (string= line-type "3")
    (progn
      (print-with-color "error" 'red line-number)
      (log-to-file "error")))
  (when (string= line-type "4")
    (print-with-color text))
  (when (string= line-type "5")
    (print-with-color "nothing")))

to this, using a macro

Common Lisp code

(defmacro check(identifier &body code)
  `(progn
     (when (string= line-type ,identifier)
     ,@code)))
    
(when value
  (check "3"
         (print-with-color "error" 'red line-number)
         (log-to-file "error"))
  (check "4"
         (print-with-color text))
  (check "5"
         (print-with-color "nothing")))

The code is much more readable and the macro is easy to understand. One could argue that in another language a switch/case could work here, I choosed a simple example to illustrate the use of a macro, but they can achieve more.

Create powerful wrappers with macros

I’m using macros when I need to repeat code that affect variables. A lot of CL modules offers a structure like with-something, it’s a wrapper macro that will do some logic like opening a database, checking it’s opened, closing it at the end and executing your code inside.

Here I will write a tiny http request wrapper, allowing me to write http request very easily, my code being able to use variables from the macro.

Common Lisp code

(defmacro with-http(url)
  `(progn
     (multiple-value-bind (content status head)
         (drakma:http-request ,url :connection-timeout 3)
       (when content
         ,@code))))
    
(with-http "https://dataswamp.org/"
  (format t "We fetched headers ~a with status ~a. Content size is ~d bytes.~%"
          status head (length content)))

In Perl, the following would be written like this

Perl code

sub get_http {
    my $url = $1;
    my %http = magic_http_get $url;
    if($http{content}) {
        return %http;
    } else {
        return undef;
    }
}
    
{
    local %data = get_http "https://dataswamp.org/";
    if(%data) {
        printf "We fetched headers %s with status %d. Content size is %d bytes.\n",
            $http{headers}, $http{status}, length($http{content});
    }
}

The curly brackets are important there, I want to emphase that the local %data variable is only available inside the curly brackets. Lisp is written in a successive of local scope and this is something I really like.

Python code

import requests
with requests.get("https://dataswamp.org/") as fd:
    print("We fetched headers %s with status %d. Content size is %s bytes." \
                % (list(fd.headers.keys()), fd.status_code, len(fd.content)))