Compile time, Run time, Scheme, and everything (part 1)

For this summer I wanted to learn Scheme and compare with my knowledge of Common Lisp and Emacs Lisp. The idea came after starting to read the wonderful SICP, and also, after playing around a little bit with the wonderful Racket implementation.

I also selected Racket because it provides a wonderful GUI library that is multi-platform too. The project I'm describing here is part of another one that has a GUI.

The Project

The project itself can be seen in GitHub (ecore2racket). The aim of the project is to provide a mapping of Eclipse Modeling Framework (EMF) Ecore metamodels to Racket/Scheme code, using Racket's object-oriented features (as Ecore is object-oriented). This is similar to what we did for C++: EMF4CPP.

First of all, Ecore is object oriented. That is, the main building blocks of metamodels are classes and relations among them (inheritance, reference, etc.) That said, Lisp in general, and Scheme in particular, allow a wide range of implementation options for the mapping: In a heartbeat you can find three implementations of OOP frameworks that you can use: TinyCLOS, Swindle (included in Racket as a package) and off-the-shelf Racket support for OOP.

At first I thought of using TinyClOS, as it is compatible with most Scheme implementations (more on compatibility later), but the fact is that the support for classes in Racket is so good that I couldn't resist using it (paired with the great support for class/function contracts, that I will talk about in future entries).

At first sight, Racket classes are easy to spot, even coming from any other language that uses OOP constructs:

(define eobject%
  (class* object% (classifier<%>)

    (super-new)

    (define -e-name "EObject")
    (define -e-package "ecore")

    ;; classifier<%> interface methods
    (define/public (e-name) -e-name)
    (define/public (e-name-set! n) (set! -e-name n))

    (define/public (e-package) -e-package)
    (define/public (e-package-set! p) (set! -e-package p))))

The class* construct creates a class that inherits, in this case, from object% (by convention Racket class names end with a %), implements the classifier<%> interface (not shown) and has some internal members (those defined with define) and some public methods, those stated with define/public. There is more (but not much more) to it. You can check the documentation.

Creating an object and calling methods is also natural:

(define myeobject (new eobject%))
(send myeobject e-name)
;=> "EObject"
(send myeobject e-name-set! "EObject1")

new creates an object, and send can call methods previously defined with define/public.

Generating code for the metamodels

Now to the problem. Given an Ecore metamodel, we have to create a mapping classes in Racket. The EMF framework provides a series of tools to generate code from Ecore metamodels. You can collect all the classes, attributes, and relationships, and generate code for each of them. In the MDE jargon, this is known as M2T (Model to Text).

Among them, I'll use Acceleo. It has reasonable power, and also is backed by an OMG standard. To show you an example of how Acceleo code looks like for a class expansion, here is what I currently do:

[template public generateRacket(anEPackage : EPackage)]
[comment @main/]
[file (decamel(anEPackage.name)+'.rkt', false, 'UTF-8')]
#lang racket/base

[generate_provides(anEPackage)/]

[for (klass : EClass | anEPackage.eClassifiers->filter(EClass))]
[generate_EClass(klass)/]
[/for]

...

[template private generate_EClass(klass: EClass)]
[generate_interface(klass)/]
[generate_EClass_proper(klass)/]
[/template]

[template private generate_interface (aEClass : EClass) ]
(define [eclass_to_racket_interface_name(aEClass)/]
  (interface ([superiface_spec(aEClass)/])
[for (sf: EStructuralFeature | aEClass.eStructuralFeatures)]
[let dec : String = decamel(sf.name)]
[dec/]
[if sf.changeable]
[dec/]-set!
[/if]
[/let]
[/for]
))
[/template]

There are several templates that are expanded for each type of element of the model (classes, attributes, etc.) In the shown snippet, for each class in a package, the template generate_EClass is called. This generates an interface and a class definition. There are auxiliary functions not shown, like eclass_to_racket_interface_name, that build Racket idiomatic names out of Ecore classes.

Next Part

Enough for today. In the next part we'll see alternatives in the code generation: either generating all the Racket code using Acceleo or generating Racket/Scheme macros. I can tell already that we'll select the second version, as the complete metaprogramming capabilities of Racket are far superior than Acceleo, that lacks the more basic features of any programming language (such as case, for example, vital for generating different code for each model element type).

Modified deft-mode to multiple directories

After some hacking, I've been able to modify the wonderful Deft mode by Jason R. Blevins to support several directories. I had my notes divided in several directories, including my TO-DO entries, and I thought it would be nice to give Deft the possibility to support several directories, and at the same time, an exercise for my Emacs Lisp.

