Emacs Lisp (o elisp) è un dialetto di Lisp, the LISt Processing Language.

Il nome è piuttosto autoesplicativo, visto che in Lisp le liste abbondano. Ogni lista è definita da delle parentesi in cui il primo elemento è da considerarsi un operatore ed il resto dati forniti all'operatore per compiere un lavoro di qualche tipo.

(= 1 2)
;; => nil

Questa funzione, ad esempio, si occupa di verificare l'identità dei due parametri forniti e restituire una booleana (t per "true", nil per "false"). Anche la lista vuota () è un esempio di "false".

Nested expressions§

(+ 1 (* 2 3))
;; => 7

Qualche operatore utile§

if§

(if t "it's true" "it's false")
;; => "it's true"

concat§

Per concatenare le string in Emacs Lisp:

(concat "ab" "cd")
;; => "abcd"

Data Types§

Per testare di che tipo sia il valore fornito:

(integerp 11)
;; => t

Vedi anche:

  • floatp
  • numberp
  • zerop
  • wholenump

Per convertire da un tipo all'altro:

(float 1)
;; => 1.0

E se volessimo una semplice lista di dati?§

Non sempre vogliamo impiegare una funzione, a volte abbiamo solo bisogno di conservare delle informazioni tra le nostre parentesi; per spiegare all'interpreter che anche il primo valore è un semplice dato, anteponiamo alla parentesi un apostrofo, in questo modo:

;; List of integer values
'(1 2 3)

Questa pratica è detta più propriamente quoting.

cons§

Aggiungi un valore alla testa della lista:

(cons 1 '(2 3))
;; => (1 2 3)

car§

Recupera il valore in testa alla lista:

(car '(1 2 3))
;; => 1

cdr§

Recupera una lista che includa tutti i valori meno il primo (la "coda"):

(cdr '(1 2 3))
;; => (2 3)

Trovo interessante che lisp ragioni lavorando sul primo valore della lista e non sull'ultimo, come a me invece verrebbe spontaneo fare (penso alle diffuse funzioni append). Gordon Guthrie, nel suo Learn Elisp for Emacs, spiega che persino il nome di questi operatori deriva dai corrispettivi in assembly. Mi viene da pensare che anche questa propensione a visualizzare le liste in questo modo sia dovuta a delle esigenze di quello specifico ambiente.

Code evaluation§

Per "valutare" un'espressione lisp in Emacs, è sufficiente spostare il cursore sopra la parentesi di chiusura e lanciare la funzione eval-last-sexp (che si può ricercare dopo aver premuto Alt+x, come tutte le altre funzioni disponibili). Ci sono anche delle shortcut, chiaramente, tra cui Ctrl+x Ctrl+e (più brevemente C-x C-e) è la shortcut tradizionale. Perché la parentesi di chiusura? Perché "sexp" sta per "S-expression".

Per valutare il codice in una selezione, invece, si usa eval-region. Altre funzioni:

  • eval-buffer (per l'intero buffer);
  • load-file
  • eval-defun (per la funzione che viene definita nel blocco in cui si trova il cursore, ma Emacs potrebbe faticare a trovare la funzione se il codice non è bene indentato)
  • eval-expression: apre un prompt per farti scrivere una funzione

Definire una funzione§

definire una funzione è semplice: si usa un'altra funzione, detta defun.

(defun print-string (mystring)
    (print mystring)
    )

Una funzione richiamabile§

Perché una funzione possa essere richiamata con Alt+x, deve essere definita "interactive":

(defun do-stuff ()
    "Describe what stuff this function do"
    (interactive)
    <STUFF HERE>
    )

Accettare input utente§

(defun do-stuff-with-this-url ()
    "Describe what stuff this function do"
    (interactive "sEnter URL: ")
    ;; (print url) ;; maybe print something here for debugging?
    <STUFF YOU DO TO YOUR URL HERE>
    )

Ad esempio, potremmo volere stampare una versione hexsafe dell'URL, usando l'Url Package, come descritto nella wiki.

Perché sEnter e non solo Enter?

Buffer§

Grazie a goto-char, è possibile muoversi attraverso il buffer.

Basic debugging§

Qualche funzione utile:

  • message stampa una stringa sul minibuffer;
  • boundp cerca una variabile e verifica che non sia vuota: se la trova, la restituisce, altrimenti restituisce nil;
  • fboundp fa la stessa cosa del precedente, ma restituisce t se trova la variabile.

Check type§

(setq a_random_list '(1 2))

(if (listp a_random_list)
      (message "ciao"))
      
;; => "ciao" 

Dalla documentazione, sfruttando cond

(defun add-on (x)
  (cond ((symbolp x)
         ;; If X is a symbol, put it on LIST.
         (setq list (cons x list)))
        ((listp x)
         ;; If X is a list, add its elements to LIST.
         (setq list (append x list)))
        (t
         ;; We handle only symbols and lists.
         (error "Invalid argument %s in add-on" x))))

Semplificando:

(cond ((listp a_random_list)
      (message "ciao")))
      
;; => "ciao"

cond vuole una lista con due liste:

  • Condizione
  • Azione

Buone pratiche§

Aiutarsi con Common Lisp§

Loop§

Per ogni numero nella lista, se è dispari restituisce il suo quadrato aggiungendolo alla lista.

From here

(cl-loop for i in '(1 2 3)
      if (cl-oddp i) collect (* i i))

Map§

From here:

(setq sequence "AGCTTTTCATTCTGACTGCAACGGGCAATATGTCTCTGTGTGGATTAAAAAAAGAGTGTCTGATAGCAGC")
(cl-map nil
        (lambda (element)
          (when (= element ?A)
            (print element)))
        sequence)