Spotkanie lispowe -- sobota 26 lipca o 19.00 (przeniesione)

Z powodów okołosamolotowych spotkanie zostaje przeniesione.

Spotykamy się w piątek 25 lipca sobotę 26 lipca o 19.00 w kafejce Lente przy ul. Kubusia Puchatka (mapka na dole) aby pogadać o Lispie, a w szczególności programowaniu dla Internetu. Bezpośrednim pretekstem do spotkania są odwiedziny Kevina1, programisty Common Lispa z Hagi, w związku z czym przynajmniej część spotkania odbędzie się po angielsku.

Serdecznie zapraszam wszystkich zainteresowanych.

(Tak, jeśli pojawi się miłośnik nie Lispa, a np. Haskella, OCamla, Prologa, Ruby, Pythona, etc. będzie nam również bardzo miło.)


View Larger Map

miniHOWTO: zdalny lisp za pomocą slime (poczuć się jak Paul Graham)

Jedną z fajniejszych opcji Slime'a jest możliwość podłączenia się do sesji lispa rezydującej gdzieś na jakimś serwerze. Pozwala nam to przeprowadzić programistyczny odpowiednik operacji na otwartym sercu: podłączamy się do żywego obrazu, sprawdzamy zawartość zmiennych, dodajemy nowe funkcje, korzystamy z SLDB... A wszystko to bez zatrzymywania nawet na chwilę naszego programu, w bezpiecznym otoczeniu naszego wiernego Emacsa... Cóż za wspaniałe uczucie... Jesteśmy prawie tak fajni jak Paul Graham... Nie, jesteśmy lepsi, w końcu my (w przeciwieństwie do niego) nie uważamy, że strony HTML oparte na tabelkach są w porządku...

No tak, tylko: jak to zrobić? W ciągu kilku ostatnich dni zacząłem bardzo tego potrzebować, więc postanowiłem pójść o krok dalej od używania tego jako argumentu wyższości Lispa nad <inny-język-programowania>: faktycznie się tego nauczyć.

Uruchamianie Lispa

Musimy doprowadzić do wykonania następującego kodu w naszym kompilatorze na serwerze:

(require :asdf)
(require :swank)

(swank:create-server :port 4005 :dont-close t :external-format :utf-8-unix)

;; Since we're going to be tunneling our connection via ssh2 and we'll
;; only have on port open we want to tell swank to not use an extra
;; connection for output
(setf swank:*use-dedicated-output-stream* nil)

Możemy to zrobić albo ładując plik z odpowiednią zawartością, albo ustawiając odpowiednie opcje przy ładowaniu Lispa.

Warto przyjrzeć się argumentowi external-format w funkcji swank:create-server. Musi się zgadzać z formatem, którego będziemy używać: w przeciwnym wypadku połączenie będzie się zrywać w losowych momentach.

Przygotowanie tunelu

Mamy już Swanka, który słucha na porcie 4005. Pozostaje kwestia: jak się do niego podłączyć? Otwarcie portu 4005 na serwerze i sprawienie, że każdy chętny mógłby zrobić wszystko z naszym obrazem nie wydaje się najatrakcyjniejszą opcją. Na pomoc przychodzi nam SSH.

Spod konsoli:

$ ssh -L 4006:localhost:4005 user@zdalny-host

Tworzy to tunel SSH, który sprawi, że wszystko co będziemy czytać z i pisać do portu 4006 w localhost będzie przesyłane z i do portu 4005 na zdalny-host. Można użyć po obu stronach tego samego portu, jednak ja lubię mieć możliwość wygodnego startowania lokalnego obrazu lispa.

Slime

Włączamy emacsa z załadowanym Slimem i uruchamiamy M-x slime-connect. Na pytanie o host odpowiadamy 127.0.0.1, o port 4006. Kiedy uda nam się połączyć, Slime powita nas radosnym Connected. Hack and be merry!

Uwagi

  • Jeśli z jakiegoś powodu zawiśnie nam sesja ssh, stracimy również kontakt z Lispem. Zazwyczaj trzeba wtedy zabić proces ssh (w przeciwnym razie nowy nie będzie mógł słuchać na wybranym przez nas porcie).
  • C-c C-c (slime-compile-defun) i C-x C-e (eval-last-sexp) działają tak, jak się tego spodziewamy. Jednak już ładowanie systemu za pomocą ASDF będzie ładowało kopię na serwerze.
  • Z oczywistych względów restart-inferior-lisp nie robi tego, co byśmy chcieli

I to wszystko. Z radością przyjmę wszelkie propozycje ulepszeń w komentarzach, postaram się też odpowiedzieć na ewentualne pytania.

Swoją drogą, to jest chyba dobry argument na rzecz wyższości Lispa nad innymi językami programowania. Nie sądzę, aby dało się coś takiego zrobić w Pythonie albo Rubym... Może w Smalltalku? (W razie czego proszę o sprostowanie!).

Wpis gościnny: Porównanie lispowych bibliotek dopasowania wzorców

To fajnie, kiedy ktoś się zgadza napisać wpis gościnny. Jeszcze fajniej, jeżeli ów materiał jest najwyższym poziome. Ale zupełnie najfajniej jest, jeżeli to autor wpisu gościnnego sam się do autora bloga zgłasza. Z przyjemnością zatem przedstawiam zupełnie najfajniejszy (czyli spełniający wszystkie 3 kryteria) wpis gościnny Maćka Pasternackiego na temat dopasowywania wzorców w Common Lisp. Chylę czoła przed skrupulatnością i pracowitością Maćka. Oto człowiek, który nie idzie na kompromis jeśli chodzi o metodologię ;-). Zapraszam do lektury.

(Maciek zgodził się wstępnie na moje poprawki redakcyjne, jednak zdecydowałem, że tekst ich nie potrzebuje. Niewykluczone jednak, że wprowadziłem jakieś błędy edytując wpis joggerowy, i będę bardzo wdzięczny za ich wskazanie.)

Kolejną rzeczą, którą zrobię w ramach planu przejęcia władzy nad światem, będzie biblioteka dla Hunchentoot umożliwiająca łatwe definiowanie schematów adresów URL, wybieranie na ich podstawie funkcji mającej obsłużyć dane żądanie (handlera) oraz przekazywanie takiej funkcji odpowiednich parametrów. Taki rozgałęźnik. Skoro jednak przez tę bibliotekę przechodziło będzie praktycznie każde obsługiwane żądanie, przekombinowanie może skończyć się bardzo przykrym wąskim gardłem. Zatem, zanim zabrałem się do faktycznej pracy, przetestowałem kilka dostępnych bibliotek do dopasowywania wzorców pod kątem wydajności i sensowności zastosowania do trasowania adresów.

Główny problem z trasowaniem polega na uzyskaniu ładnych, dynamicznych adresów. Gdyby wystarczały mi adresy sparametryzowane, w stylu /foo?id=23, użyłbym metody opisanej pod adresem http://sean-ross.blogspot.com/2007/05/hunchentoot-and-packages.html i miał problem z głowy. Wolę jednak mieć bez potrzeby użycia potworków w rodzaju mod_rewrite możliwość korzystania z ładnych adresów takich, jak /foo/23, czy nawet /foo/nazwa-obiektu. Biblioteka, którą napiszę, ma umożliwiać:

  • Elastyczny i miły system opisywania adresu,
  • Walidację wybranych fragmentów adresu,
  • Przypisywanie wybranych fragmentów adresu do zmiennych oraz przekazywanie ich handlerowi jako parametrów,
  • W tym np. fragmenty, które mają być liczbą, powinny być przekazywane jako liczba, a nie jako napis,
  • Co więcej, jeśli fragment adresu jest np. identyfikatorem wpisu w bazie danych, już na poziomie parsowania adresu odpowiedni wpis powinien zostać pobrany z bazy danych i przekazany handlerowi jako odpowiedni obiekt lub zestaw obiektów,
  • Jeśli jednak żądanego ID nie ma w bazie danych, rozgałęźnik powinien według widzimisię programisty albo przekazać handlerowi NIL zamiast odpowiedniego parametru, albo nie kłopocząc handlera sam pokazać klientowi palec^Wbłąd 404.

Wymagań, jak widać, kilka jest. Nie są one, zapewniam, wyssane z palca – wynikają bezpośrednio z pracy nad wcześniejszymi projektami, gdzie stosowany w nieformalny sposób podobny podział odpowiedzialności pozwolił bardzo ładnie nadać projektowi strukturę. Handler nie musi zastanawiać się nad parametrami i obiektami, cała logika rozdzielania adresów jest skupiona w jednym miejscu, słonko świeci, ptaszki śpiewają i jest pięknie. Poza tym, istniejący już, nieformalny kod przydał się jako pierwsza testowana implementacja – testował też kod testujący ;) ale o tym za chwilę.

Co mamy do wyboru?

Krótkie przeszukanie http://cl-user.net/ i http://cliki.net/ dało, prosto z kapelusza, następujące możliwości dopasowywania wzorców:

  • fare-matcher, którego używałem we wcześniejszym projekcie i służy za pierwszą implementację,
  • cl-ppcre
  • arnesi (które, jak się później okazało, zawiera dwie implementacje dopasowywania wzorców…)
  • cl-unification

Ponad to, być może mógłbym zbadać także meta-sexp lub inne odmiany parsera Meta1, ale byłoby to nietrywialne nagięcie narzędzia do dziwacznych celów, do których nie zostało przewidziane (czytaj: zacząłem kodować, wyglądało gorzej niż Perl i nadal nie do końca robiło to, czego oczekiwałem). Najprawdopodobniej istnieje więcej bibliotek dopasowujących wzorce, ale wątpię (zwłaszcza znając już wyniki testu), żeby były znacząco wydajniejsze od powyższych. Oczywiście, sprawdzenie kolejnej implementacji to tylko dopisanie jednej funkcji, więc jeśli ktoś z Czytelników ma pomysł, proszę o komentarz.