I tried to make the least modifications as possible to the original source, so that the change can be made with a patch, if the original author considers it (this is also why I didn't change the name of the mode either.) You can download it, and read instructions of installation and usage here:

deft-multidir on GitHub

As always, feedback is highly appreciated.

Macros Emacs para facilitar introducir entradas

Continuando con el formato org-mode, he añadido también un esqueleto que rellena de forma automática los campos de una entrada del blog, para que sólo tenga que escribir el texto y las categorías. Es la siguiente función Emacs-Lisp:

(define-skeleton add-new-post
  "Add a new post with the current date and time. Ask for the post title."
  "Post title: "
  "(new-post" \n
  > "\"" str "\"" \n
  > ":body \"" _  "\"" \n
  > ":categories '('general)" \n
  > ":body-format 'string" \n
  > (format-time-string
     ":hours %H :minutes %M :day %e :month %m :year %Y)"
     (current-time)) \n )

La función new-post me permite añadir una entrada al blog. Es código Common Lisp, por lo que se ve que comienza por un parémtesis. El patrón (definido por el macro de Emacs-Lisp define-skeleton después me pregunta por el título de la entrada (el uso de la variable str hace que me pregunte a la hora de insertar el patrón). Después, añade el cuerpo vacío y también la fecha actual de la entrada obtenida de la función de Emacs-Lisp (current-time). Finalmente, el carácter _ indica la posición en la que queda el cursor, con lo que puedo empezar a escribir el cuerpo de la entrada.

Prueba del formato org-mode

En este caso, he utilizado org-mode para crear escribir esta entrada.

¡Incluso soporta varios párrafos!

El código para hacerlo en Emacs-lisp es el siguiente:

(defun org-to-html ()
  (interactive)
  (when (mark)
    (save-excursion
      (let ((text (buffer-substring-no-properties (point) (mark))))
        (delete-region (point) (mark))
        (insert
         (with-temp-buffer
           (insert text)
           (org-export-region-as-html (point-min) (point-max) t 'string)))))))

Lo que hago pues es seleccionar el texto que escribo en formato org y llamar a esa función org-to-html, que me transforma el texto seleccionado en HTML, incluso el trozo de código anterior, con coloreado de sintaxis que no necesita del embellecedor de código JavaScript de la página…

¡Otra vez no! Muere John McCarthy, inventor de Lisp

Estamos de tristeza últimamente. Tras la muerte de Ritchie, muere también el inventor de Lisp, John McCarthy. Para un blog escrito en Lisp (y para toda la comunidad informática mundial) una mala noticia. Si C es el padre de la mayoría de los lenguajes imperativos, Lisp lo es de los funcionales. Con esos dos lenguajes casi cubrimos el 99% de la informática... A este seguro que tampoco lo vemos en cientos de periódicos... Os dejo un enlace al artículo original de LISP.

McCarthy

Translation within Emacs using Google Translate

Just to show a little Emacs-Lisp script I wrote the other day. We're in the process of translating all our class material into English, and thought of getting some help from Google Translator. Selecting a phrase and calling the insert-translation function substitutes current text with its traduction into English. You know, Google Translate fails a fair bit, but it helps, and you don't have to write all the slides again...

Let Over Lambda--50 years of Lisp

Hoy por casualidad he encontrado esta referencia, Let Over Lambda, Closures de Doug Hoyte. Es curioso que sin haberlo leído antes, la solución que he dado al problema de extraer la descripción de una entrada del blog sin tags HTML ha sido así usando un closure.

Búsqueda en el blog

Como véis, todavía no funciona la búsqueda en el blog, pero tengo una idea muy interesante para implementarla. Recordad que el blog se genera estáticamente, así que no puedo depender de ningún proceso de servidor, así que, por decirlo de alguna manera, tengo que precalcular la búsqueda y almacenarla en algún sitio, que además, no entorpezca con el blog (no tarde más tiempo en cargar, por ejemplo). Lo que estoy preparando lo explicaré con tranquilidad. Por ahora, valga una muestra de lo que llevo implementado:

BLOG> (hash-table-count
       *words-to-post-hash*)
18119

18119 palabras diferentes. Ahora los posts que contienen "corba", y sus títulos:

BLOG> (gethash "corba" *words-to-post-hash*)
(#<POST {100A12E8A1}> #<POST {100A12E881}> #<POST {100A12E521}>
 #<POST {100A12E4E1}> #<POST {100A12E4C1}> #<POST {100A12E3E1}>
 #<POST {100A12E021}> #<POST {100A12DBA1}> #<POST {100A12D321}>
 #<POST {100A12CEE1}> #<POST {100A12C821}> #<POST {100A12C641}>
 #<POST {100A12C5A1}> #<POST {100A12C361}> #<POST {100A12C2E1}>
 #<POST {100A12B4E1}> #<POST {1003344011}> #<POST {1003343EB1}>
 #<POST {10033437B1}> #<POST {10033436B1}> #<POST {1007887F81}>
 #<POST {1007887221}> #<POST {10078870A1}> #<POST {1007886801}>)
T
BLOG> (mapcar #'post-title (gethash "corba" *words-to-post-hash*))
("¿Por qué un Weblog?" "5.000.000.000" "CCM page, reloaded"
 "Recent articles on CCM" "German book on CCM" "CCM Wiki updated"
 "Some thoughts on Web Services" "New version of the CCM tutorial"
 "Stefan Tilkov on RPC Web Services" "Trabajo en la tesis" "CORBA Reborn?"
 "Interesting post on XML messaging by Stefan Tilkov" "Sistemas Distribuidos"
 "Prácticas de Sistemas Distribuidos" "Un día de trabajo con mi tesis" "FreeNX"
 "Fowler, inversion del control" "Ser profesor tiene sus cosas buenas"
 "Función C++ rara del día" "The S stands for Simple"
 "Conversión sencilla de tipos CORBA" "Parecía que nunca iba a llegar..."
 "Más avances: Cátedra SAES-UMU" "SOAP, entre lo peor de la década")

Lo cual es, por cierto, una magnífica lista de entradas para esa palabra... Esta información también me permitirá añadir al final un conjunto de "posts relacionados" en cada entrada.

Imágenes en el blog

Iba a introducir imágenes en el blog y he querido escribir una pequeña función que hace más sencillo introducir fácilmente las imágenes con la ruta por defecto, y, si procede, un enlace para las mismas. La función queda como sigue:

(defun blog-img (img-file &key alt anchor title params)
  (let* ((param-list
          (cons `(src . ,(format nil "~A/~A" *base-img-url* img-file))
                (cons `(alt . ,(or alt "Blog image.")) ; alt is obligatory
                      (when title `((title . ,title))))))
         (param-list-1 (append param-list params))
         (img-html (img param-list-1)))
    (if anchor
        (a `((href . ,anchor)) img-html)
        img-html)))

¿No es bonito? En particular me gusta el uso del seudoquote. Las funciones img y a generan el HTML para las imágenes y para los enlaces, respectivamente. Un ejemplo de uso de esa función sería:

(blog-img "abc.jpg" :alt "Alt text" :params '((:width . 500)))

donde se elige el fichero img/abc.jpg con un texto alternativo y con el conjunto de parámetros adicionales, entre ellos el ancho de la imagen. Si se especifica un elemento anchor el código que se genera es el siguiente:

(blog-img "abc.jpg" 'anchor "http://wherever.com"  'alt "bah" 'params '(('width . 500)))
<A HREF="http://wherever.com"><IMG SRC="img/abc.jpg" ALT="bah" WIDTH="500"></IMG></A>

Estadísticas de tiempo de generación del blog

De cara a optimizar la generación de las páginas del blog con multiprogramación, he querido registrar el tiempo que tarda en ejecutar la generación en el ordenador que se genera, para compararla después con la optimización. Para mi sorpresa, la mayor parte del os 8 segundos que lleva la generación se la lleva el leer y compilar el fichero Lisp que contiene las entradas antiguas (1.3MB), mientras que la generación de todas las páginas no tarda más de 4 segundos:

[dsevilla@neuromancer:~/svn/blog]$ sbcl --script packages.lisp
Doing pre-calculations...
Generating index page...
Evaluation took:
  0.116 seconds of real time
  0.120000 seconds of total run time (0.080000 user, 0.040000 system)
  [ Run times consist of 0.040 seconds GC time, and 0.080 seconds non-GC time. ]
  103.45% CPU
  325,871,901 processor cycles
  63,697,264 bytes consed

Generating post pages...
Evaluation took:
  0.408 seconds of real time
  0.410000 seconds of total run time (0.270000 user, 0.140000 system)
  [ Run times consist of 0.010 seconds GC time, and 0.400 seconds non-GC time. ]
  100.49% CPU
  1,144,029,612 processor cycles
  167,055,120 bytes consed

Generating categories pages...
Evaluation took:
  0.074 seconds of real time
  0.070000 seconds of total run time (0.070000 user, 0.000000 system)
  94.59% CPU
  209,026,050 processor cycles
  39,985,040 bytes consed

Generating archives pages...
Evaluation took:
  0.086 seconds of real time
  0.090000 seconds of total run time (0.090000 user, 0.000000 system)
  104.65% CPU
  240,093,459 processor cycles
  43,278,720 bytes consed

Generating RSS...
Evaluation took:
  0.084 seconds of real time
  0.080000 seconds of total run time (0.080000 user, 0.000000 system)
  [ Run times consist of 0.020 seconds GC time, and 0.060 seconds non-GC time. ]
  95.24% CPU
  236,784,003 processor cycles
  35,828,208 bytes consed

Esto hace que la optimización, como máximo, sólo pueda reducir esos 0,4 segundos que tarda la generación. Aún así lo intentaré como un ejercicio de programación. La otra idea será ver optimizar el proceso de carga quizá a través de pre-compilación de los ficheros .lisp. Por cierto, para que luego digan que los lenguajes interpretados son lentos... 1 segundo en generar 34MB de ficheros de texto.

La última sorpresa... Por casualidad he probado clisp... Bien, aquí la carga de los ficheros .lisp es instantánea, y la ejecución es incluso más rápida (diría incluso increíblemente rápida:

[dsevilla@neuromancer:~/svn/blog]$ clisp packages.lisp
Doing pre-calculations...
Generating index page...
Real time: 0.186066 sec.
Run time: 0.19 sec.
Space: 24960736 Bytes
GC: 13, GC time: 0.03 sec.
Generating post pages...
Real time: 0.708989 sec.
Run time: 0.69 sec.
Space: 83706152 Bytes
GC: 36, GC time: 0.06 sec.
Generating categories pages...
Real time: 0.051339 sec.
Run time: 0.05 sec.
Space: 11157832 Bytes
GC: 5, GC time: 0.0 sec.
Generating archives pages...
Real time: 0.249124 sec.
Run time: 0.25 sec.
Space: 13408648 Bytes
GC: 6, GC time: 0.02 sec.
Generating RSS...
Real time: 0.891364 sec.
Run time: 0.88 sec.
Space: 20991880 Bytes
GC: 9, GC time: 0.05 sec.

Comparando el tiempo de ejecución visto por el usuario:

[dsevilla@neuromancer:~/svn/blog]$ time clisp packages.lisp
real	0m2.320s
user	0m1.970s
sys	0m0.310s
[dsevilla@neuromancer:~/svn/blog]$ time sbcl --script packages.lisp
real	0m10.405s
user	0m9.940s
sys	0m0.440s

Esto es, ¡5 veces más rápido en general clisp que sbcl! Sin embargo, mirando los datos de cada parte, hay resultados muy extraños e inconsistentes. Por ejemplo, clisp tarda casi un segundo en generar RSS, mientras que sbcl tarda 0,08 segundos (un orden de magnitud menos). Estudiaré el código para ver dónde puede estar el problema, pero por ahora, usaré clisp para generar el blog, aunque use sbcl, con el magnífico entorno Slime para Emacs para seguir programando y probando.

Multiprocesamiento para generar el blog

Siguiendo este enlace voy a intentar añadir multiprocesamiento a la generación del blog para acelerarlo. No va a ser tan sencillo como debería ser, por ejemplo, debería existir, como en Clojure, un parallel map pero la verdad es que no hay, sólo hilos tradicionales... La ventaja, sin embargo, será grande, ya que todas las páginas se pueden generar en paralelo.

Añadido colorización de código con google-code-prettify

Pues no ha sido complicado. Simplemente he seguido las instrucciones del README de la página de google-code-prettify y ya está.

Y las páginas de los tags

Sólo por curiosidad, he aquí cómo está implementada la generación de los links con diferente tamaño del sidebar:

(defun categories-links ()
  (if *categories-links*
      *categories-links*
      (setf *categories-links* (multiple-value-bind (max-n-posts min-n-posts)
          (loop for c being the hash-values in *posts-for-category*
             maximizing (car c) into max
             minimizing (car c) into min
             finally (return (values max min)))
        (apply #'concatenate 'string
                (loop for k being the hash-keys in *posts-for-category*
                   using (hash-value v)
                   collect (format nil "<a href=\"category-~A.html\"
                             title=\"~A topic~:*~p\" rel=\"category tag\"
                             style=\"font-size: ~Apx;\">~3:*~A</a> "
                             (string-downcase k)
                             (car v)
                             (+ 9 (round
                                   (/ (- (car v) min-n-posts)
                                      (/ (- max-n-posts min-n-posts) 10)))))))))))

Y eso sin contar con el cálculo de *posts-for-category*.

¡Primera entrada!

Esta es la primera entrada de este nuevo blog cuyo generador está escrito en Common-Lisp. ¿Lisp? Sí, más de 50 años después sigue vivo, y sinceramente, es una maravilla aprenderlo y usarlo. Iré mostrando en el futuro cómo se genera.