Esta función está escrita en el lenguaje de programación Emacs Lisp.
(defun google-dictonary (query) "Translate word at point" (interactive "sTranslate: ") (let* ((url (concat "http://translate.google.com/" "translate_dict?langpair=en|es&" "q=" query)) (definition (progn (set-buffer (url-retrieve-synchronously url)) (goto-char (point-min)) (buffer-substring-no-properties (search-forward "<span class=\"definition\">") (- (search-forward "<span") 5))))) (message "%s: %s" query definition)))
La función se invoca interactivamente, se le pasa una palabra y traduce con un traductor online. En este caso se utiliza el traductor de Google pero es fácil cambiarlo a cualquier otro. La idea es aprovechar la función para mostrar de qué manera se puede extender Emacs.
Una función se define utilizando la forma especial defun, un nombre y una lista de argumentos. Opcionalmente incluimos una cadena para documentar la función.
Como nuestra función va a ser llamada por el usuario y no por otros programas vamos hacer una llamada a la función interactive que especifica de que manera el usuario puede realizar la llamada y como va a pasar los argumentos.
Por ejemplo, un defun sencillo:
;; Esto es un comentario.
(defun llamame (arg) "Función que no hace nada" (interactive "sDecime:") (message arg))
La función interactive puede recibir diferentes argumentos para comportarse de muchas maneras, en este caso queremos que nos pregunte mediante un prompt interactivo por el valor del argumento arg, como queremos que arg sea una cadena indicamos una s como primer caracter de la cadena y luego escribimos el texto a mostrar en el prompt.
Si ejecutamos C-x C-e sobre el último parentesis de cierre estaremos evaluando la definición y nuestro entorno de Emacs dispondrá, a partir de ese momento, de la función para llamarla. Esta es una gran ventaja del entorno de Emacs, no hace falta reiniciar el editor para extenderlo.
Podemos llamar a la función como a cualquier otra M-x RET llamame RET. Si todo salió bien tendríamos que estar viendo el prompt "Decime:".
message imprime en el minibuffer el argumento arg. Es importante notar que dentro del cuerpo de defun se ejecutan en orden de aparición todas las expresiones que aparecen.
Ya sabemos como preguntarle al usuario por un término que necesite traducir, al final del ejemplo mejoraremos la llamada a interctive para agregar más funcionalidad.
Vamos ahora a mirar por alguna librería de Emacs que nos permita realizar la consulta GET a la página del traductor. Tenemos una librería llamada url que ya viene con Emacs y la función que nos interesa es url-retrieve-synchronously[1].
Cuando te encuentres resolviendo otros problemas puedes encontrar funciones y librerías que te ayuden pensando en palabras claves que definan el dominio de tu problema y buscando con el comando interactivo de emacs apropos. Por ejemplo M-x apropos RET url nos va a dar una lista de funciones relativas al término url. Por supuesto consulta C-h i info que tiene una referencia completa de Emacs Lisp.
La función url-retrieve-synchronously cuando recibe como argumento una dirección de http (como cadena) intenta comunicarse con el recurso. Si lo logra devuelve un buffer con la respuesta del servidor.
Probemos (evalua la expresión posicionandote en el último parentesis de cierre y presiona C-x C-e):
;; simplemente pedimos el home de la wikipedia
;; y cambiamos al buffer devuelto
(switch-to-buffer (url-retrieve-synchronously "http://es.wikipedia.com"))
Si todo salió bien tu Emacs te llevó a un nuevo buffer con la respuesta enviada por el servidor de wikipedia. Podrás leer en primer lugar las cabeceras y luego reconocer el código HTML de la página principal de la Wikipedia.
Siempre que quieras conocer más detalles sobre alguna función aprovecha la ayuda interactiva de Emacs. Posiciona el cursor sobre el nombre de la función y presiona la combinación C-h f RET RET, por defecto nos llevará a la documentación de la función bajo el cursor. Lo mismo podemos hacer con las variables C-h v.
Bien, ya tenemos el argumento y sabemos hacer una petición GET y obtener su HTML, estamos muy cerca de nuestro objetivo original, juntemos estas dos cosas y vemos que se empieza a armar el esqueleto de nuestra función:
(defun google-dictonary (query) "Primera versión" (interactive "sTérmino de búsqueda:") (switch-to-buffer (url-retrieve-synchronously (concat "http://translate.google.com/translate_dict?langpair=en|es&q=" query))))
Evaluemos la definición de la función como antes con C-x C-e. Inmediatamente la tenemos disponible, probamos llamandola desde el minibuffer con M-x google-dictonary RET apple RET. De nuevo nos encontraremos con un buffer, esta vez tendrá el HTML de Google Translator para la palabra apple.
Pero el html se ve terrible (y el de Google suele ser especialmente anti-humano). Busquemos la manera de quedarnos con la parte que nos interesa de aquel este buffer que obtenemos.
Pero antes de esto vamos a tener que hablar un poco de dos formas especiales muy importantes en Emacs Lisp y son: let y de let*. Formas que nos permiten asociar símbolos a valores o procedimientos (piensa en variables locales).
Veamos primero a let:
;; De la documentación: (let varlist body...)
(let ((a 5) (b 6) c) (print a) (print b) (print c))
Lo que hace let es ligar cada una de los símbolos a, b, y c a un valor. En el caso de a y b este valor se especificó expresamente y en el caso de c se omitió para que se le ligue el valor nil (o lista vacía).
Pero además de ligar los valores let, ejecutará el resto de las expresiones que se agreguen luego de la primer lista que liga los valores. Estas expresiones son los tres print. Cuando ejecutamos la función veremos que se imprime cada uno de los símbolos. Pero al final de todo vemos que aparecen dos impresiones de nil. Esto es porque let devuelve como resultado la última expresión de su cuerpo.
Es importante hacer ver que el valor ligado a las variables mediante let sólo tiene alcance en el cuerpo de let:
(defun f (a) (let ((a 5)) (print a)) (print a)) (f 4)
¿Y que pasa con let*? let* funciona idénticamente pero con una sutil diferencia. Las variables ligadas pueden ser utilizadas inmediatamente después de su definición, es decir, en la misma lista que liga los valores a los símbolos uno puede hacer referencia a un valor de un símbolo que se ligó anteriormente en la misma lista, así esto funcionará como esperamos:
(let* ((a 5) (b a))
(print b))
En cambio en el caso de let no podemos hacer referencia a a:
(let ((a 5) (b a))
print b)
Al evaluar lo anterior entramos en un error, ya que intentamos ligar a b con el símbolo a pero en este ámbito todavía no se ha definido a!
Ahora vamos a seleccionar lo que nos interesa del buffer devuelto por url-retrieve-syncrhonously para esto vamos a utilizar la función buffer-substring-no-properties aunque con buffer-substring bastaría (revisa el manual en info para leer sobre properties).
Vamos a ver como funciona esta función:
(buffer-substring-no-properties (point) (- (point) 54))
Si evaluamos el código anterior con C-x C-e, obtendremos el mismo código como resultado de la evaluación. Esto se debe a que la función buffer-substring-no-properties recibe como argumento dos enteros que son el pedazo de buffer con el cual nos quedemos quedar. Como primer argumento le pasamos la posición actual del puntero (resultado de la función point): (point) y como segundo argumento hacemos: (- (point) 54): la diferencia entre la posición actual del puntero y 54 (puse 54 porque es la lóngitud que tiene ese pedazo de código). Si nos aseguramos de presionar C-x C-e justo detras del parentésis la expresión devolverá al mismo código.
Ahora debemos aplicar buffer-substring-no-properties para obtener la traducción de la palabra en el buffer de HTML que habíamos obtenido anteriormente. Para no complicar demasiado las cosas con un parser HTML simplemente vamos a quedarnos con las posiciones de encontrar las etiquetas que encierran la definición en la respuesta de Google:
(buffer-substring-no-properties
(search-forward "<span class=\"definition\">")
(- (search-forward "<span") 5)) Pero este código no debe ejecutar sobre el buffer desde dónde se llama la función sino que debe ejecutar al comienzo del buffer devuelto por url-retrieve-syncrhonously, así que antes de buscar cambiamos al principio de este buffer:
(progn (set-buffer (url-retrieve-synchronously url)) (goto-char (point-min)) (buffer-substring-no-properties (search-forward "<span class=\"definition\">") (- (search-forward "<span") 5)))
La forma especial progn, simplemente evalua secuencialmente los argumentos que se le pasen y devuelve el resultado del último. En este caso devolverá definición de la palabra que estamos buscando.
set-buffer es lo que se utiliza para cambiar de buffer temporalmente en un programa. Pero el usuario no notará tal cambio. Ni bien cambiamos de buffer ponemos el puntero en la posición inicial del buffer para que las busquedas hacia adelante funcionen correctamente (goto-char (point-min)), y al final de todo llamamos a buffer-substring-no-properties que al ser la última expresión de progn, su resultado será el valor de retorno de toda la expresión progn.
Con todo lo que vimos ya casi podemos armar la función final, así que vamos a hacerlo:
(defun google-dictonary (query) "Translate word" (interactive "sTranslate: ") (let* ((url (concat "http://translate.google.com/" "translate_dict?langpair=en|es&" "q=" query)) (definition (progn (set-buffer (url-retrieve-synchronously url)) (goto-char (point-min)) (buffer-substring-no-properties (search-forward "<span class=\"definition\">") (- (search-forward "<span") 5))))) (message "%s: %s" query definition)))
Esto ya es bastante útil y usable. El problema de esta función es que la mayoría de las veces uno no traduce palabras que libremente se le ocurren en la cabeza, si no que son palabras que uno está leyendo en un correo, en un grupo de noticias o en una página web (actividades que los que llegaron hasta esta parte del documento realizan dentro de Emacs!). Entonces lo ideal sería que esta función tome por defecto la palabra bajo el puntero.
Obtener la palabra bajo el puntero es muy fácil de hacer ejecutando la función thing-at-point (buen nombre!):
(thing-at-point 'word)
Revisa la documentación que hay otros parámetros aparte del símbolo word (ojo que word está escapado con una comilla, porque estamos pasando el símbolo y queremos evitar que evalue).
Ahora vamos a ver un código que junta esto con read-string que lo usamos para leer una entrada desde el minibuffer
(list (let* ((word (thing-at-point 'word)) (input (read-string (format "Translate%s: " (if (not word) "" (format " (default %s)" word)))))) (if (string= input "") (if (not word) (error "No word given") word) input)))
Lo anterior mostrará el prompt con la palabra sobre la que esté posicionado el cursor (sólo si existe). Si el usuario escribe algo omitirá este valor default y utilizará la entrada. Si no hay palabra bajo el puntero ni input introducido salimos con un error.
Por último el argumento se devuelve dentro de una lista que es la manera como espera los argumentos la función interactive, y así podemos finalizar el ejemplo:
(defun google-dictonary (query) "Translate word at point" (interactive "sTranslate: ") (let* ((url (concat "http://translate.google.com/" "translate_dict?langpair=en|es&" "q=" query)) (definition (progn (set-buffer (url-retrieve-synchronously url)) (goto-char (point-min)) (buffer-substring-no-properties (search-forward "<span class=\"definition\">") (- (search-forward "<span") 5))))) (message "%s: %s" query definition)))
Si están contentos con el resultado pueden asociar la función a una secuencia de teclas, yo la tengo entre mi colección de eFes-algo:
(global-set-key (kbd "<f1>") 'next-buffer) (global-set-key (kbd "<f2>") 'previous-buffer) (global-set-key (kbd "<f5>") 'other-window) (global-set-key (kbd "<f6>") 'google-dictonary) (global-set-key (kbd "<f7>") 'matar-ya) (global-set-key (kbd "<f8>") 'delete-other-windows) (global-set-key (kbd "<f9>") 'find-file) (global-set-key (kbd "<f11>") 'google)
La función es pequeña, pero implica el uso de muchos conceptos sobre Emacs y Lisp. También se puede pensar programar otras conexiones a otros servicios online. En Emacs Wiki se encuentran montones de estos ejemplos (y bastante mejores), cuando ya hablamos de modos más grandes se suelen publicar en el grupo de noticias gnu.emacs.sources. También puedes obtener el paquete de fuentes de elisp con tu distribución de Emacs. Siempre que visites la documentación de una función (C-h f funcion RET) podrás visitar el archivo fuente en dónde se define.
La cantidad de recursos es bastante extensa, hay miles de librerías y programas que corren bajo Emacs, navegadores web, clientes de correo, juegos, calendarios, organizadores, clientes de irc, paquetes de matemática, lectores de grupos de noticias, una interfaz para casi cualquier programa popular de GNU y muchísimo más. Es imposible conocer todos los recovecos de Emacs y su medio ambiente, son unos 32 años de hackers escribiendo y extendiendo a la bestia.
La referencia de Emacs Lisp la puedes leer en info u online. También existe una introducción para no programadores (y que se distribuye con Emacs), es un poco densa si ya conocen algún lenguaje.
El post de Steve emergency elisp es otro buen pantallazo de las características del lenguaje para aquellos que tienen algún bg con Java o C++.
Pasando a Lisp en general, SICP es un gran recurso para aprender sobre Lisp y cosas más profundas sobre los lenguajes interpretados. El libro utiliza Scheme (otro dialecto de Lisp) y es el libro con el que se les enseña a programar a los cerebritos del MIT, se distribuye gratuitamente y se puede leer online. No es fácil leerlo a conciencia y me encuentro en el intento.
On Lisp de Paul Graham también se puede obtener online, pero trata temas avanzados de Lisp. Existe otro libro muy conocido e introductorio del mismo autor pero no resulta fácil conseguirlo. Y otro más sobre CL que se puede leer online: Practical Common Lisp.
No duden en enviarme cualquier corrección o mejora a la función en los comentarios.
[1] Existe una función url-retrieve con la que evitaríamos bloquear el editor ante una respuesta lenta del servidor, su uso requiere comprender que en Lisp uno puede pasar procedimientos como parámetros. Sería mejor usarla en este caso, pero la explicación ya resulta lo suficientemente larga como está. Subir
FECHA ESTIMADA DE LANZAMIENTO
CUANDO PODRA ESTAR UNA BETA COGADA EN LA RED?