Testowanie

Na początek oczywiście instalujemy sobie testowane biblioteki oraz split-sequence wykorzystywane przez kod testujący. Gdy już skompletowaliśmy przedmiot testów, możemy zabrać się za kod (całość opisywanego kodu można znaleźć w pliku pmtest.lisp).

Wstęp

Na początku definiujemy pakiet i parę przydatnych funkcji:

(defpackage #:pmtest
  (:use #:common-lisp #:arnesi #:fare-matcher #:cl-ppcre)
  (:shadowing-import-from #:fare-matcher #:_ #:value #:match))
(in-package #:pmtest)

(defun int (s) "Integer z napisu lub symbolu." (parse-integer (string s) :junk-allowed t)) (defun make-keyword (str) "Zwaca słowo kluczowe o nazwie STR." (intern (string-upcase (string str)) :keyword)) (defun uri-to-keyword-list (uri) "/foo/bar/quux -> (:foo :bar :quux)" (mapcar #'make-keyword (remove "" (split-sequence:split-sequence #\/ uri) :test #'string=))) (defparameter +chars+ "qwertyuiopasdfghjklzcvbnmqwertyuiopasdfghjklzcvbnm-----1234567890" ;; Celowo nie ma literki #\x, żeby łatwo generować nieprawidłowe adresy ;; Powtórzone znaki manipulują prawdopodobieństwem wystąpienia liter/cyfr/myślników "Litery do składania losowych słów") (defparameter +max-word-len+ 32 "Maksymalna długość losowego słowa") (defun randstr (&optional (length (+ (/ +max-word-len+ 2) (random (/ +max-word-len+ 2))))) "Losowy napis złożony z +chars+ długości LENGTH, domyślnie długość jest losowana od połowy do jednego +MAX-WORD-LEN+." (coerce (loop for i from 0 to length collect (aref +chars+ (random (length +chars+)))) 'string)) (defun urlify (objs) "Połącz OBJS slashami, dając urlopodobny napis" (format nil "~{/~A~}" objs))

Obiekty

Gdy piaskownica jest już przygotowana, możemy zająć się badanymi obiektami. Chcemy przecież, żeby dopasowywanie adresów znajdowało też obiekty – powinniśmy więc wymyśleć jakieś obiekty, których „znajdywanie” nie zabierałoby zbyt wiele czasu. Najprostszymi w użyciu, a przy tym względnie tanimi obiektami są symbole, i właśnie symbole będziemy znajdować, w hasztablicy lub pakiecie.

Identyfikatory numeryczne

Zacznijmy od znajdywania symboli według identyfikatorów numerycznych. Trzymać je będziemy w hasztablicy, a żeby łatwo generować nieprawidłowe identyfikatory, zakładamy, że prawidłowe są z definicji niepodzielne przez 100:

(defun numsym (i &optional (name (string '#:⚐)))
  "Symbol tworzony na podstawie numeru."
  (intern (format nil "~A~D" name i) :keyword))

(defvar *objs* (make-hash-table) "Hasztablica obiektów") (defparameter +max-id+ 100000 "Najwyższy identyfikator, wzięty z kapelusza") (defun valid-id () "Prawidłowe ID - liczba niepodzielna przez 100" (loop (let ((rv (random +max-id+))) (unless (zerop (mod rv 100)) (return rv))))) (defun invalid-id () "Nieprawidłowe ID - liczba podzielna przez 100" (* 100 (random (/ +max-id+ 100)))) (defun new-obj (&optional (id (valid-id)) &aux (rv (numsym id))) "Nowy obiekt o losowym prawidłowym ID" (setf (gethash id *objs*) rv) (values id rv)) (defun obj-by-id-string (id) "Obiekt na podstawie ID w postaci napisu lub symbolu" (gethash (int id) *objs*))

Identyfikatory tekstowe

Tekstowe identyfikatory, z angielska zwane czasem slugami, są podstawą przyjaznych urli, które uważam za niezbędne we współczesnych aplikacjach webowych. Żeby łatwo generować prawidłowe i nieprawidłowe identyfikatory, posługujemy się trikiem podobnym do poprzedniego: literka „x” nie jest używana w prawidłowych identyfikatorach. Do znajdowania obiektów za to używamy po prostu pakietu:

(defun valid-slug ()
  (randstr))

(defun invalid-slug () (concatenate 'string (valid-slug) "x")) (defpackage #:pmtest.symbols (:use)) (defun new-slugobj () (intern (string-upcase (valid-slug)) :pmtest.symbols)) (defun check-slug (slug) (find-symbol (string-upcase (string slug)) :pmtest.symbols))

Schematy adresów

Weźmy się teraz za schematy adresów. W zmiennej *SCHEMAS* trzymamy listę anonimowych funkcji, które według kilku zadanych schematów generują losowe adresy razem z wersją zdekodowaną (listą z napisem opisującym schemat i rozpoznanymi obiektami lub NIL w razie niepowodzenia), którą ma później zwrócić prawidłowo działający parser.

(defparameter +p-validate+ 8/10
  "Prawdopodobieństwo, że generowany adres będzie walidowany,
  tzn. że nieprawidłowe ID ma kończyć się niedopasowaniem całego
  adresu.")
(defparameter +p-valid+ 6/10
  "Prawdopodobieństwo, że generowany adres będzie prawidłowy.")
(defun prob (p)
  "Prawda, z prawdopodobieństwem zadanym ułamkiem P."
  (< (random (denominator p))
     (numerator p)))

(defparameter *schemas* (list ; Funkcje mają zwracać (CONS URL UNPARSED), dla ; nieprawidłowych adresów UNPARSED powinno być NIL #'(lambda (&aux (subpath (subseq (urlify (loop for i from 0 to (max 0 (- (random 8) 3)) collect (randstr))) 1)) (unparsed (list "static" subpath))) "/static/<whatever>*, wynikiem jest whatever jako string" (cons (urlify unparsed) unparsed)) #'(lambda (&aux (subpath (loop for i from 0 to (max 0 (- (random 8) 3)) collect (randstr)))) "/q/:kw*, wynikiem jest lista keywordów" (cons (urlify (cons "q" subpath)) (cons "q" (mapcar #'make-keyword subpath)))) #'(lambda (&aux (validatorp (prob +p-validate+)) (validp (prob +p-valid+)) (unparsed (list (if validatorp "vn" "n") (if validp (random most-positive-fixnum) (concatenate 'string "d" (randstr (+ 1 (random 4)))))) )) "/(n|vn)/<(valid-|)integer>" (cons (urlify unparsed) (if validp unparsed (if validatorp nil (list "n" nil))))) #'(lambda (&aux (validatorp (prob +p-validate+)) (validp (prob +p-valid+)) (objdata (when validp (multiple-value-list (new-obj)))) (unparsed (list (if validatorp "vi" "i") (if validp (first objdata) (invalid-id))))) "/(i|vi)/<(valid-|)id>" (cons (urlify unparsed) (if validp (list (first unparsed) (second objdata)) (if validatorp nil (list "i" nil))))) #'(lambda (&aux (validatorp (prob +p-validate+)) (validp (prob +p-valid+)) (unparsed (list (if validatorp "vs" "s") (if validp (new-slugobj) (invalid-slug))))) "/(s|vs)/<(valid-|)slug>" (cons (urlify unparsed) (if validp unparsed (if validatorp nil (list "s" nil))))) #'(lambda () "/xxx/whatever (invalid)" (cons (urlify (cons "xxx" (loop for i from 0 to (random 5) collect (random-string)))) nil))))

Testy poprawnościowe

Najpierw robimy testy poprawnościowe. Generowanie losowego adresu, pojedynczy test matchera, a na koniec - testowanie matchera w pętli na losowych adresach.

(defun randuri ()
  "Nowy adres wygenerowany według losowego schematu."
  (funcall (nth (random  (length *schemas*))
                *schemas*)))

(defun check-matcher (matcher &optional (uri (randuri)) &aux (rv (funcall matcher (first uri)))) "Sprawdź MATCHER na adresie URI." (values (equalp rv (rest uri)) uri rv)) (defun loop-matcher (matcher &optional (n 529)) "Check matcher for correctness in loop N times." (dotimes (i n) (when (zerop (mod i 100)) (terpri)) (write-char #\·) (force-output) (multiple-value-bind (p u r) (check-matcher matcher (randuri)) (unless p (print (list u '<-> r)))) (sleep 0.3)))

Testy wydajnościowe

Teraz możemy dojść do najważniejszego: testów wydajnościowych. Najpierw robimy listę SIZE adresów. Na tej liście każdy matcher będzie testowany REPETITIONS razy. Za każdym przebiegiem listy zapamiętujemy czas trwania próby w sekundach. Najpierw jednak, aby poznać narzut testowania, robimy pusty przebieg (zamiast matchera testujemy domknięcia kopiujące znany wynik). Dla równomierności wyników przy każdej zmianie matchera robimy pełne odśmiecanie.

Ostatecznie drukujemy podsumowanie (średnią i odchylenie standardowe czasu dla każdego matchera) i, żeby podsumowanie nie było zbyt łatwo widoczne, ;) zwracamy pełną alistę czasów (wszystkie REPETITIONS czasów przebiegu całej listy adresów dla suchego przebiegu i każdego matchera):

(defvar *matchers* ()
  "Lista wszystkich matcherów, ustawiana na końcu")



(defun time-matchers (&key
                      (matchers *matchers*)
                      (size 1000)
                      (repetitions 100)
                      &aux
                      (uris (loop for i from 1 to size
                               collect (randuri)))
                      (rv '((match-uri-dry-run))))



  ;; Pusty przebieg.
  (print (caar rv))
  #+sbcl (sb-ext:gc :full t)
  (dotimes (i repetitions)
    (let ((start (get-internal-run-time)))
      (dolist (uri uris)
        ;; Brzydko olewamy narzut na stworzenie domknięcia i
        ;; kopiowanie kilkuelementowej listy.
        (unless (check-matcher #'(lambda (u)
                                   (declare (ignore u))
                                   (copy-list (rest uri)))
                               uri)
          (error "CAN'T HAPPEN")))
      (push (float (/ (- (get-internal-run-time) start)
                      internal-time-units-per-second))
            (rest (first rv)))
      (princ (second (first rv)))
      (write-char #\Tab)
      (force-output)))



  (dolist (matcher matchers)
    (print matcher)
    #+sbcl (progn (sb-ext:gc :full t)
                  (unless (check-matcher matcher (first uris))
                    (error "Wrong answer for ~S" (first uris))))
    
    (push (list matcher) rv)
    (dotimes (i repetitions)
      (let ((start (get-internal-run-time)))
        (dolist (uri uris)
          (unless (check-matcher matcher uri)
            (error "Wrong answer for ~S" uri)))
        (push (float (/ (- (get-internal-run-time) start)
                        internal-time-units-per-second))
              (rest (first rv))))



      (princ (second (first rv)))
      (write-char #\Tab)
      (force-output)))
  



  (dolist (result rv)
    (let* ((n (length (rest result)))
           (avg (/ (reduce #'+ (rest result)) n))
           (stddev (sqrt (/ (reduce #'+ (mapcar #'(lambda (xi)
                                                    (expt (- xi avg) 2))
                                                (rest result)))
                            (1- n)))))
      (format t "~%~A: avg ~A stddev ~A"
              (first result) avg stddev)))
  
  rv)

Przedstawienie wyników w R

Żeby porządnie przeanalizować wyniki (czytaj: żebym mógł na końcu tego wpisu zamieścić kolorowy obrazek), mała funkcyjka drukująca dane w formie strawnej dla R:

(defun results-in-r (rr &aux (*print-pretty* nil) (firstp nil))
  (format t "data.frame(")
  (dolist (r rr)
    (format t "~:[~;,~] ~A=c(~{~F~^,~})"
            firstp
            (substitute #\_ #\-
                        (string-downcase (subseq (string (first r))
                                                 #.(length (string '#:match-uri-)))))
            (rest r))
    (setf firstp t))
  (format t " )"))

Do tego, dla potomności (oraz żeby samemu nie zapomnieć) wkleję funkcje R sortujące kolumny ramki danych według średniej i robiące ładny wykres:

sortbymeans <- function(frame) {
  frame[c(1,1+order(colMeans(frame[-1]),decreasing=TRUE))]
}



plotmatchers <- function(matchers) {
  plot <- barplot(mean(matchers),
                  ylab="mean time[s]",
                  ylim=c(0,max(matchers)*1.3),
                  col=rainbow(length(matchers)));
  arrows(plot, apply(matchers,2,min),
         plot, apply(matchers,2,max),
         code=3, angle=90, length=0.1);
  plot }

Kod dopasowujący

Bez dłuższego komentarza. Ten wpis i bez wprowadzenia do każdej z użytych bibliotek jest potwornie długi.

fare-matcher

(define-macro-matcher map
    #'(lambda (fun sym)
        "Przypisz do SYM wynik wywołania FUN z dopasowywanym obiektem jako argumentem"
        (check-type sym symbol)
        (multiple-value-bind (matcher vars) (pattern-matcher sym)
          (with-unique-names (item)
            (values `#'(lambda (,item)
                         (funcall ,matcher (funcall ,fun ,item)))
                    vars)))))



(define-macro-matcher map-or-fail
    #'(lambda (fun sym)
        "Przypisz do SYM wynik wywołania FUN z dopasowywanym obiektem jako argumentem.



Jeśli wynikiem wywołania FUN jest NIL, dopasowanie nie powodzi się."
        (check-type sym symbol)
        (multiple-value-bind (matcher vars) (pattern-matcher sym)
          (with-unique-names (item)
            (values `#'(lambda (,item)
                         (aif (funcall ,fun ,item)
                              (funcall ,matcher it)
                              (m%fail)))
                    vars)))))



(defun match-uri-fare (uri)
  (match (uri-to-keyword-list uri)
         ((list* :static *)
          (list "static" (subseq uri #.(length "/static/"))))
         ((list* :q (and v *))
          (cons "q" v))
         ((list :n (map #'int n))
          (list "n" n))
         ((list :vn (map-or-fail #'int n))
          (list "vn" n))
         ((list :i (map #'obj-by-id-string o))
          (list "i" o))
         ((list :vi (map-or-fail #'obj-by-id-string o))
          (list "vi" o))
         ((list :s (map #'check-slug o))
          (list "s" o))
         ((list :vs (map-or-fail #'check-slug o))
          (list "vs" o))))

cl-ppcre

(defun match-uri-cl-ppcre (uri)
  (or (register-groups-bind (rest) (#.(create-scanner "^/static/(.*)") uri)
        (list "static" rest))
      (register-groups-bind ((#'uri-to-keyword-list rest))
          (#.(create-scanner "^/q/(.*)") uri)
        (cons "q" rest))
      (register-groups-bind ((#'int i))
          (#.(create-scanner "^/n/(\\d*)") uri)
        (list "n" i))
      (register-groups-bind ((#'int i))
          (#.(create-scanner "^/vn/(\\d+)") uri)
        (list "vn" i))
      (register-groups-bind ((#'obj-by-id-string o))
          (#.(create-scanner "^/i/(\\d+)") uri)
        (list "i" o))
      (register-groups-bind ((#'obj-by-id-string o))
          (#.(create-scanner "^/vi/(\\d+)") uri)
        (if o (list "vi" o)
            (return-from match-uri-cl-ppcre nil)))
      (register-groups-bind ((#'check-slug o))
          (#.(create-scanner "^/s/([^/]+)") uri)
        (list "s" o))
      (register-groups-bind ((#'check-slug o))
          (#.(create-scanner "^/vs/([^/]+)") uri)
        (if o (list "vs" o)
            (return-from match-uri-cl-ppcre nil)))))

arnesi

Arnesi ma dwa matchery, więc sprawdzamy oba:

  • list-match
    (defun match-uri-arnesi-list-match (uri)
      (arnesi:list-match-case (uri-to-keyword-list)
        ((:static . arnesi:_)
         (list "static" (subseq uri #.(length "/static/"))))
        ((:q . ?v)
         (cons "q" ?v))
        ((:n ?n)
         (list "n" (int ?n)))
        ((:vn ?n)
         (awhen (int ?n)
           (list "vn" it)))
        ((:i ?id)
         (list "i" (obj-by-id-string ?id)))
        ((:vi ?id)
         (awhen (obj-by-id-string ?id)
           (list "vi" it)))
        ((:s ?slug)
         (list "s" (check-slug ?slug)))
        ((:vs ?slug)
         (awhen (check-slug ?slug)
           (list "vs" it)))))
    
  • match
    (defun match-uri-arnesi-match (uri)
      (arnesi:match-case (uri-to-keyword-list)
        ((:list* :static :anything)
         (list "static" (subseq uri #.(length "/static/"))))
        ((:list* :q v)
         (cons "q" v))
        ((:list :n n)
         (list "n" (int n)))
        ((:list :vn n)
         (awhen (int n)
           (list "vn" it)))
        ((:list :i id)
         (list "i" (obj-by-id-string id)))
        ((:list :vi id)
         (awhen (obj-by-id-string id)
           (list "vi" it)))
        ((:list :s slug)
         (list "s" (check-slug slug)))
        ((:list :vs slug)
         (awhen (check-slug slug)
           (list "vs" it)))))
    

cl-unification

(defun match-uri-cl-unification (uri)
  (unify:match-case (uri-to-keyword-list)
    ('(:static . _)
     (list "static" (subseq uri #.(length "/static/"))))
    ('(:q . ?v)
     (cons "q" ?v))
    ('(:n ?n)
     (list "n" (int ?n)))
    ('(:vn ?n)
     (awhen (int ?n)
       (list "vn" it)))
    ('(:i ?id)
     (list "i" (obj-by-id-string ?id)))
    ('(:vi ?id)
     (awhen (obj-by-id-string ?id)
       (list "vi" it)))
    ('(:s ?slug)
     (list "s" (check-slug ?slug)))
    ('(:vs ?slug)
     (awhen (check-slug ?slug)
       (list "vs" it)))))

Lista matcherów

(setf *matchers*
      '(match-uri-cl-unification match-uri-arnesi-match
        match-uri-arnesi-list-match match-uri-cl-ppcre
        match-uri-fare))

Wyniki

Kod uruchamiałem na swojej stacji roboczej: PLD Linux x86_64, procesor AMD64 3000+, pamięć 1,5G DDR, obciążony jak workstacja (GNOME, Opera, Emacs, trochę gnome-terminali), w kompilatorze SBCL 1.0.12. Dla 100 powtórzeń zestawu 10000 adresów, podsumowanie czasu analizy w sekundach wygląda tak:

MATCH-URI-CL-UNIFICATION: avg 1.3523299 stddev 0.08542315
MATCH-URI-ARNESI-MATCH: avg 0.50683 stddev 0.022677824
MATCH-URI-ARNESI-LIST-MATCH: avg 0.26718 stddev 0.019077396
MATCH-URI-CL-PPCRE: avg 0.12663995 stddev 0.015561882
MATCH-URI-FARE: avg 0.2772199 stddev 0.017097805
MATCH-URI-DRY-RUN: avg 0.0021599988 stddev 0.0032867037

Po dłuższej walce z R (swoją drogą, w takich sytuacjach ESS pozwala zachować zdrowie psychiczne) i wypoceniu trzech wklejonych wcześniej linijek kodu, dostałem taki obrazek:

Wykres

CL-Unification trochę psuje skalę, więc pomińmy pierwszą kolumnę:

Wykres

Tutaj zastanawia jedna rzecz: wszystkie biblioteki, poza CL-PPCRE, dopasowują listy, więc wymagają wstępnego etapu konwersji adresu na listę symboli. Sprawdźmy zatem, jak wpłynie na czas wykonania suchego przebiegu dodanie tego etapu konwersji… oszczędzę kodu (w końcu to jest dopisanie jednej linijki do tego, co już było powyżej) i przedstawię poprzedni wykres z wklejoną dodatkową kolumną i pominiętym arnesi:match. Pusty przebieg z konwersją adresów na listę symboli opisano dry_keywords:

Wykres

Bingo! Co więcej, arnesi:list-match i fare-matcher są, po odjęciu dzielenia adresu na listę, jeszcze szybsze niż samo CL-PPCRE. Niestety, jeżeli nie ominął mnie jakiś istotny element funkcjonalności (Dear Lazyweb…), dla tych bibliotek taki etap jest konieczny. Może jednak warto byłoby sprawdzić Meta, albo Meta-sexp, nie wymagające konwersji? Nie wydaje mi się. Nie dość, że albo będzie wyglądać gorzej niż Perl, albo będzie potwornie rozwlekłe, CL-PPCRE jest szeroko znane i bardzo dobrze przetestowane. Nie wydaje mi się, żeby miało okazać się wąskim gardłem – a jeżeli, po to właśnie tworzę osobny komponent do analizy adresów, żeby móc go w razie potrzeby wymienić.

Na zakończenie, jeszcze jeden kolorowy wykres: zbliżenie zwycięskiego CL-PPCRE na tle suchego przebiegu.

Wykres

To tyle. Teraz można niecierpiwie oczekiwać na nowy, błyszczący, pachnący mocą i olejem maszynowym rozgałęźnik do adresów WWW dla Hunchentoota (a jak będzie zainteresowanie, to może i będzie się dało go używać z innymi serwerami). Stay tuned. Wytrwałym gratuluję cierpliwości,

--Maciek

1 Swoją drogą, artykuł zdecydowanie wart przeczytania.

Spotkanie lispowe -- sobota 17 maja, Cafe Kulturalna, 19.00

Miejsce i czas spotkania zostało ostatecznie ustalone: sobota, 17 maja 2008, Cafe Kulturalna, 19.00.

Serdecznie zapraszam wszystkich miłośników i sympatyków CL (i nie tylko).

Rejestracja oczywiście nie jest obowiązkowa, ale Oiola jest taka fajna, a ja mam konto testowe, więc: co nam szkodzi. Poza tym, jakby dziwnym trafem się okazało, że jest więcej niż 6 chętnych (taki stolik zamówiłem), da mi to możliwość zmiany rezerwacji.

Spotkanie lispowe

Spotkanie lispowe odbędzie się najprawdopodobniej w sobotę 17 maja w Warszawie.

Cel spotkania: pogadać o lispie i poznać się nawzajem, przy piwie.

Bardzo zainteresowanych o zostawienie komentarza do niniejszego posta -- chciałbym móc oszacować na jak dużo ludzi możemy liczyć (czy trzeba rezerwować jakieś stoliki itp., czy też zmieścimy się wszyscy przy barze).

Dalsze informacje (np. na temat miejsca i godziny spotkania) będą się pojawiać jako wpisy do tego bloga.

Update: Choć spotkanie ma być w założeniu lispowe, serdecznie zapraszam również miłośników innych fajnych języków, takich jak Haskell, Joy, Forth, Scheme, OCaml, J, Dylan...

Refleksje po ECLM i pomysł spotkania w Warszawie

  • Na ECLM 2008 bardzo mi się podobało. Po namyślę muszę jednak z całą mocą stwierdzić, że to nie prezentacje (z których część, przyznaję, była faktycznie ciekawa) stanowiły główną wartość konferencji.
    • Powiedzmy sobie szczerze, wykład jest bardzo kiepską metodą przekazywania wiedzy (tym gorszą jest godzinny wykład w dwugodzinnym bloku, przez ponad osiem godzin z krótkimi przerwami). Nie możemy zerknąć dwie strony wcześniej aby przypomnieć sobie definicję, której nie zapamiętaliśmy. Nie możemy przeskoczyć fragmentu, który znamy.
      • Nie możemy porobić czego innego kiedy stwierdzimy, że temat jednak nas nie interesuje.
    • Znakomita większość referatów byłaby dużo łatwiejsza w odbiorze w formie pisemnej (z jedynym chyba wyjątkiem programu do komponowania muzyki).
  • Dlaczego w takim razie wróciłem z ECLM taki zadowolony?
    • Oto mam namacalny dowód na to, że Common Lisp jednak żyje. Średnia wieku, wbrew moim oczekiwaniom, wcale nie była taka wysoka.
    • Niby zawsze wiedziałem, że na świecie jest więcej niż 3 programistów Lispa (byłem pracowałem przecież w firmie, która utrzymywała się w sporej części z projektów lispowych), ale kiedy koledzy z pracy robią najbardziej kretyńskie, anegdotyczne żarty lispowe (podmienianie nawiasów na klawiaturze, takie tam), to można zwątpić.
      • Szczególnie, że te docinki były tak naprawdę robione przez tych najsensowniejszych kolegów, którzy wiedzieli, że Lisp to dużo nawiasów... W pozostałych wypadkach można było liczyć na co najwyżej na dość tępe co? bądź pomylenie Lispa z OCamlem...
      • Aż boję się pomyśleć, co muszą czuć na co dzień miłośnicy nie Common Lispa, a takiego Dylana... Użytkowników Dylana naprawdę jest nastu. Jedyne aplikacje napisane w Dylanie to serwer http, silnik wiki i sniffer. No i, rzecz jasna, aż dwa kompilatory.
      • Z drugiej strony, Dylan Hackers zajęli kilka razy bardzo wysokie miejsca w ICFP (m.in drugie miejsce i nagroda jury ICFP 2005). Chapeau bas!
    • Bardzo poprawiło mi samopoczucie pogadanie z ludźmi, których również interesują takie rzeczy jak to, dlaczego Slime z cvs-u dziwnie działa, jak zintegrować Slime'a z bibliotekami do testów, dlaczego w niektórych kontekstach iterate działa wolniej niż loop. Kolacja po wykładach była najmilszą częścią ECLM-u.
      • Momentami mi się nawet wydaje, że te wykłady to trochę przeszkadzały. Że najfajniej by było po prostu zgromadzić lispiarzy w jednym miejscu z dobrymi zakąskami i kawą i pozwolić im po prostu porozmawiać. A ludziom niewymęczonym przez 8 godzin by się milej rozmawiało.
      • Oczywiście zdaję sobie sprawę, że prelekcje były niezbędne. Nikt przy zdrowych zmysłach by nie przyleciał ze Stanów ot tak sobie na pogaduchy o lispie. Może za wyjątkiem Kenny'ego Tiltona. ;-)
  • Pomyślałem sobie, że strasznie fajnie byłoby kiedyś móc zorganizować taką imprezę jak ECLM w Polsce. Polska jest bardzo dobrym miejscem do organizowania konferencji: jest bardzo ładnie, stosunkowo tanio, knajpy są otwarte do późna w nocy.
    • Wydaje mi się, że sporo fajnych ludzi by przyjechało. Na pewno grupa z Holandii. Tak samo Niemcy (w końcu to blisko). Liczyłbym na Pascala Costanzę. Do kroćset -- skoro dużo ludzi przyjechało ze Stanów do Amsterdamu, dlaczego nie mieliby do Polski?
    • W końcu, dlaczego nie? W naszym kraju jest całkiem dużo programistów Common Lisp. Na ECLM można było przecież spotkać mniej niż połowę wszystkich polskich programistów Lispa.
    • Oczywiście, wielka konferencja lispowa w polsce to póki co tylko marzenia...
  • Schodząc bardziej na ziemię: pomyślałem sobie, że byłoby strasznie fajnie urządzić jakieś niewielkie spotkanie lispowe w Warszawie.
    • Nie mówię o niczym specjalnie ambitnym, z prelekcjami i rzutnikiem. Mam na myśli raczej niezobowiązujące wyjście na piwo.
    • Można by pogadać, poznać się, dowiedzieć się co robimy, jakie są nasze projekty związane z Lispem...
  • Wstępnie proponuję spotkać się w środę 14 maja sobotę 17 maja.
    • Termin 14 17 maja wybrałem głównie dla ustalenia uwagi (jest na tyle daleko, że większość ludzi nie ma jeszcze planów, a na tyle blisko, aby ronienie planów miało jakikolwiek sens). Zależałoby mi na pojawieniu się jak największej ilości osób. Mam nadzieję, że na drodze negocjacji uda się nam ustalić optymalny termin. Środa to dobry dzień ze względu na stosunkowo niewielkie zatłoczenie knajp.
    • Trzeba jeszcze ustalić godzinę i miejsce. Lokal musi być niezbyt zatłoczony i cichy, aby dało się swobodnie pogadać. (Mam kilka swoich typów.)
    • Piszcie kiedy Wam pasuje w komentarzach
    • I powiedzcie o idei swoim znajomym lispiarzom, którzy nie znają tego bloga. (Lub którzy po półrocznej przerwie spisali go byli na straty.)
  • A w przyszłości... Może coś ciekawego się z tego rozwinie?

UCW umarło, niech żyje UCW?

UCW to przykład biblioteki, która chyba zawiodła pokładane w niej nadzieje. Kiedy kilka lat temu pojawił się Lispowy framework do tworzenia aplikacji sieciowych, oparty co więcej na kontynuacjach i komponentach, wzbudził zrozumiały entuzjazm. Przez pewien czas wydawało się, że oto mamy lispowy killer app. Niestety, nadzieje owe zostały dość mocno ostudzone przez dość specyficzny sposób rozwijania UCW. Częste zmiany niszczące kompatybilność wstecz, forkowanie bibliotek, nietypowy styl kodowania, brak jakiejkolwiek sensownej dokumentacji (dość powiedzieć, że kiedy zainteresowałem się UCW, istniały trzy tutoriale, wszystkie niekompletne i przestarzałe), chyba: brak jasno określonej wizji przyszłości…To wszystko raczej zniechęcało do oparcia swojego projektu (niezależnie od tego, czy miało to być drugie Google, czy strona domowa hackera) na tej bibliotece.

Ostatnimi czasy wydawało się, że UCW jest praktycznie zeżarte przez entropię. Większość użytkowników się dość mocno zniechęciła, a nowych nie mogło być z powodu wspomnianego już braku dokumentacji. Kiedy rozmawiałem o tym na ECLM 2008 z Marco Baringerem, wyraźnie stwierdził, że nie planuje pisać żadnej dokumentacji, ponieważ nie ma na to czasu (o czym wspomniałem w ostatniej notce).

Tym niemniej, 29 kwietnia pojawił się promyczek nadziei. Drew Crampsie w wiadomości do grupy dyskusyjnej UCW poinformował o tym, że przejął on od Marco projekt, i że zamierza nakierować go z powrotem na właściwe tory. Zadeklarował, że będzie dość dużą wagę przywiązywał do zasad, które do tej pory wydawały się ideologicznie obce UCW. Chodzi między innymi oto, aby

  • nie wprowadzać z dnia na dzień i bez ostrzeżenia zmian niszczących kompatybilność wstecz
  • wszystkie zmiany były udokumentowane i obwarowane testami
  • nie stosować niekoszernej składni (reader macros, defclass* etc.)
  • ogólnie starać się przestrzegać konwencji uznawanych przez większość społeczności.

Jednocześnie Attila Lendvai ogłosił rozwój własnej gałęzi UCW już pod nową nazwą. Zapowiedział, między innymi, porzucenie zależności od Arnesi (która u wielu wywołuje mieszane uczucia: Gabriele ze Streamtechu bladł na samo wspomnienie).

W każdym razie, wokół UCW zaczęło się wreszcie coś dziać. Czy to znaczy, że za kilka miesięcy zamiast jednego jeszcze niedojrzałego (mówię o Weblocks) i jednego mocno przejrzałego frameworka lispowego będziemy mieli aż trzy mocne frameworki oparte na kontynuacjach i komponentach? Brzmi jak pierwszy krok do lispowego world domination plan. (Następnym będzie rzecz jasna przepisanie najlepszego frameworku do Arca).

ECLM 2008

  • ECLM 2008 w Amsterdamie: 16 godzin w jedną stronę pociągiem, 8 godzinnych wystąpień, 96 miłośników lispa z 17 krajów, 3 przedstawicieli naszego kraju: Maciek (of CL-Librarian fame), Daniel (of Poliqarp fame), oraz niżej podpisany.
  • Nie piszę dokładnego sprawozdania: już ubiegł mnie Daniel. Ograniczę się do kilku luźnych uwag.
    • Wystąpienie Stefana Richtera z freiheit.com technologies gmbh (Hamburg, Niemcy), Using Common Lisp for large Internet systems to był najprawdziwszy HUD (pozytywny, pro-lispowy odpowiednik FUD. O ile uwagi odnoszące się do architektury umożliwiającej tworzenie skalowalnych serwisów internetowych były ciekawe i sensowne, o tyle już podawanie Weblocksów jako przykładu frameworku sieciowego, który ma się łatwo skalować jest nieporozumieniem.
      • Webclocksy, choć strasznie fajne, opierają się na kontynuacjach (zaimplementowanych w cl-cont) i domknięciach. Dopóki nie będzie dobrej (czyli taniej i szybkiej) metody serializacji i deserializacji domknięć i kontynuacji, dopóty Weblocksy będą ograniczone do jednego obrazu lispowego. Choć oczywiście można kombinować (sticky sessions i takie tam), skalowanie Weblocksów nie wydaje się trywialne.
      • Z drugiej strony, chciałoby się, aby jak najwięcej tego HUD-u dotarło do głównego nurtu IT. Z tego, że Stefan podaje kiepskie przykłady nie wynika przecież, że Common Lisp per se źle się skaluje. Stefan ma spore doświadczenie jeśli chodzi o duże aplikacje internetowe: freiheit.com robiła m.in. księgarnie internetowe libri.de i buch.de, więc jego sympatia wobec Common Lispa powinna stanowić pewien argument dla firmy rozważającej jego użycie. (Przemilczałbym, że freiheit.com większość swoich projektów pisze póki co… w Javie, gdyby mnie sumienie potem nie gryzło.)
    • Bardzo ciekawie mówił Juan José García-Ripoll, autor ECL. Mimo słówka embedded w nazwie, ECL jest pełnoprawną, zgodną ze standardem implementacją Common Lispa.
      • Kompilacja do C i łatwiejsze korzystanie z bibliotek napisanych w tym języku to jest coś. Szczególnie w świecie, który zdaje się nieszczególnie przychylny aplikacjom czysto lispowym.
      • Wyjątkowo interesującym projektem jest XEmacs z osadzonym ECL-em. W pierwszej fazie pozwoliłoby to pisać dla niego rozszerzenia nie tylko w Emacs Lispie, ale również w Common Lispie. W drugiej fazie Emacs Lisp mógłby zostać przez Common Lisp całkowicie zastąpiony. Czy nie wydaje się to Wam piękne? Emacs, nie dość, że CL-owy, to jeszcze z rozszerzeniami kompilowanymi do kodu maszynowego! (Osobiście wolę Emacsa od XEmacsa, ale w takiej sytuacji nie zastanawiałbym się długo nad przesiadką.)
      • Juan wydawał się dość mocno zasmucony niewielkim zainteresowaniem ECL ze strony użytkowników. Cóż, prawda jest taka, że ECL nie należy do bestyjek łatwych w obsłudze (nieraz już sama instalacja przysparza kłopotów). Jednak warto się ECL-em zainteresować. Odnoszę wrażenie, że Juan tak się ucieszy z objawów zainteresowania projektem w postaci zgłoszonych problemów, że będzie je bardzo szybko poprawiał, a ECL stanie się łatwiejszy w obsłudze. Daniel zgłosił mu problem z kompilacją jeszcze na kolacji…
    • Bardzo podnoszą na duchu dwie lispowe sucess stories.
      • InspireData, czyli narzędzie dla szkół średnich do wizualizacji danych, doskonale się sprzedaje i robi naprawdę świetne wrażenie. Choć raczej nie należę do grupy docelowej produktu, sądzę, że w niektórych sytuacjach skorzystanie z niego byłoby nie bez pożytku.
      • Norweski House Designer służy z powodzeniem tamtejszym architektom do walki z uporczywymi błędami, powtarzającą się pracą i zmieniającymi się regulacjami prawnymi (w końcu to Skandynawia). Architekt dostarcza programowi szkic budynku, a program już zadba o ilość i rozmieszczenie kontaktów, okna, czy po mieszkaniu da się poruszać na wózku inwalidzkim, itp.
        • Co ciekawe, decyzję o użyciu Common Lispa przeforsował w latach dziewięćdziesiątych zarząd firmy, mimo zdecydowanego oporu programistów (!). Pozostałością po owym pierwszym buntowniczym zespole są użycia 'yes i 'no zamiast t i nil, pozostawione w niektórych miejscach kodu z powodów sentymentalnych. A może jako memento?
    • Trochę zawiodło mnie wystąpienie Kenny’ego Tiltona. Owszem, było śmieszne (na całe szczęście, ponieważ poprzednie wystąpienie było niestety nudne jak diabli), choć z powodu dygresji (dość zabawnych) nie zdążył powiedzieć zbyt dużo o właściwym temacie prezentacji, czyli o Cells.
  • Konferencje na temat niezbyt popularnych języków programowania to nie tylko wystąpienia: to także możliwość poznania środowiska. Pod tym względem ECLM2008 nie zawiodło. Można było pogadać z Edim Weitzem, Danem Weinrebem (założycielem Symbolics), Marco Baringerem (nb. nie spodziewajcie się sensownej dokumentacji do UCW w najbliższym czasie)... A także poznać chorwackich hackerów Lispa, lub hackerów Dylana.
  • Konwencję pisania w punktach bezczelnie zgapiłem od netto. Choć przed netto już tak pisał Wittgenstein (ale on bloga nie prowadził).
  • Na koniec metauwaga: pogłoski o śmierci tego bloga są mocno przedwczesne (choć niewątpliwie uzasadnione). W najbliższym czasie postaram się poświęcać mu trochę więcej uwagi.

HOP do przodu

Dawno temu fajność (którą definiuję jak to, o co najlepiej kłócić się we flejmłorach) danego języka programowania można było ocenić na podstawie środowiska służącego do budowania interfejsu graficznego. Nie wiem, gdzie na skali fajności wedle tego kryterium sytuuje się Scheme: ani nie mam zbyt dużych doświadczeń jeśli chodzi o projektowanie interfejsów graficznych, ani nie znam się specjalnie na Scheme.

Współcześnie, w erze Web 2.0, najlepszym probierzem fajności jest framework do pisania aplikacji sieciowych. To jest już dużo bliższe moim zainteresowaniom, i nawet mimo mojej słabej znajomości Scheme jestem w stanie ocenić: Scheme rządzi. Schematycy nie muszą się nawet obawiać flame warów ze strony miłośników Ruby-on-Rails czy Django: są w stanie odnieść w nich zwycięstwo.

Czym jest zatem wunderwaffe Scheme? Otóż, jest nią HOP. HOP jest językiem od budowania wszelkiego rodzaju aplikacji sieciowych nadbudowanym nad Scheme (a konkretnie, dialektem używanym przez Bigloo, od którego HOP jest zależny), z kangurkiem w logo.

Co daje nam HOP? Przede wszystkim, dostajemy dwa podjęzyki. Pierwszy służy do generowania HTML-a (w stylu podobnym do Common Lispowego yalclml). Drugi generuje JavaScript, podobnie do CL-owego Parenscriptu.

Po drugie, i to jest o wiele ciekawsze, dostajemy dwa makra Readera. ~ informuje HOP, że chcemy przejść od generowania HTML-a do JavaScriptu. Drugie, $, służy z kolei do poinformowania JavaScriptu, że chcemy wykonać dany kod po stronie serwera. Oba makra możemy dowolnie zagnieżdżać. Z rzeczy znanych najbardziej przypomina to użycie ` i ,. Jeżeli dobrze rozumiem, HOP sprytnie tłumaczy wywołania $ na JavaSciptowe XMLHttpRequest. Osobiście, nie mam nic przeciwko pisaniu w JavaScripcie (w końcu jest to bardzo miły, lispowaty w głębi duszy język, wtłoczony w brzydką składnię i z reputacją popsutą przez legiony kiepskich programistów i niekompatybilne implementacje), jednak przyjęte przez HOP podejście bardzo upraszcza logikę aplikacji.

Prócz tego, w prezencie dostajemy całkiem spora kolekcje różnych widgetów, w tym sporo ajaksowych, takich jak drzewka, składnia wiki albo ekrany logowania. Można zobaczyć je w działaniu oglądając demo (które robi duże wrażenie i, ku mojemu zaskoczeniu, nawet nie zawiesza mojego Firefoksa).

Do dużych zalet HOP-a w stosunku do konkurencji (czyli np. serwera PLT Scheme albo Common Lispowego UCW) należy to, że udało mu się oprzeć dość dużej w przypadku Scheme pokusie ostensywnego oparcia się na kontynuacjach. Tak, wiem, kontynuacje fajne, ale jest to technika dość hermetyczna i niekoniecznie poprawia czytelność kodu (hm, czy nie są one przypadkiem stosunkowo niskopoziomową abstrakcją nad GOTO?).

Jeśli chodzi o wady HOP-a, jest nią niewątpliwie zależność od konkretnego kompilatora (Bigloo). Wiąże się to między innymi z niestandardowym systemem modułów i nie-CLOS-owym systemem obiektowym. (Jako zapalony miłośnik Common Lispa chętnie dodałbym, że najgorsze jest to, że jest napisany w niewłaściwym języku programowania, jednak powstrzymam się przez przyzwoitość ;-).)

Dawno nie miałem momentu, abym poważnie zastanawiał się nad przesiadką (przynajmniej na jakiś czas) z Common Lispa na Scheme. HOP wydaje się być dużo wygodniejszy niż trójka Hunchentoot, HT-AJAX i YACLML (JavaScript piszę póki co ręcznie, aby się w nim wyćwiczyć), z której obecnie korzystam. Powstrzymuje mnie póki co kilka rzeczy. Po pierwsze, nie do końca orientuje się w różnych systemach makr i pakietów dla Scheme. Po drugie, nie znalazłem środowiska programistycznego które byłoby w jednej piątej tak wygodne jak Slime (istnieje Slime48, ale działa on tylko w spatchowanej wersji Scheme48).

Ech, nie ma nikt ochoty tego przeportować na Common Lisp?

Żółta łódź podwodna

Projekt, nad którym pracowałem od dobrych kilku tygodni dostał wreszcie miejsce na Common-Lisp.net. Zrobiłem mu już nawet stronę internetową. Submarine, bo o tym właśnie mowa, jest biblioteką lispową, którą można zaklasyfikować gdzieś pomiędzy Postmodernem a systemami do object persistency, takimi jak Elephant. Celowo nie rozpisuję się tutaj na temat funkcjonalności Submarine’a, bo nie chcę tu bez sensu powtarzać tego, co napisałem na stronie.

Pracując w Streamtechu nad projektem dla Haskiego Urzędu Miejskiego, z dużą przykrością odkryłem, że Postmodern nie obsługuje foreign keys. Niezniechęcony tym wziąłem się za pisanie patcha, który miał to naprawić. W międzyczasie przyszło mi do głowy jeszcze kilka udogodnień których brakowało Postmodernowi. Na przykład, przy próbie ponownego utworzenia tabeli już istniejącej w bazie danych Postmodern głośno krzyczy. A mógłby zamiast tego sprawdzić, czy jej definicja jest zgodna ze specyfikacją podaną w kodzie lispowym i zaproponować za pomocą restartów odpowiednie poprawki.

To co napisałem działało, bez dwóch zdań. Gdy jednak przyjrzałem się krytycznie swojemu kodowi, zdałem sobie sprawę, że niekoniecznie będzie on miły w pielęgnacji. Makro DEFTABLE z moimi dodatkami rozrosło się do rozmiarów potworka (ponad 150 linii). Nawet trywialne poprawki wymagały dużego namysłu i wysiłku. Słowem, rokowania nie były zbyt dobre. Jeżeli nawet mnie (autora) przerażała myśl o pielęgnacji tego kodu, jak mógłbym oczekiwać, że ktoś inny (w tym wypadku Marijn) miałby się na to zgodzić?

W Postmodernie są pewne rzeczy, które mi się nie podobają. Model DAO przyjęty przez Marijna wydawał się za ciasny jak na moje zamierzenia. Klasy, których zadaniem był dostęp do tabel nic praktycznie o nich nie wiedziały. Co gorsza, informacja o strukturze danych była przechowywana w dwóch różnych miejscach (w klasie DAO i w obiekcie tabeli). Próba zmiany tego wymagałaby praktycznie przepisania połowy biblioteki i prawie na pewno złamałaby kompatybilność wstecz, czego nikt by nie chciał. Zatem, napisanie nowej biblioteki wydało się jedynym sensownym wyjściem.

Zamiast tworzyć barokowe makra zdecydowałem się tym razem pobawić MOP-em. Z perspektywy czasu mogę powiedzieć, że to była dobra decyzja. Po pierwsze, pozwoliło mi to korzystać ze wszystkich dobrodziejstw CLOS-a. Kod podzielony ładnie na metody jest dużo łatwiejszy w zrozumieniu, testowaniu i poprawianiu. Po drugie, wiele rzeczy mogłem zrobić w dużo naturalniejszy sposób. Na przykład nie musiałem czyścić definicji klas z keywordów nie występujących w normalnych definicjach slotów, jak np. :UNIQUE etc. Po trzecie, całe przedsięwzięcie stało się dużo ciekawsze z poznawczego punktu widzenia :)

Zapraszam więc wszystkich do rzucenia okiem na Submarine. Za wszystkie komentarze i uwagi z góry dziękuję.

Atak postmoderny

Jedną z rzeczy, których zdecydowanie nie lubię jest SQL. Nie chodzi tu, Boże broń, o jakąś irracjonalną niechęć wobec relacyjnych baz danych: używam ich, lubię je i doceniam (choć czasami z dużą nadzieją patrzę na projekty typu CouchDB). Chodzi o składnię.

Jeżeli chcemy dobrze mówić w jakimś języku obcym (mam na myśli języki naturalne, a nie programowania), musimy się nauczyć myśleć w tym języku. Chodzi o to, aby móc się skupić na wyrażaniu myśli (co bywa nietrywialne), a nie męczyć się przekształcając wpierw struktury składniowe jednego języka w struktury drugiego.

Gdy programujemy jest bardzo podobnie. W Lispie trzeba nauczyć się myśleć z notacją prefiksową i nawiasami, w Prologu za pomocą relacji (na początku to boli, oj boli). I nie jest to w zasadzie trudne. Gorzej jednak, gdy mamy pracować naraz w dwóch różnych językach (pracując nad rozwiązaniem tego samego problemu). Nie wiem, czy próbowaliście kiedyś tłumaczyć z jednego języka obcego w drugi. Zaręczam Wam, że nawet jak znacie oba języki bardzo dobrze jest to bardzo duże wyzwanie i, pomijając nawet już fakt, jak bardzo jest to męczące, łatwo dostać od tego kręćka.

Kiedy programujemy w swoim ulubionym języku i nagle musimy wstawiać kawałki SQL-a, jest bardzo podobnie. Nie wiem, jak u Was, ale u mnie poważnie zakłóca to normalny proces myślowy przy rozwiązywaniu problemu. Co gorsza, nigdy nie mogę składni SQL-a zapamiętać, i bez Gógla jako ściągawki byłbym kompletnie zagubiony.

Na szczęście, Lisp się doskonale nadaje do robienia domain specific languages i w związku z tym lispowe biblioteki do obsługi baz danych często takowe dostarczają. Osobiście, zdarzyło mi się pracować z dwiema takimi bibliotekami: CL-SQL i Postmodern. Pozwolę je sobie pokrótce przedstawić.

CL-SQL

CL-SQL jest dość ambitnym projektem, który już teraz obsługuje dość pokaźną liczbę platform i systemów baz danych. API jest wzorowane na CommonSQL (stworzonym dla Lispworks). Oprócz dsl, dostarcza również tzw. view classes, czyli klasy lispowe mapujące się do tabel w bazie danych.

Niestety, CL-SQL nie jest idealny. Po pierwsze, bywa dość trudny w instalacji i zmuszeniu do działania. Po drugie, kilka razy spotkałem się z sytuacją, że makro do-query nie robiło tego, co powinno (choć oczywiście mogło to wynikać z niezrozumienia jego istoty). Po trzecie, lispowa składnia dla SQL-a używa nawiasów kwadratowych. To ma dwie wady. Primo, dziwnie to wygląda (choć to akurat jest kwestią gustu). Secundo, przed jej użyciem trzeba ją włączyć, za pomocą makra readera. Prócz tego, pamiętam jakieś problemy z zagnieżdżonymi SELECT-ami (być może już to poprawiono). Summa summarum, choć CL-SQL wykonał to, czego od niego oczekiwałem, nie mogę powiedzieć, aby korzystanie z niego było wielką przyjemnością.

Postmodern

Moje pierwsze zetknięcie z Postmodernem nie było zbyt pozytywne. Jako student filozofii trenowany na filozofa analitycznego zostałem wychowany w głębokiej niechęci wobec wszelkiej postmoderny (przynajmniej filozoficznego autoramentu: Eco (jako pisarz), Themerson, a nawet Borges byli OK). Zatem, już sama nazwa nie wzbudzała mojej sympatii. Rzut okiem na przykłady, gdzie pojawiało się nazwisko Foucault, upewnił mnie, że moje podejrzenia są słuszne. Choć Postmodern w przeciwieństwie do wspomnianego wyżej CL-SQL-a instalował się bezboleśnie, z przyczyn emocjonalnych postanowiłem pomęczyć się jeszcze z tym drugim.

Gdy rozpocząłem staż w Streamtechu w Hadze okazało się, że Postmodern powstał właśnie w tej firmie. Jego autorem jest były pracownik firmy, Marijn Haverbeke, którego nb. miałem okazję poznać osobiście. Używany do projektów wewnątrz firmy, po pewnym czasie został wyołpensołrsowany.

Postanowiłem mu zatem dać druga szansę (choć może bardziej odpowiednie byłoby stwierdzenie, że „postanowiono, że dam mu drugą szansę”). Gdy odrzuciłem swoje filozoficzne przekonania, okazało się, że Postmodern jest biblioteką bardzo przyjemną w korzystaniu (szczególnie po moich trudnych doświadczeniach z CL-SQL). Umożliwia wzniesienie się na nieco wyższy poziom abstrakcji, ale pod żadnym względem do tego nie zmusza (np. można bezproblemowo mieszać wyrażenia lispowe ze stringami zawierającymi tradycyjnego SQL-a). Generalnie, dopóki będę mógł pracować z PostgreSQL-em (Postmodern innych systemów baz danych nie wspiera), nie będę tęsknił za CL-SQL-em.

Podobnie jak CL-SQL, Postmodern dostarcza abstrakcję do obsługi tabel SQL-owych za pomocą klas CLOS-owych (są to tzw. DAO, czyli database access objects). Niestety, DAO są dość ograniczone. Na przykład, nie wspierają foreign keys, co jest dość dużym problemem. Jako jego rozwiązanie, napisałem nawet bibliotekę do czegoś w rodzaju lekkiego object persistency, Submarine. Ale o tym będzie w następnym poście (lub dopiero jak napiszę dokumentację).

Książki o Common Lispie

GDR! zapytał mnie w komentarzu, jakie książki polecam do nauki Lispa. Już zabierałem się do odpisywania, gdy zdałem sobie sprawę, że prędzej czy później będę chciał porządnego posta na ten temat napisać. A skoro są ludzie, którzy chcą go przeczytać, nie ma najmniejszego sensu, aby musieli czekać.

Na wstępie: polskie książki o Common Lispie nie istnieją (z tego co wiem). A z tutoriali, niestety, Lispa się trudno nauczyć. Zatem, polski miłośnik Lispa musi być przygotowany na czytanie w języku Jerzego Waszyngtona. (Istnieje jeden chlubny wyjątek, o którym poniżej.)

Do rzeczy. Oto lista książek, które moim zdaniem są najbardziej pożyteczne:

David Touretzky, Gentle Introduction to Symbolic Computation

Osobiście, moja ulubiona książka o Lispie. Adresowana również do ludzi nie mających zielonego pojęcia o programowaniu, krok po kroku (ale nie żółwim tempem!) uczy Lispa i dobrych manier algorytmicznych. Przykłady są ciekawe i zabawne, z robotami i smokami. Ja natrafiłem na nią w odpowiednim momencie: byłem w trzeciej klasie liceum (w starym systemie, w klasie humanistycznej) i właśnie nauczyłem się z szemranych tutoriali programować w PHP. Nie wiem, czy bym się nie stoczył, gdyby nie dobry pan Touretzky ;-) (Oczywiście, ta książka stanowczo nie jest dla ludzi, którzy się zainteresowali Lispem bo ponoć wygodnie się w nim pisze kompilatory.)

Paul Graham, ANSI Common Lisp

Mała, przyjemna książeczka. Choć ukazała się w 95 roku, nie zestarzała się jakoś strasznie (choć być może świadczy to o niezbyt dużym, hm, tempie rozwoju standardu Common Lisp). Wyjątkowo użyteczna jest ściąga z funkcji wchodzących w skład standardu -- uporządkowane są tematycznie, a nie alfabetycznie, co bardzo pomaga. Szybko się czyta i nawet wciąga. Kiedy po dwóch latach nieużywania Lispa i chciałem go sobie przypomnieć, ta książka była niezastąpiona. Niestety, nie ma wersji internetowej (ja przynajmniej nie znalazłem).

Książkę, notabene, bardzo chwali autor poprzedniej opisywanej przeze mnie pozycji.

Peter Norvig, Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp

Monumentalne dzieło obecnego szefa działu badań w Góglach. Niby oficjalnie nie jest o Lispie, a o sztucznej inteligencji, ale trudno o lepsze źródło przykładów dobrze napisanych programów w Lispie. Tutaj można nauczyć się, co to jest prawdziwe the Lisp way of doing things. PAIP bardzo wyróżnia się na tle “klubu książek o nudnych pierwszych rozdziałach“ --- w drugim rozdziale, tuż po rudymentarnym wprowadzeniu do Lispa, zamiast typowego rozszerzonego “Hello, World!” możemy przyjrzeć się z bliska działającej implementacji Elizy.

Ksiązka jest gruba, droga jak sto diabłów i nigdy nie miałem egzemplarza na tyle długo, aby go w całości przeczytać, czego bardzo żałuję.

Harold Abelson, Jay Sussman, Struktura i interpretacja programów komputerowych

Na swiecie znana jako SICP albo the wizzard book. Co prawda nie traktuje o Common Lispie, a o Scheme, i nie zajmuje się inżynierią oprogramowania, a informatyką z punktu widzenia teoretycznego, jednak powinna sprawić przyjemność nawet najbardziej praktycznie nastawionemu rzemieślnikowi (o poszukiwaczach oświecenia nie wspominając). Ta książka naprawdę poszerza horyzonty. Dodatkowo, w Internecie można znaleźć wykłady video autorów. Polskie wydanie ukazało się nakładem WNT, i zostało rewelacyjnie przetłumaczone (co nie zdarza się często książkom informatycznym).

Do zeszłego bodaj roku stanowiła podstawę wykładu wstępu do programowania na MIT... Teraz przeszli na Javę (świat schodzi na psy).

User-Level Language Crafting

Właściwie nie książka, a artykuł. Świetne wprowadzenie do MOP-u, gęste, ale strawne. Czego nie można powiedzieć niestety o The Art of the Metaobject Protocol, który nawet bardzo zmotywowanego czytelnika może zanudzić. Na szczęście, do jedzenia chleba nie trzeba znać technologii jego produkcji :-) (Choć przeczuwam, że prędzej czy później będę zmuszony po tę książkę sięgnąć.)

Inne książki

Za bardzo dobre (szczególnie jeśli chodzi o rozdziały poświęcone makrom) uchodzi On Lisp Paula Grahama, choć ja akurat nie miałem okazji jej przeczytać. Ludzie chwalą Practical Common Lisp Petera Seibla. Mnie, gdy czytałem ją równolegle z ANSI Common Lisp, nieco znudziła. Nie mam niestety przy sobie swojego egzemplarza, więc nie jestem w stanie powiedzieć, skąd dokładnie wzięły się moje takie a nie inne wrażenia. Na pewno jednak warto przeczytać sobie niektóre rozdziały, jak na przykład rozdział o Conditions (i chwała mu za umieszczenie pełnego tekstu w Internecie).

Post jest w płodem rozmyślań po dyskusji, którą odbyłem wczoraj w tramwaju z kolegami z pracy. W dużym więc stopniu ich zasługą jest ten artykulik.

A Wy, drodzy polscy lispnicy, jakie książki polecacie?

Co jest fajnego w Common Lispie?

W Internecie bardzo łatwo znaleźć artykuły opisujące metafizyczną wyższość Lispa nad pomniejszymi (czytaj: wszystkimi innymi) językami programowania. Olśnienie, kompletna zmiana sposobu myślenia, cały świat wygląda nagle inaczej, iluminacja (tak, wiem, że to znaczy to samo co olśnienie), stary, po prostu totalny zen. Całkiem śmieszną parodię okołolispowej propagandy można znaleźć tutaj.

XKCD o Lispie

Smutna prawda jest jednak taka, że ludzie nie sięgają po nowy język programowania tylko po to, aby przeżyć olśnienie albo pogłębić swoje zrozumienie istnienia. Do tego służyć powinny raczej literatura, filozofia (polecam Quine'a i Wittgensteina), matematyka (twierdzenie Goedla), muzyka, w ostateczności narkotyki...

Nauczenia się zupełnie nowego języka programowania to jest zazwyczaj ogromny wysiłek. Bo przecież tu nie chodzi tylko o składnię (składnia lispowa, jak dziwna i nienaturalna by się wydawała na początku, po dwóch dniach staje się bardzo naturalna), ale również o pragmatykę nowego języka. Chodzi mi o rzeczy typu: jakich użyć bibliotek, jak je zainstalować, jak dostarczyć gotowa aplikację klientowi, co jest dobre, co jest złe, jakie pomysły są z góry skazane na porażkę, itp. Jeżeli ktoś w miarę normalny podejmuje ten ogromny wysiłek, zazwyczaj ma jakiś problem, który chce rozwiązać, i wierzy, że dzięki lispowi rozwiązanie tego problemu będzie lepsze, efektywniejsze, ciekawsze, a gotowy program łatwiejszy w pielęgnacji.

(Oczywiście, ciekawość poznawcza jest bardzo dobrym powodem, aby nauczyć się Lispa. Zazwyczaj jednak ciekawość poznawcza jest motywowana chęcią fajnego wykorzystania nowo nabytej wiedzy, a nie pragnieniem wiedzy samej dla siebie.)

Teraz, oczyściwszy przedpole, mogę opisać kilka powodów, dla których uważam, że Common Lisp jest lepszy od wielu innych, bardziej popularnych języków. Przez “lepszy”, oczywiście, rozumiem: lepiej dostosowany do moich potrzeb i pozwalający na łatwiejsze rozwiązanie sporej części moich problemów.

Makra

Makra lispowe są konstruktem językowym umożliwiającym metaprogramowanie. Czyli, narzędziem umożliwiającym pisanie programów, które same piszą inne programy. Brzmi to świetnie. Pytanie jednak: jak to się przekłada na praktykę? Czy jest to wygodne? Czy sprawia to, że programista może skupić się na rozwiązywanym problemie zamiast walczyć z systemem, czy wręcz przeciwnie, wysiłek związany ze stosowaniem makr niweczy wszystkie ich korzyści?

Makro definiuje się jak zwykłą funkcję. Różnica jest następująca: zamiast zwracać jakąś wartość, makro ma za zadanie zwrócić kod, którym kompilator zastąpi jego wywołanie w źródle. Istotną kwestią jest to, że generowanie owego kodu jest bardzo proste, dzięki “nieintuicyjnej“ składni Lispa. Mianowicie, program lispowy składa się z list, więc tylko listy musimy generować. (Kiedyś zastanawiałem się, jak można by dodać makra do Pythona i doszedłem do wniosku, że byłoby to dość skomplikowane. Bardzo lubię wyrażenia regularne, ale pisanie skomplikowanych programów za pomocą zastępowania ciągów znaków innymi ciągami znaków wydaje się dość nietrywialne. Nie wspomnę już o tym, jak bardzo grupowanie bloków kodu za pomocą tabulacji (coś, co bardzo lubię w Pythonie) utrudniałoby całą operację.)

Kiedy należy używać makr? Wtedy, gdy zauważamy, że nasze programy piszemy według jakiegoś wzorca, i chcielibyśmy ten wzorzec wyabstrahować i wykorzystać w innych miejscach (w końcu jedną z głównych zadań komputerów jest zastąpienie ludzi w prostych, algorytmizowalnych czynnościach --- dlaczego miałoby to nie dotyczyć programistów?). Niezły przykład takiego zastosowania makra można znaleźć tutaj.

CLOS

W Common Lispie, w przeciwieństwie do Javy, C++ albo Pythona metody przynależą nie do klas, a do funkcji (tzw. generic functions). Z syntaktycznego punktu widzenia wywołanie takiej funkcji nie różni się niczym od wywołania dowolnej innej, zwykłej funkcji (nie ma kropek, strzałek ani żadnego innego śmiecia). Gdy taka funkcja zostanie wywołana, decyduje ona którą metodę zawołać na podstawie rodzaju wszystkich swoich argumentów. Jest to zachowanie inne niż w pozostałych językach, które wymieniłem powyżej, gdzie jedynie pierwszy argument (czyli np. w Pythonie self) decyduje o tym, która metodę wywołać.

MOP

Ostatnią wielką przewagą Lispa jest daleko posunięta introspekcja, osiągana dzięki Metaobject Protocol. Jest to jedna z najciekawszych funkcji Lispa, choć jej sensowne i poprawne użycie może być dość nietrywialne.

Każdy obiekt należy do jakiejś klasy. Pytanie: czym jest z kolei klasa? Otóż, w Common Lispie klasa również jest obiektem, typu standard-class. Nie tylko funkcje, ale też generic functions i metody są obiektami. Co to znaczy dla nas, programistów? Otóż, możemy zmieniać klasy w trakcie działania programu. Z tego co wiem, dla Javy istnieje biblioteka dająca podobne możliwości, ale w porównaniu z możliwościami MOP-a Lispowego jest dość ograniczona.

Idąc dalej, skoro klasy są (meta)obiektami typu standard-class (którą możemy nazywać metaklasą), nic nie stoi na przeszkodzie, aby napisać klasę dziedziczącą po standard-class i sprawić, aby definiowane przez nas obiekty były właśnie tego typu. Czyli, zdefiniować metaklasę. Do czego możemy to wykorzystać? Kilka pomysłów:

  • Nie podoba się nam wielokrotne dziedziczenie? Proszę bardzo, napiszmy metaklasę obsługującą jedynie pojedyncze dziedziczenie!
  • Obiekty, które działają jak komórki w arkuszu kalkulacyjnym, automatycznie zmieniając wartości w swoich polach w zależności od stanu innych obiektów. Z tego co wiem, jest to używane w aplikacjach służących do projektowania samolotów pasażerskich, gdzie np. wymiary różnych elementów zależą od wymiarów podzespołów wchodzących w ich skład.
  • Obiekty z leniwym wartościowaniem pól (patrz lazy evaluation), których wartości powinny być wyliczone jedynie i nie wcześniej niż wtedy, kiedy są naprawdę potrzebne.
  • Trwałe (persistent) obiekty, automagicznie zapisujące swoje wartości w bazie danych.
  • ...

MOP przełamuje pewną barierę: programista, który go używa, jest prawie na tym samym poziomie, co projektant języka. Jak wiadomo, With great power comes great responsibility. Ale, chyba nie warto się tego bać: w najgorszym wypadku nasze programy przestaną działać (-;.

Oczywiście, może błądzę, a podobne rzeczy są lepiej rozwiązane w Foobarbaz (za Foobarbaz wstaw swój ulubiony język programowania różny od Common Lispa) --- będę bardzo wdzięczny za uświadomienie mnie w komentarzach. Co można, na przykład powiedzieć o makrach w Scheme. Oficjalnie są higieniczne, ale słyszałem, że większość ludzi używa Common Lispowych -- czy ktoś wie, czy to prawda? Tak samo, istnieje coś zwanego Tiny-CLOS --- jak bardzo to działa?

Czy jest tam kto? A konkretniej, programiści Lispa?

Czy w Polsce są jacyś programiści Lispa? I nie mam przez to na myśli ludzi, którzy studiując informatykę zmuszeni zostali zaliczyć przedmiot ze słówkiem Lisp w nazwie i narzekają na to, że interpretowany i że ma dużo nawiasów. Chodzi mi o ludzi, którzy umieją w Lispie zrobić coś sensownego, a przynajmniej: poważnie się starają.

Wiem na pewno o istnieniu dwóch: Maćka Pasternackiego i Rafała Strzalińskiego. Idąc po linkach z bloga Rafała trafiłem na posta opisującego bolączki instalacji SBCL (to daje czterech). Maciek ma szczęście programować Lispa za pieniądze, więc zapewne w jego firmie jest ktoś jeszcze, kto mówi w Lispie. Załóżmy, że tych lispników od Maćka jest pięciu, co razem daje dziewięć osób. Dodajmy jednego Tajemniczego Nieznajomego, aby wyszła ładna liczba. Razem, dziesięć osób. Słowem, niedużo.

I pomyśleć, że te dziesięć osób było w stanie wyprodukować aż 839 stron, o czym lojalnie informuje mnie Gógl :-)

Dlaczego tak jęczę? Otóż, od pierwszego lipca praktykuję jako programista Lispa w pewnej holenderskiej firmie, o bardzo oryginalnej nazwie Streamtech (nie mylić z kilkunastoma pozostałymi streamtechami). Nie jest ona zbyt duża (obecnie jest nas 9 osób), projekty dotyczą szeroko pojętego programowania dla Internetu i są robione prawie wyłącznie w Common Lispie (jest jeden projekt w Javie, który musieli przyjąć w ramach offsetu). Towarzystwo jest dość międzynarodowe (czterech Holendrów, dwóch Amerykanów, Niemka, Włoch i ja), atmosfera przyjemna. Kiedy nie ma dobrego tematu do rozmowy, zawsze można się przedyskutować wady i zalety używania LOOP, co gwarantuje rozruszanie towarzystwa.

Rzeczą, która zrobiła na mnie świetne wrażenie jak tylko przyjechałem, było odbywające się spotkanie Beneluksiańskiego Towarzystwa Użytkowników Lispa. Lispnicy benelukśni pogadali sobie o swoich projektach, Pascal Costanza zaprezentował referat o introspekcji i MOP-ie, następnie zamówiono pizzę i otwarto piwo. Czy nie byłoby fajnie, gdyby coś takiego mogło odbywać się w Polsce?

No ale aby coś takiego było możliwe, polscy lispiarze musieliby się poznać. Albo, może to ja jestem na uboczu, a polskie środowisko jest po prostu zakamuflowane przed Google?

Zapraszam szanownych lispiarzy do wpisywania się w komentarzach. Zakładając, że jakimś cudem znajdą tego bloga, oczywiście.

A tak w ogóle: nazywam się Ryszard Szopa, i wyglądam tak:

Może zdjęcie jest nienajlepsze, ale za to z najprawdziwszą maszyną lispową (nie mogłem się powstrzymać).