| Title: Showing some Common Lisp features | |
| Author: Solène | |
| Date: 05 December 2017 | |
| Tags: lisp | |
| Description: | |
| # 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; | |
| } | |
| } | |
| } | |
| 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() | |
| 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)) | |
| **Perl code** | |
| sub my_function { | |
| my $parameter1 = shift; | |
| my $parameter2 = shift; | |
| my @rest = @_; | |
| parameters are\n", | |
| $parameter1, $parameter2; | |
| printf " - %s\n", $element; | |
| } | |
| } | |
| **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)) | |
| 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)) | |
| 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; | |
| $hash->{key1} = "default"; | |
| } | |
| $hash->{key2} = 0; | |
| } | |
| $hash->{key1}, $hash->{key2}; | |
| } | |
| **Python code** | |
| def my_function(key1="default", key2=0): | |
| print("My key1 is {} and key2 ({}) default to 0.".format(key1, | |
| key2)) | |
| ## 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; | |
| } | |
| 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)) | |
| 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)))) | |
| (with-open-file (x file | |
| :direction :output | |
| :if-does-not-exist :create | |
| :if-exists :supersede) | |
| (print data x))) | |
| (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))) | |
| (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)))) | |
| (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))) | |