Dritter Termin

Emacs-Workshop

1 2010-04-12 – Emacs Lisp

1.1 Typische Konfigurationsschnipsel

Emacs wird in Emacs-Lisp konfiguriert. Das kann z.B. so aussehen:

(setq inhibit-startup-message t)

(require 'uniquify)

(add-to-list 'load-path "~/.local-elisp")

(add-to-list 'auto-mode-alist '("\\.thm\\'" . tar-mode))

(add-hook 'text-mode-hook 
  (lambda () (auto-fill-mode 1)))

(global-set-key (kbd "C-c m")
  (lambda () (interactive)
    (insert
     (format "%s <%s>" 
             (format-time-string "%Y-%m-%d")
             user-mail-address))))

Um Emacs konfigurieren zu koennen sind Kenntnisse in Emacs-Lisp
zumindest hilfreich.

1.2 Datentypen

1.2.1 Zahlen und Strings

Integer: 12, -4, 0, #b101100, #x2c
Float:   1.0, 15e2, -.43
String: 
  "foo" 
  "Das Programm 
schreibt \"Hallo Welt\"."
  "foo \\(bar\\)"
  "  "

1.2.2 Booleans

  • nil steht fuer “false” (“not in list”)
  • alle andere ist “true”, man verwendet das konstante Symbol t

(setq dired-x-hands-off-my-keys nil)
(setq visible-bell t)

1.2.3 Listen

Die Liste ist der zentrale Datentyp in Lisp (“LISt Processing”). Wenn
man Listen aufschreibt, muessen sie zitiert werden, um ihre Evaluation
zu verhindern.

'(1)
'(1 "abc" 4.0)
'(1 (2 3 4))
'()

Man muss nur ein mal zitieren. Die enthaltenen Daten koennen einen beliebigen Typ haben,
Listen koennen verschachtelt sein,

Mit der list-Funktion kann man sich auch eine frische Liste erzeugen
lassen. Dadurch wird bei jedem Durchlauf Speicher verbraucht, und die
so erzeugten Listen koennen veraendert werden, ohne dass man
unvorhergesehene Seiteneffekte befuerchten muss.

(list 1)
(list 1 "abc" 4.0)
(list 1 (list 2 3 4))
(list)

Listen bestehen aus Cons Cells. Eine Cons Cell besteht aus zwei
Zeigern, car und cdr. Man kann sie explizit mit einem Punkt
notieren (“dotted notation”):

'(1 . 2)
'(1 . nil)
'(1 . (2 . nil))
'(1 . (2 . (3 . nil)))

Frische Cons Cells liefert die cons-Funktion:

(cons 1 2)
(cons 1 nil)
(cons 1 (cons 2 nil))
(cons 1 (cons 2 (cons 3 nil)))

Hier eine Visualisierung der folgenden Liste:

(list "foo" (list 1 2) 10) 

1.2.4 Symbole

Symbole muessen zitiert werden, wenn man ihre Evaluation verhindern
will. Sie koennen fast beliebige Zeichen enthalten:

'foo
'foo-bar
'foo/bar
'damn%/*+$!
'\"\'

Ein Symbol laesst sich schneller vergleichen als ein String und wird
deswegen gern als Marker eingesetzt:

(setq dired-recursive-copies 'always)

Ein Symbol hat ausserdem zwei wichtige “Slots”.

  • Symbole als Variablen
    (set 'foo "bar")
    foo ; => "bar"
    

    Kurzschreibweise:

    (setq foo "bar")
    (setq bar foo)
    

    Noch mal zur Verdeutlichung des Unterschieds zwischen set und setq:

    (setq bar 'foo)
    (set bar "baz")
    foo
    (symbol-value bar)
    

    Werte gelten global, es gibt keine Namensraeume.

  • Symbole als Funktionen

    Den Funktionswert setzt fset. Anonyme Funktionen werden mit lambda
    erzeugt. Eine nicht-zitierte Liste fuehrt die Funktion aus.

    (fset 'foo 
      (lambda (arg1)
        (insert "hello from function")
        (insert arg1)))
     
    (foo "hallo")
    

    Natuerlich gibt es auch dafuer eine Kurzschreibweise:

    (defun foo (arg1)
      (insert arg1)
      (insert arg1))
    

    Den Funktionswert kann man auch explizit auslesen:

    (symbol-function 'foo)
    (symbol-value 'foo)
    

    Auch der Variablen-Zelle kann man eine Funktion zuweisen, dann muss
    man sie aber mit funcall oder apply aufrufen:

    (set 'foo
      (lambda (arg1)
        (insert "hello from variable")
        (insert arg1)))
    
    (foo "hello")
     
    (funcall foo "hallo")
    (funcall (lambda (arg) (insert arg arg arg)) "abc")
    (apply foo '("hallo"))
    

    Symbole haben zwei Speicherzellen => Emacs Lisp ist ein Lisp-2.

  • Symbole und Strings

    Symbole lassen sich in Strings und Strings in Symbole wandeln:

    (symbol-name 'load-path)
    (intern "load-path")
    

    Der etwas spezielle Name intern ist ein Hinweis, dass diese Funktion
    sehr grundlegend und alt ist. Symbole werden in einer Art Hash, einem
    obarray gespeichert.

  • Spass mit fset
    (yes-or-no-p "Do you want to sell your soul? ")
    (y-or-n-p "Do you want to sell your soul? ")
    (fset 'yes-or-no-p (symbol-function 'y-or-n-p))
    (yes-or-no-p "Do you want to sell your soul? ")
    

    Auch das geht kuerzer:

    (fset 'yes-or-no-p 'y-or-n-p)
    

1.3 Mehr ueber Listen

1.3.1 Zugriff auf Listen mit car, cdr und nth

car (“Contents of Address Register”) liefert das erste Element einer
Liste, cdr (sprich: “kudder”, “Contents of Decrement Register”) die Liste ohne das
erste Element. Die Namen beziehen sich auf die Hardwarearchitektur,
unter der Lisp zuerst implementiert wurde.

(car load-path)
(cdr load-path)
(car (cdr load-path))
(car (cdr (cdr load-path)))

Als alternative Namen sind first und rest verfuegbar. Allerdings
haben die traditionellen Namen den Vorteil, dass man aus ihnen
zusammengesetzte Funktionsnamen bilden kann.

# evtl. vorher: (require 'cl)
(cadr load-path)
(caddr load-path)

(first load-path)
(rest load-path)

Die Funktion nth bietet direkten Zugriff auf ein einzelnes
Listenelement, technisch macht sie aber nichts anderer, als den
Zeigern zu folgen, ist also im Prinzip nicht schneller:

(nth 2 load-path)

1.3.2 Listen verlaengern

Man fuegt Elemente an eine Liste am einfachsten vorne an:

(setq load-path 
  (cons "~/.local-elisp" load-path))

An’s Ende etwas anzufuegen ist etwas komplizierter:

(setcdr (last load-path) (list "~/.local-elisp"))

Die Funktion append haengt mehrere Listen aneinander und erzeugt
eine neue:

(setq load-path
  (append load-path (list "~/.local-elisp")))

add-to-list fuegt ein Element einer Liste hinzu, wenn es nicht
schon vorhanden ist:

(add-to-list 'load-path "~/.local-elisp")

Es hat auch einen optionalen Parameter, mit dem man einen Wert hinten
anfuegen kann:

(add-to-list 'load-path "~/.local-elisp" t)

1.3.3 Association Lists

Aehnlich wie ein Hash/Dictionary, weniger effizient fuer grosse
Datenmengen, bei wenig Daten aber schneller.

(setq flowers
  '((roses . red)
    (violets . blue)))

(cdr (assoc 'violets flowers))
(rassoc 'red flowers)
(assoc 'carrots flowers)

(cdr (assoc 'filename (assoc ".emacs" bookmark-alist)))
(cdr (assoc 'filename (assoc "mpcp" bookmark-alist)))

(rassoc 'python-mode auto-mode-alist)
(rassoc 'c-mode auto-mode-alist)

(add-to-list 'auto-mode-alist '("/.notes$" . org-mode))

1.4 mehr ueber Funktionen

1.4.1 Rechnen, Evaluationsreihenfolge

Man kann Emacs-Lisp auch einfach zum Rechnen benutzen. Dabei kann man
Zwischenergebnisse innerhalb eines groesseren Ausdrucks jederzeit
ueberpruefen. Durch die Klammerung ist die Ausfuehrungsreihenfolge
klar.

(+ 1 100)
(+ 1 2 3)
(* (+ 1 2) 3)

1.4.2 Kommandos vs. Functions

Kommandos sind Funktionen, die eine interactive-Deklaration
enthalten. Eine Funktion, die keine interactive-Deklaration hat,
kann nicht interaktiv vom Benutzer aufgerufen werden:

(defun insert-foo () 
  "Inserts the text \"foo\" into the current buffer."
  (insert "foo"))
(global-set-key (kbd "C-c f") 'insert-foo)

Wenn wir nun versuchen, die Funktion mit C-c f aufzurufen, bekommen
wir den folgenden Fehler:

Wrong type argument: commandp, insert-foo

So muss es korrekt aussehen:

(defun insert-foo () 
  "Inserts the text \"foo\" into the current buffer."
  (interactive)
  (insert "foo"))
(global-set-key (kbd "C-c f") 'insert-foo)

1.5 Praktische Beispiele

Hier eine ganz einfache Funktion, die optional ein Praefix-Argument nimmt:

(defun teatime (minutes)
  "Beeps when tea is ready."
  (interactive "P")
  (run-at-time (format "%d min" (or minutes 2)) nil
    (lambda ()
      (message "tea is ready!")
      (dotimes (i 10)
        (beep) (sit-for 0.2)))))

Hier eine Funktion, die einen haeufigen Tippfehler im aktuellen Buffer
korrigiert. save-excursion stellt sicher, dass point am Ende der
Funktion wieder an seinem Platz ist.

(defun fix-c-base-spelling () 
  (save-excursion
    (goto-char (point-min))
    (while (re-search-forward "C-Base" nil t)
      (replace-match "c-base" t))))

Um diese Funktion regelmaessig auszufuehren, muessen wir fuer sie
einen Timer aufsetzen. Um das wiederum fuer alle Dateien zu tun,
aktivieren wir den Timer im after-find-file Hook.

(defun run-fix-c-base-spelling ()
  (run-with-idle-timer 5 t 'fix-c-base-spelling))

(add-hook 'after-find-file 'run-fix-c-base-spelling)