index

Una introducción a la programación en Emacs Lisp

Esto es una Introducción a la Programación en Emacs Lisp, para personas que no son programadoras.

Traducido desde la edición 3.10

Copyright ® 1990–1995, 1997, 2001–2013 Free Software Foundation, Inc.

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; there being no Invariant Section, with the Front-Cover Texts being “A GNU Manual”, and with the Back-Cover Texts as in (a) below. A copy of the license is included in the section entitled “GNU Free Documentation License”.

(a) The FSF's Back-Cover Text is: “You have the freedom to copy and modify this GNU manual. Buying copies from the FSF supports it in developing GNU and promoting software freedom.”

Prefacio

La mayoría del entorno integrado GNU Emacs está escrito en el lenguaje de programación llamado Emacs Lisp. El código escrito en este lenguaje de programación es el software––el conjunto de instrucciones––que le indican al ordenador qué hacer cuando se le dan comandos. Emacs está diseñado de forma que se pueda escribir nuevo código en Emacs Lisp e instalarlo fácilmente como una extensión al editor.

(GNU Emacs se define muchas veces como un “editor extensible”, pero hace mucho más que proporcionar capacidad de edición. Es mejor referirse a Emacs como un “entorno de computación extensible”. Sin embargo, esa frase es un poco pretenciosa. Es más fácil referirse a Emacs simplemente como un editor. De hecho, cada cosa que se hace en Emacs––encontrar la fecha Maya y fases de la luna, simplificar polinomios, depurar código, administrar ficheros, leer cartas, escribir libros––todas estas actividades son maneras de editar en un sentido mas amplio de la palabra.)

Aunque Emacs Lisp normalmente se asocia solo con Emacs, es un lenguaje de programación completo. Se puede usar Emacs Lisp del mismo modo que con cualquier otro lenguaje de programación.

Quizás quieras entender la programación; quizás quieras extender Emacs; o tal vez quieras convertirte en un programador. Esta introducción a Emacs Lisp está diseñada para que empieces: para guiarte en el aprendizaje de los fundamentos de la programación y, lo que es más importante, para mostrarte como puedes aprender a ir mas más allá.

Sobre la lectura de este texto

A lo largo de este documento, verás pequeños programas de ejemplo que se pueden ejecutar dentro de Emacs. Si lees este documento dentro de GNU Emacs, puedes ejecutar los programas tal y como aparecen. (Esto es fácil de hacer y se explica cuando se presentan los ejemplos). Alternativamente, puedes leer esta introducción como un libro impreso mientras se está sentando frente a un ordenador ejecutando Emacs. (Esto es lo que me gusta hacer; me gustan los libros impresos.) Si no tienes un Emacs funcionando a tu lado, todavía puedes leer este libro, pero en este caso, lo mejor es tratarlo como una novela, o como una guía de viaje a un país que aún no visitas: interesante, pero no es lo mismo que estar allí.

Gran parte de esta introducción esta dedicada a recorridos o visitas guiadas del código usado en GNU Emacs. Estos recorridos están diseñados para dos propósitos: primero, para que te familiarices con el código de trabajo real (código que se usa cada día); y, segundo, para que te familiarices con el funcionamiento de Emacs. Es interesante ver cómo se implementa un entorno de trabajo completamente operativo. Ademas, espero que adquieras el hábito de navegar a través del código fuente. Puedes aprender mucho comparando código de otros con el propio y extraer nuevas ideas. Tener GNU Emacs es como tener la cueva del dragón de los tesoros.

Además de aprender sobre Emacs como editor y Emacs Lisp como lenguaje de programación, los ejemplos y visitas guiadas te darán una oportunidad para familiarizarte con Emacs como entorno de programación Lisp. GNU Emacs soporta programación y provee herramientas que llegaras a sentirte comodo usando, como M-. (el atajo que invoca el comando find-tag). También aprenderas sobre los búfers y otros objetos que forman parte del entorno. Aprender estas caracteristicas de Emacs es como aprender nuevas rutas alrededor de tu ciudad natal.

Finalmente, espero poder transmitirte algunas habilidades para utilizar Emacs con el fin de aprender aspectos de programación que no conoces. Con frecuencia se puede usar Emacs como ayuda para entender un rompecabezas o para encontrar la manera de hacer algo nuevo. Este auto-descubrimiento no es solo un placer, también es una ventaja.

Para quien está escrito esto

Este texto está escrito como una introducción elemental para personas que no son programadoras. Si eres es un programador, es posible que no estes satisfecho con este manual. La razón es que un programador puede tener que convertirse en experto leyendo manuales de referencia y este texto no está organizado como un manual de referencia.

Un programador experto que revisó este texto me dijo:

Prefiero aprender desde manuales de referencia. Me “zambullo” en cada párrafo y “subo a tomar aire” entre párrafos.

Cuando llego al fin de un párrafo, asumo que este asunto está hecho, terminado, que sé todo lo que necesito (con la posible excepción del caso en que el siguiente párrafo empiece a hablar de ello con más detalle). Espero que un manual de referencia bien escrito no tenga mucha redundancia, y que tenga excelentes indicadores del (unico) lugar donde está la información que quiero.

¡Esta introducción no está escrita para esta persona!

En primer lugar, intento decir cada cosa al menos tres veces: primero, para introducirlo; segundo, para mostrarlo en contexto; y tercero, para mostrarlo en un contexto diferente, o revisarlo.

En segundo lugar, casi nunca pongo toda la información sobre un tema en un solo lugar, y mucho menos en un párrafo. A mi manera de pensar, eso impone una carga bastante pesada al lector. En cambio, trato de explicar solo lo que necesitas saber en ese momento. (Algunas veces incluyo una pequeña información extra, para que no haya sorpresas más tarde cuando la información adicional sea presentada formalmente.)

Cuando leas este texto, no se esperes aprender todo la primera vez. Frecuentemente, solo necesitas, por asi decirlo, un ‘asentir con la cabeza’ en alguno de los articulos mencionados. Mi esperanza es haber estructurado el texto y dar suficientes pistas para indicar lo que es importante y te concentres en ello.

Necesitaras “sumergirte” en algunos párrafos; no hay otro modo de leerlos. Pero he intentado reducir el número de esos párrafos. Este libro pretende ser como una colina accesible, mas que una montaña abrumadora.

Esta introducción a la Programación en Emacs Lisp tiene un documento complementario. El Manual de Referencia de GNU Emacs Lisp. El manual de referencia tiene más detalles que esta introducción. En el manual de referencia, toda la información sobre un tema se concentra en un solo lugar. Deberias recurrir a el si eres como el programador citado anteriormente. Y, por supuesto, después de haber leido esta Introducción, encontraras el Manual de Referencia util cuando escribas tus propios programas.

Historia de Lisp

Lisp fué originariamente desarrollado a finales de los años 50 en el Instituto Tecnológico de Massachusetts para la investigacion en inteligencia artificial. El gran poder del lenguaje Lisp lo hace superior también para otros propósitos, como la escritura de comandos de edición y entornos integrados.

GNU Emacs Lisp está fuertemente inspirado en Maclisp, que fue escrito en el MIT en la decada de 1960. Está en cierto modo inspirado en Common Lisp, que se convirtio en un estándar en los 80. Sin embargo, Emacs Lisp es mucho más simple que Common Lisp. (La distribución estándar de Emacs contiene un fichero de extensiones opcional, cl.el, que añade muchas caracteristicas de Common Lisp a Emacs Lisp.)

Nota para los principiantes

Si no conoces GNU Emacs, todavia puedes leer este documento provechosamente. Sin embargo, te recomiendo que aprendas Emacs, al menos aprender a moverte alrededor de la pantalla del ordenador. Puedes aprender por ti mismo como usar Emacs con el tutorial incorporado. Para usarlo, escribe C-h t. (Esto significa que debes presionar la tecla CTRL y la h al mismo tiempo, y luego presionar y soltar t).

Ademas, a menudo me refiero a uno de los comandos estándar de Emacs listando las teclas que se presionan para invocar el comando y, luego dando el nombre del comando entre paréntesis, asi: M-C-\ (indent-region). Esto significa invocar el comando indent-region presionando M-C-\. (Puedes, Si lo deseas, cambiar las teclas que se presionan para invocar el comando; esto se denomina rebinding. Consulta la Sección Mapas de teclado.) La abreviatura M-C-\ significa que debes presionar la tecla META, CTRL, y \ al mismo tiempo. (En muchos teclados modernos la tecla META se llama ALT.) Algunas veces una combinación como esta se llama un acorde de teclas, puesto que es similar a tocar un acorde en un piano. Si el teclado no tiene una tecla META, en su lugar se usa la tecla ESC como prefijo. En este caso M-C-\ significa que se presiona y libera ESC y luego se presionan la tecla CTRL y la tecla \ al mismo tiempo. Pero normalmente M-C-\ significa presionar la tecla CTRL junto a la tecla que está marcada como ALT y, al mismo tiempo, presionar la tecla \.

Además de pulsar una sola combinación de teclas, puedes prefijar lo que escribes con C-u, que se llama el ‘argumento universal’. El atajo C-u pasa un argumento al siguiente comando. Por lo tanto, para indentar una región de texto plano por 6 espacios, marca la región, y lugo presiona C-u 6 M-C-\. (Si no se especifica un número, Emacs pasa el número 4 al comando o ejecuta el comando de forma diferente a como lo haria de otro modo). Consulta la Sección Argumentos Numéricos en El Manual de GNU Emacs.

Si estás leyendo esto en Info usando GNU Emacs, puedes avanzar a través de todo este documento presionando la barra espaciadora, SPC. (Para aprender acerca de Info, presiona C-h i y luego selecciona Info.)

Una nota sobre terminología: cuando uso la palabra Lisp sola, con frecuencia me estoy refiriendo a los diversos dialectos de Lisp en general, pero cuando hablo de Emacs Lisp, me estoy refiriendo a GNU Emacs Lisp en particular.

Agradecimientos

Estoy agradecido a todos los que me ayudaron con este libro. Mi especial agradecimiento a Jim Blandy, Noah Friedman, Jim Kingdon, Roland McGrath, Frank Ritter, Randy Smith, Richard M. Stallman, y Melissa Weisshaus. Gracias también a Philip Johnson y David Stampe por su paciente aliento. Mis errores son míos.

Robert J. Chassell mailto:bob@gnu.org

Procesamiento de listas

Para el ojo inexperto, Lisp es un lenguaje de programación extraño. En código Lisp hay paréntesis por todas partes. Algunas personas incluso afirman que el nombre signfica ‘Lots of Isolated Silly Parentheses’ (‘Montones de Paréntesis Aislados Estúpidos’). Pero el reclamo es injustificado. Lisp significa LISt Processing, y el lenguaje de programación maneja listas (y listas de listas) poniéndolas entre paréntesis. Los paréntesis marcan los límites de la lista. Algunas veces una lista va precedida por un apóstrofe simple o una marca de cita, ‘'’1. Las listas son el fundamento de Lisp.

Listas Lisp

En Lisp, una lista tiene el siguiente aspecto: '(rosa violeta margarita tulipan). Esta lista es precedida por una comilla. Bien, podría estar escrita de la siguiende manera, que se parece mas al tipo de lista con la que probablemente estes familiarizado:

'(rosa
  violeta
  margarita
  tulipan)

Los elementos de esta lista son los nombres de 4 flores diferentes, separados por espacios en blanco y rodeados de paréntesis, como flores en un campo con un muro de piedras a su alrededor.

Las listas también pueden tener números, como en esta lista: (+ 2 2). Esta lista tiene un signo más, ‘+’, seguido por dos ‘2’, cada uno separado por espacios en blanco.

En Lisp, tanto datos como programas se representan de la misma manera; es decir, son a la vez listas de palabras, números, u otras listas, separadas por espacios en blanco y rodeadas de paréntesis. (Puesto que un programa se parece a los datos, un programa puede servir fácilmente como datos para otros; esta es un caracteristica muy poderosa de Lisp.) (A proposito, estas dos marcas de paréntesis no son listas Lisp, porque contienen ‘;’ y ‘.’ como signos de puntuación.)

Aquí hay otra lista, esta vez con una lista dentro de ella:

'(esta lista tiene (una lista dentro de ella))

Los componentes de esta lista son las palabras ‘esta’, ‘lista’, ‘tiene’, y la lista ‘(una lista dentro de ella)’. La lista interior se construye con las palabras ‘una’, ‘lista’, ‘dentro’, ‘de’, ‘ella’.

Átomos Lisp

En Lisp, lo que hemos estado llamando palabras son en realidad átomos. Este término proviene del significado historico de la palabra átomo, que significa ‘indivisible’. En lo que a Lisp concierne, las palabras que hemos estado usando en las listas no se pueden dividir en partes mas pequeñas, sin perder su significado dentro del programa; lo mismo ocurre con números y símbolos de un caracterer como ‘+’. Por otro lado, a diferencia de un átomo antiguo, una lista puede dividirse en partes. Consulta la seccion car, cdr, cons: Funciones fundamentales.

En una lista, los átomos se separan unos de otros por espacios en blanco. Pueden estar justo al lado de un paréntesis.

Técnicamente hablando, una lista en Lisp consiste en paréntesis que rodean átomos separados por espacios en blanco o alrededor de otras lista o alrededor de ambos átomos y otras listas. Una lista puede tener solo un átomo o no tener absolutamente nada en ella. Una lista con nada dentro se ve así: (), y se llama lista vacía. A diferencia de cualquier otra cosa, una lista vacía es tanto un átomo, como una lista al mismo tiempo.

La representación impresa de átomos y listas se llaman expresiones simbólicas o, más concisamente, expresiones-s. La palabra expresión por sí misma puede referirse o bien a la representación impresa, o al átomo o a la lista tal como se encuentra internamente en el ordenador. Con frecuencia, la gente usan el término expresión indiscriminadamente. (Ademas, en muchos textos, la palabra forma se usa como un sinónimo de expresión.)

Por cierto, los átomos que componen nuestro universo fueron nombrados asi cuando se pensaba que eran indivisibles; pero se ha descubierto que los átomos fisicos no son indivisibles. Las partes pueden dividir un átomo o puede fisionarse en 2 partes de igual tamaño. Los átomos físicos se nombraron prematuramente, antes de que su verdadera naturaleza fuese encontrada. En Lisp, ciertos tipos de átomos, como un array, pueden ser separados en partes; pero el mecanismo de hacer esto es diferente del mecanismo para dividir una lista. En lo que se refiere a las operaciones de lista, los átomos de una lista son indivisibles.

Al igual que en el español, el significado de las letras que componen un átomo Lisp difieren del significado de las letras compuestas como una palabra. Por ejemplo, la expresión ‘ay’, es completamente diferente de las dos palabras ‘a’, e ‘y’.

Hay muchos tipos de átomos en la naturaleza, pero solo unos pocos en Lisp: por ejemplo, los números, como 37, 511, o 1729, y los símbolos, como ‘+’, ‘foo’, o ‘forward-line’. Las palabras que hemos listado en los ejemplos anteriores son todos símbolos. En las conversaciones cotidianas de Lisp, la palabra “átomo” no se usa con frecuencia, porque los programadores normalmente intentan ser más específicos acerca del tipo de átomo que están tratando. La programación Lisp es mayormente sobre símbolos (y algunas veces números) dentro de listas. (De ese modo, tres palabras rodeadas de paréntesis son una lista apropiada en Lisp, ya que consiste de átomos, que en este caso son símbolos, separados por espacios en blanco y encerrados entre paréntesis, sin ninguna puntuacion ajena a Lisp.)

El texto entre comillas––incluso oraciones o párrafos––son también un átomo. Aquí hay un ejemplo:

'(esta lista incluye "texto entre comillas.")

En Lisp, todo el texto entre comillas, incluyendo la marca de puntuación y los espacios en blanco, son un unico átomo. Este tipo de átomo se llama string (cadena), por ‘cadena de caracteres’) y es el tipo de cosa que se utiliza para los mensajes que un ordenador puede imprimir para que los lea un humano. Las cadenas son un tipo de átomo diferente a los números, o símbolos y se usan de manera diferente.

Espacios en blanco en las listas

La cantidad de espacios en blanco en una lista no importa. Desde el punto de vista del lenguaje Lisp,

'(esta lista
   se ve asi)

es exactamente lo mismo que esto:

'(esta lista se ve asi)

Ambos ejemplos muestran lo que para Lisp es la misma lista, la lista compuesta por los símbolos ‘esta’, ‘lista’, ‘se’, ‘ve’, y ‘asi’ en ese orden.

Los espacios en blanco adicionales y los saltos de línea están diseñados para crear una lista más legible para los humanos. Cuando Lisp lee la expresión, se deshace de todos los espacios en blanco adicionales (pero necesita tener al menos un espacio entre los átomos para poder distinguirlos.)

Por extraño que parezca, los ejemplos que hemos visto cubren casi todos los aspectos de las listas de Lisp. Cualquier otra lista en Lisp se ve más o menos igual a uno de estos ejemplos, excepto que la lista puede ser más larga y compleja. En resumen, una lista está entre paréntesis, una cadena entre comillas, un símbolo se parece a una palabra, y un número a un número. (Para ciertas situaciones, es pueden utilizar corchetes, puntos y otros caracteres especiales; sin embargo; iremos bastante lejos sin ellos.)

GNU Emacs te ayuda a escribir listas

Cuando se escribe una expresión Lisp en GNU Emacs usando el modo de Interacción Lisp o el modo Emacs Lisp, están disponibles varios comandos para formatear la expresión Lisp, de modo que sea fácil de leer. Por ejemplo, presionando la tecla TAB automáticamente se indenta la línea donde se encuentra el cursor a la cantidad correcta. Un comando para indentar apropiadamente el código en una región suele vincularse a M-C-\. La indentación está diseñada de modo que se pueda ver qué elementos pertenecen a cada lista––los elementos de una sublista están más indentados que los elementos de la lista adjunta.

Además, al escribir un paréntesis de cierre, Emacs mueve el cursor momentáneamente hacia el parentesis de apertura correspondiente, para que puedas ver cual es. Esto es muy útil, ya que cada lista que escribas en Lisp debe tener su paréntesis de cierre que coincida con su paréntesis de apertura. (Consulta la Seccion Modos Mayores en El Manual de GNU Emacs, para más información sobre los modos de Emacs.)

Ejecutar un programa

Una lista en Lisp––cualquier lista––es un programa listo para ejecutarse. Si lo ejecutas (lo que la jerga Lisp llama evaluar), el ordenador hará una de tres cosas: nada excepto devolverte la lista misma; enviar un mensaje de error; o, tomar el primer símbolo en la lista como un comando para hacer alguna cosa. (¡Normalmente, por supuesto, es la última de estas tres cosas lo que realmente quieres!).

El apóstrofe, ', que puse delante de algunas de las listas de ejemplo en las secciones anteriores se llama quote (citar); cuando precede a una lista, le dice a Lisp que no haga nada con la lista, mas que tomarla tal como está escrita. Pero si no hay una cita precediendo la lista, el primer elemento de la lista es especial: es un comando que el ordenador debe obedecer. (En Lisp, estos comandos se llaman funciones.) La lista (+ 2 2) mostrada a continuación no tiene una cita delante de ella, por lo que Lisp comprende que + es una instrucción para hacer alguna cosa con el resto de la lista: sumar los números que siguen.

Si estás leyendo esto dentro de GNU Emacs, aquí está como puedes evaluar tal lista: coloca tu cursor justo después del paréntesis derecho de la siguiente lista y presiona C-x C-e:

(+ 2 2)

Verás que el número 4 aparece en el área eco. (En la jerga, lo que acabas de hacer es “evaluar la lista.” El área de eco es la línea en la parte inferior de la pantalla que muestra o hace “eco” del texto.) Ahora intenta lo mismo con una lista con cita: coloca el cursor justo después de la siguiente lista y presiona C-x C-e:

'(esto es una lista citada)

Verás aparecer (esto es una lista citada) en el área eco.

En ambos casos, lo que estás haciendo es dar un comando al programa dentro de GNU Emacs llamado intérprete Lisp––dando al intérprete un comando para evaluar la expresión. El nombre del intérprete Lisp viene de la palabra para la tarea realizada por un humano que viene con el significado de una expresión––quien lo “interpreta”.

También puedes evaluar un átomo que no es parte de una lista––uno que no está rodeado por paréntesis; una vez mas, el intérprete Lisp traduce desde la expresión humanamente legible al lenguaje del ordenador. Pero antes de discutir esto (consulta la Seccion Variables), veremos lo que hace intérprete de Lisp cuando cometes un el error.

Generar un mensaje de error

En parte para que no te preocupes si generas un mensaje de error de manera accidental, ahora daremos un comando al intérprete de Lisp que genera un mensaje de error. Esta es una accion inofensiva; y de hecho, a menudo intentaremos generar mensajes de error de manera intencional. Una vez se entiende la jerga, los mensajes de error pueden ser informativos. En lugar de llamarse mensajes de “error”, deberían llamarse mensajes de “ayuda”. Son como señales para un viajero en un país extraño; descifrarlas puede ser dificil, pero una vez se comprenden, pueden señalar el camino.

El mensaje de error es generado por un depurador de codigo incorporado dentro de GNU Emacs. Vamos a ‘entrar al depurador’. Para salir del depurador, presiona q.

Lo que vamos a hacer es evaluar una lista que no tiene cita y que no tiene un comando con significado como primer elemento. Aquí hay una lista casi exactamente igual a la que acabamos de usar, pero sin la cita al inicio. Coloca el cursor a la derecha donde esta finaliza y presiona C-x C-e:

(esto es una lista sin cita)

Se abrirá una ventana *Backtrace* y deberias ver lo siguiente:

---------- Buffer: *Backtrace* ----------
Debugger entered--Lisp error: (void-function esto)
  (esto es una lista sin cita)
  eval((esto es una lista sin cita) nil)
  eval-last-sexp-1(nil)
  eval-last-sexp(nil)
  call-interactively(eval-last-sexp nil nil)
  command-execute(eval-last-sexp)
---------- Buffer: *Backtrace* ----------

Tu cursor estará en esta ventana (es posible que tengas que esperar unos pocos segundos antes de que se haga visible). Para salir del depurador y hacer que su ventana desaparezca, presiona: q

Por favor, ahora presiona q, para que puedas comprobar que se puede salir del depurador. A continuacion, presiona C-x C-e una vez mas para re-entrar.

Basandonos en lo que ya sabemos, casi podemos leer este mensaje de error.

Leyendo el búfer *Backtrace* desde abajo hacia arriba; dice lo que hizo Emacs. Cuando presionaste C-x C-e, se hiso una llamada interactiva al comando eval-last-sexp. eval es una abreviatura para ‘evaluar’ y sexp es una abreviatura para ‘expresión simbólica’. El comando significa ‘evalúa la última expresión simbólica’, que es la expresión inmediatamente anterior al cursor.

Cada linea sobre esta cuenta lo que el intérprete Lisp evaluo a continuacion. La acción más reciente está en la parte superior. El búfer se llama *Backtrace* por que permite realizar un seguimiento de Emacs hacia atrás.

En la parte superior del búfer *Backtrace*, verás la línea:

Debugger entered--Lisp error: (void-function esto)

El intérprete Lisp intentó evaluar el primer átomo de la lista, la palabra ‘esto’. Es esta acción la que genero el mensaje de error ‘void-function esto’.

El mensaje contiene las palabras ‘void-function’ y ‘esto’.

La palabra ‘function’ (función) fué mencionada antes. Es una palabra muy importante. Para nuestros propósitos, podemos definirla diciendo que una función es un conjunto de instrucciones para decirle al ordenador que haga alguna cosa.

Ahora podemos empezar a entender el mensaje de error: ‘void-function esto’. La función (es decir, la palabra ‘esto’) no tiene una definición de ningun conjunto de instrucciones que el ordenador pueda realizar.

La palabra ligeramente extraña, ‘void-function’, está diseñada para cubrir la forma en que Emacs Lisp lo implementa, que es cuando un símbolo no tiene una definición de función adjunta, el sitio que contiene la instruccion esta ‘vacio’ (void).

Por otro lado, ya que fuimos capaces de sumar 2 más 2 de manera exitosa, evaluando (+ 2 2), se puede inferir que el símbolo + debe tener un conjunto de instrucciones que el ordenador ejecuta y estas instrucciones deben ser para sumar los números despues del +.

Es posible evitar que Emacs entre en el depurador en casos como este. No se explicará cómo hacer esto aquí, pero mencionamos como se ve el resultado, porque puede que te encuentres con una situación similar si hay un error en algún código de Emacs que estes usando. En tales casos, solo verá una línea de mensaje de error; aparecera en el área de eco con el siguente aspecto:

Symbol's function definition is void: esto

El mensaje desaparece tan pronto se presione una tecla, aunque sólo sea para mover el cursor.

Conocemos el significado de la palabra ‘Symbol’. Se refiere al primer átomo de la lista, la palabra ‘esto’. La palabra ‘function’ se refiere a las instrucciones que indican al ordenador que hacer. (Técnicamente, el símbolo le indica al ordenador donde encontrar las instrucciones, pero esta es una complicación que podemos ignorar por el momento.)

El mensaje de error es comprensible: ‘La definición del símbolo está vacía: esto’. El símbolo (que es, la palabra ‘esto’) carece de instrucciones para que el ordenador las lleve a cabo.

Nombres de símbolos y definiciones de funcion

Podemos articular otra característica de Lisp basada en lo que hemos discutido hasta ahora––una característica importante: un símbolo, como +, no es en sí mismo el conjunto de instrucciones que el ordenador lleva a cabo. En su lugar, el símbolo se utiliza, quizás temporalmente, como una forma de localizar la definición o conjunto de instrucciones. Lo que vemos es el nombre con el cual se pueden encontrar las instrucciones. Los nombres de las personas funcionan de la misma manera. Por ejemplo puedo ser referido como ‘Bob’; sin embargo, no soy las letras ‘B’, ‘o’, ‘b’ pero soy, o fuí, conscientemente asociado con una forma de vida en particular. El nombre no soy yo, pero puede ser usado para referirse a mi.

En Lisp, un conjunto de instrucciones puede ligarse a varios nombres. Por ejemplo, las instrucciones para sumar números se pueden ligar al símbolo mas asi como al símbolo + (y se encuentran en algunos dialectos de Lisp). Entre los humanos, puede referirse a ‘Robert’ tan bien como ‘Bob’ y con otras palabras también.

Por otra parte, un símbolo solo puede estar ligado con una definicion de función a la vez. De lo contrario, el ordenador estaría confundido en cuanto a qué definición usar. Si este fuera el caso entre las gente, solo una persona en el mundo podría llamarse ‘Bob’. Sin embargo, la definición de función a la que el nombre hace referencia puede cambiarse fácilmente. (Consulta la Sección Instalar una Definición de Función.)

Ya que Emacs Lisp es extenso, se acostumbra nombrar los símbolos de una forma que identifique la parte de Emacs a la que pertenece la función. En consecuencia, todos los nombres de funciones relacionadas con Texinfo comienzan con ‘texinfo-’ y aquellas relacionadas con la lectura de correo empiezan con ‘rmail-’.

El intérprete Lisp

Basado en lo que hemos visto, ahora podemos empezar a entender lo que hace el intéprete Lisp cuando le ordenamos que evalue una lista. Primero, examina si hay un símbolo de cita antes de la lista; si lo hay, el intérprete solo nos da la lista. Por otra parte, si no hay cita, el intéprete mira si el primer elemento de la lista tiene una definición de función. De lo contrario, el intérprete imprime un mensaje de error.

Así es como funciona Lisp. Simple. Hay complicaciones añadidas a las que llegaremos en un minuto, pero estos son los fundamentos. Por supuesto, para escribir programas Lisp, se necesita saber como escribir definiciones de función y vincularlas a nombres, y como hacer esto sin confundirnos a nosotros mismos o al ordenador.

Ahora, una primera complicación. Además de las listas, el intérprete Lisp puede evaluar un símbolo sin citar y que no tiene paréntesis en torno a el. El intérprete intentará determinar el valor del símbolo como una variable. Esta situación se describe en el apartado de las variables. (Consulta la Seccion Variables.)

La segunda complicación ocurre debido a que algunas funciones son inusuales y no funcionan de la manera habitual. Estas se llaman formas especiales (special forms). Se utilizan para trabajos especiales, como definir una función, y no hay muchas de ellas. En los siguientes capítulos, se presentaran varias de las formas especiales más importantes.

La tercera y ultima complicación es la siguiente: si la función que el intérprete Lisp está examinando no es una forma especial, y si es parte de una lista, el intérprete Lisp mira si la lista tiene una lista dentro de ella. Si existe una lista interna, el intérprete Lisp primero determina lo qué debe hacer con la lista interna, y luego trabaja en la lista externa. Si hay otra lista incrustada dentro de la lista interna, trabaja en esta primero, y así sucesivamente. Siempre se trabaja en la lista mas interna primero. El interprete trabaja primero en la lista más interana, para evaluar el resultado de esa lista. El resultado puede ser usado por la expresión entre paréntesis.

Por lo demas, el intérprete trabaja de izquierda a derecha, de una expresión a la siguiente.

Codigo Compilado

Otro aspecto de la interpretación: el intérprete Lisp es capaz de interpretar dos tipos de entidades: código humanamente legible, en el que nos centraremos exclusivamente, y código especialmente procesado, llamado compilado, que no es humanamente legible. El código compilado se ejecuta más rápido que el código legible humanamente.

Tu puedes transformar código legible por humanos en código compilado ejecutando uno de los comandos de compilación como byte-compile-file. El código compilado se almacena normalmente en un fichero que finaliza con una extensión .elc en lugar de una extensión .el. Verás ambos tipos de ficheros en el directorio emacs/lisp; los ficheros para leer son aquellos con la extensión .el.

Como una cuestión práctica, para hacer la mayoría de las cosas como personalizar o extender Emacs, no necesitas compilar codigo; y no comentare el asunto aquí. Consulta la Seccion Código Compilado en El Manual de Referencia de GNU Emacs Lisp, para una descripción completa de la compilacion de código.

Evaluación

Cuando el intérprete Lisp trabaja en una expresión, el término para la esa actividad se llama evaluación. Decimos que el intérprete ‘evalúa la expresión’. He usado este término varias veces antes. La palabra proviene de su uso en el lenguaje cotidiano, ‘para determinar el valor o la cantidad de; para evaluar’ segun el Webster's New Collegiate Dictionary.

Después de evaluar una expresión, es muy probable que el intérprete Lisp devuelva el valor que el ordenador produce al ejecutar las instrucciones que encuentra en la definición de la función, o quizás se de por vencido con en esa función y produzca un mensaje de error. (El intérprete también puede quedarse colgado, por así decirlo, a una función diferente o puede intentar repetir continuamente lo que está haciendo por siempre en lo que se llama un ‘bucle infinito’. Estas acciones son menos comunes; y podemos ignorarlas). Con mas frecuencia, el intérprete devuelve un valor.

Al mismo tiempo que el intérprete devuelve un valor, también puede realizar cualquier otra cosa, como mover un cursor o copiar un fichero; este otro tipo de acción se denomina efecto secundario. Acciones que los humanos pensamos que son importantes, como imprimir resultados, con frecuencia son, “efectos secundarios” para el intérprete Lisp. La jerga puede sonar peculiar, pero resulta que es bastante fácil aprender a utilizar los efectos secundarios.

En resumen, la evaluacion de una expresión simbólica normalmente causa que el intérprete devuelva un valor y tal vez lleve a cabo un efecto secundario; o al menos produca un error.

Evaluación de listas internas

Si la evaluación se aplica a una lista que está dentro de otra lista, la lista externa puede usar el valor devuelto por la primer evaluación como información cuando se evalua la lista externa. Esto explica por qué las expresiones internas se evaluan primero: los valores devueltos son usados por las expresiones externas.

Podemos investigar este proceso evaluando otro ejemplo de sumas. Coloca tu cursor después de la siguiente expresión y presiona C-x C-e:

(+ 2 (+ 3 3))

El número 8 aparecerá en el área de eco.

Lo que ocurre es que el intérprete Lisp primero evalúa la expresión interna, (+ 3 3), para lo cual se devuelve el valor 6; luego evalúa la expresión externa como si fuera escrita (+ 2 6), que devuelve el valor 8. Puesto que no hay más expresiones adjuntas a evaluar el intérprete imprime este valor en el área de eco.

Ahora es fácil comprender el nombre del comando invocado por el atajo C-x C-e: el nombre es eval-last-sexp. Las letras sexp son una abreviatura de ‘expresión simbólica’ (symbolic expression), y eval es una abreviatura de ‘evaluar’ (evaluate). El comando significa ‘evaluar la última expresión simbólica’.

Como un experimento, puedes intentar evaluar la expresión poniendo el cursor al principio de la siguiente línea inmediatamente después de la expresión, o dentro de la expresión.

Aquí hay otra copia de la expresión:

(+ 2 (+ 3 3))

Si colocas el cursor al principio de la línea en blanco que sigue inmediatamente a la expresión y presionas C-x C-e, aún obtendrás el valor 8 impreso en el área eco. Ahora coloca el cursor dentro de la expresión. Si lo pones justo después del penúltimo paréntesis (de modo que parezca estar sobre el último paréntesis), ¡obtendrás un 6 impreso en el área de eco! Esto se debe a que el comando evalúa la expresión (+ 3 3).

Ahora coloca el cursor inmediatamente después de un número. Presiona C-x C-e y obtendras el número en sí. En Lisp, si evalúas un número, obtienes el número en sí––así es cómo los números difieren de los símbolos. Si evalúas una lista que inicia con un símbolo como +, obtendras un valor devuelto que es el resultado del ordenador tras ejecutar las instrucciones que aparecen en la definición de la función ligada a ese nombre. Si se evalua un símbolo por sí mismo, sucede algo diferente, como veremos en la siguiente sección.

Variables

En Emacs Lisp, un símbolo puede estar ligado a un valor como a una definición de función. Los dos son diferentes. La definición de función es un conjunto de instrucciones que el ordenador ejecuta. Un valor, por otro lado, es algo, como un número o un nombre, que puede variar (razon por la cual tal símbolo se llama variable). El valor de un símbolo puede ser cualquier expresión de Lisp, por ejemplo un símbolo, un número, una lista, o una cadena. Un símbolo que tiene un valor con frecuencia se llama variable.

Un símbolo puede ser tanto una definición de función como un valor asociado a el al mismo tiempo. O puede tener uno u otro. Los dos son independientes. Esto es algo similar a la forma en que el nombre Cambridge puede referirse a la ciudad en Massachusetts y tener alguna información ligada al nombre, por ejemplo, como “gran centro de programación”.

Otra manera de pensar en esto es imaginar un símbolo como un mueble con cajones. La definición de función se coloca en un cajón, el valor en otro, etcetera. Lo que se pone en el cajón que contiene el valor se puede cambiar sin afectar el contenido del cajón que almacena la definición de función, y viceversa.

La variable fill-column ilustra un símbolo con un valor adjunto: en cada buffer de GNU Emacs, este símbolo se establece en algún valor, normalmente 72 o 70, pero algunas veces en algún otro valor. Para encontrar el valor de este símbolo, evalúalo por sí mismo. Si estás leyendo esto dentro de GNU Emacs, puedes hacerlo poniendo el cursor después del símbolo y pulsando C-x C-e:

fill-column

Después de presionar C-x C-e, Emacs imprimió el número 72 en mi área de eco. Este es el valor que he establecido para fill-column mientras escribo esto. Puede ser diferente en tu búfer. Observa que el valor devuelto como una variable se imprime exactamente de la misma forma que el valor devuelto por una función tras ejecutar sus instrucciones. Desde el punto de vista del intérprete Lisp, un valor devuelto es un valor devuelto. La clase de expresion de la que proviene deja de importar una vez se conoce el valor.

Un símbolo puede tener cualquier valor ligado a él o, siendo tecnicos, se puede enlazar la variable a un valor: a un número, por ejemplo 72; a una cadena, "como esta"; a una lista, como (abeto pino roble); podemos incluso asociar una variable a una definición de función.

Un símbolo puede vincularse a un valor de varias maneras. Consulta la Sección Establecer el valor de una variable, para obtener información sobre como hacerlo.

Mensaje de error de un símbolo sin una función

Cuando evaluamos fill-column para encontrar su valor como una variable, no pusimos paréntesis alrededor de la palabra. Esto se debe a que no pretendiamos usarla como nombre de función.

Si fill-column fuese el primer o único elemento de una lista, el intérprete Lisp intentaría encontrar la definición de función adjunta. Pero fill-column no tiene una definición de función. Intenta evaluar esto:

(fill-column)

Se creará un buffer *Backtrace* que dice:

---------- Buffer: *Backtrace* ----------
Debugger entered--Lisp error: (void-function fill-column)
  (fill-column)
  eval((fill-column))
  eval-last-sexp-1(nil)
  eval-last-sexp(nil)
  call-interactively(eval-last-sexp)
---------- Buffer: *Backtrace* ----------

(Recuerda, para salir del depurador y hacer que la ventana del depurador desaparezca, presiona q en el buffer *Backtrace*.)

Mensaje de error de un símbolo sin un valor

Si intentas evaluar un símbolo que no tiene un valor asociado, recibirás un mensaje de error. Esto se puede ver experimentando con nuestra suma 2 más 2. En la siguiente expresión, coloca el cursor justo después del +, antes del primer número 2, presiona C-x C-e:

(+ 2 2)

En GNU Emacs 22, se creará un buffer *Backtrace* que dice:

---------- Buffer: *Backtrace* ----------
Debugger entered--Lisp error: (void-variable +)
  eval(+ nil)
  elisp--eval-last-sexp(nil)
  eval-last-sexp(nil)
  funcall-interactively(eval-last-sexp nil)
  call-interactively(eval-last-sexp nil nil)
  command-execute(eval-last-sexp)
---------- Buffer: *Backtrace* ----------

(De nuevo, puedes salir del depurador pulsando q en el búfer *Backtrace*.)

Esta traza inversa es diferente del primer mensaje de error que vimos, que decia, ‘Debugger entered--Lisp error: (void-function esto)’. En este caso, la función no tiene una valor como variable; mientras en el otro mensaje de error, la función (la palabra ‘esto’) no tenia una definición.

En este experimento con el +, lo que hicimos fué hacer que el intérprete Lisp evalúe el + y busque el valor de la variable en lugar de la definición de la función. Hicimos esto colocando el cursor justo después del símbolo en lugar de ponerlo al final de los parentesis que cierran la lista como hicimos antes. Como consecuencia, el intérprete Lisp evaluó la expresión-s precedente, que en este caso era el + en sí.

Ya que + no tiene un valor asociado, solo la definición de función, el mensaje de error informaba que el valor del símbolo como una variable esta vacío.

Argumentos

Para ver cómo se pasa la información a las funciones, veamos de nuevo nuestro viejo recurso, la suma de dos más dos. En Lisp, esto se escribe de la siguiente manera:

(+ 2 2)

Si evalúas esta expresión, el número 4 aparecerá en el área de eco. Lo que el intérprete de Lisp hace es sumar los números despues del +.

Los números sumados por + se llaman argumentos de la función +. Estos números son la información que se da o se pasa a la función.

La palabra ‘argumento’ proviene de la forma en que se usa en matemáticas y no se refiere a una disputa entre dos personas, En su lugar, se refiere a la información entregada a la función, en este caso, al +. En Lisp, los argumentos de una función son los átomos o listas que siguen a la función. Los valores devueltos por la evaluación de estos átomos o listas se transfieren a la función. Funciones diferentes requieren diferentes números de argumentos; algunas funciones no requieren ninguno en absoluto.2

Tipos de datos de los argumentos

El tipo de datos que deben pasarse a una función dependen del tipo de información que utilice. Los argumentos de una función como + deben tener valores númericos, ya que + suma números. Otras funciones utilizan diferentes tipos de datos para sus argumentos.

Por ejemplo, la función concat concatena o une dos o más cadenas de texto para producir una cadena. Los argumentos son cadenas. Concatenar las cadenas de caracteres abc, def produce una cadena abcdef. Esto se puede ver evaluando lo siguiente:

(concat "abc" "def")

El valor producido al evaluar esta expresión es "abcdef".

Una función como substring utiliza tanto una cadena como numeros como argumentos. La función devuelve una parte de la cadena, una subcadena del primer argumento. Esta función toma tres argumentos. Su primer argumento es la cadena de caracteres, el segundo y tercer argumento son números que indican el principio y el fin de la subcadena. Los números son un recuento del número de caracteres (incluyendo espacios y si signos de puntuacion) desde el principio de la cadena.

Por ejemplo, si evalúas lo siguiente:

(substring "El rápido zorro marrón saltó." 10 15)

Veras aparecer "zorro" en el área de eco. Los argumentos son la cadena y los dos números.

Ten en cuenta que la cadena pasada a substring es un unico átomo a pesar de estar compuesto de varias palabras separadas por espacios. Lisp considera todo entre las dos comillas como parte de la cadena, incluyendo los espacios. Puedes pensar en la función substring como una especie de ‘acelerador de particulas’ ya que toma un átomo de otro modo indivisible y extrae una parte. Sin embargo, substring solo es capaz de extraer una subcadena de un argumento que es una cadena, no de otro tipo de átomo por ejemplo un número o símbolo.

Un argumento como el valor de una variable o lista

Un argumento puede ser un símbolo que devuelve un valor cuando se evalua. Por ejemplo, evaluar el símbolo fill-column en si mismo, devuelve un número. Este número se puede utilizar en una suma.

Coloca el cursor después de la siguiente expresión y presiona C-x C-e:

(+ 2 fill-column)

El valor será dos mas el número que se obtiene evaluando fill-column. En mí caso, es 74, porque mi valor de fill-column es 72.

Como acabamos de ver, un argumento puede ser un símbolo que devuelve un valor cuando se evalúa. Además, un argumento puede ser una lista que devuelve un valor cuando se evalúa. Por ejemplo, en la siguiente expresión, los argumentos de la función concat son las cadenas "Los " y " zorros rojos." y la lista (number-to-string (+ 2 fill-column)).

(concat "Los " (number-to-string (+ 2 fill-column)) " zorros rojos.")

Si evaluas esta expresión––y si, como con mi Emacs, fill-column se evalúa a 72––aparecerá "Los 74 zorros rojos." en el área de eco. (Ten en cuenta que debes poner espacios después de la palabra ‘Los’ y antes de la palabra ‘zorros’ para que aparezcan en la cadena final. La función number-to-string convierte el entero que devuelve la función suma a una cadena. number-to-string también se conoce como int-to-string.)

Número de argumentos variable

Algunas funciones, como concat, +, o *, toman cualquier número de argumentos. (* es el símbolo para la multiplicacion.) Esto se puede ver evaluando cada una de las siguientes expresiones de la forma habitual.

En el primer conjunto, las funciones no tienen argumentos:

> (+)
0
> (*)
1

En este conjunto, las funciones tienen un argumento cada una:

> (+ 3)
3
> (* 3)
3

En este conjunto, las funciones tienen tres argumentos cada una:

> (+ 3 4 5)
12
> (* 3 4 5)
60

Usando el tipo incorrecto de objeto como argumento

Cuando a una función se le pasa un argumento del tipo incorrecto, el interpréte Lisp produce un mensaje de error. Por ejemplo, la función + espera que los valores de sus argumentos sean números. Como un experimento podemos pasar el símbolo citado hola en lugar de un número. Coloca el cursor después de la siguiente expresión y presiona C-x C-e:

(+ 2 'hola)

Al hacer esto se generará un mensaje de error. Lo qué ha ocurrido es que + ha intentado sumar el 2 al valor devuelto por 'hola, pero el valor devuelto por 'hola es el símbolo hola, no un número. Solo los números se pueden sumar. Por tanto + no pudo llevar a cabo su suma.

Se creará e ingresara a un búfer *Backtrace* que dice:

---------- Buffer: *Backtrace* ----------
Debugger entered--Lisp error: (wrong-type-argument number-or-marker-p hola)
  +(2 hola)
  eval((+ 2 (quote hola)) nil)
  eval-last-sexp-1(nil)
  eval-last-sexp(nil)
  call-interactively(eval-last-sexp nil nil)
  command-execute(eval-last-sexp)
---------- Buffer: *Backtrace* ----------

Como de costumbre, el mensaje de error intenta ser útil y tiene sentido después de aprender a leerlo.3

La primer parte del mensaje de error es sencilla; dice ‘wrong type argument’ (tipo de argumento incorrecto). A continuación viene la misteriosa jerga tecnica ‘number-or-marker-p’. Esta palabra está intentando decirte qué tipo de argumento espera +.

El símbolo number-or-marker-p dice que el intérprete Lisp está intentando determinar si la información presentada (el valor del argumento) es un número o una marca (un objeto especial que representa una posición de buffer). Lo que hace es probar si se le estan dando numeros a sumar a +. También comprueba si el argumento es algo llamado marcador, que es una caracteristica específica de Emacs Lisp. (En Emacs, las ubicaciones en un búfer se registran como marcadores. Cuando se establece la marca con el comando C-@ o C-SPC, su posición se guarda como un marcador. La marca se puede consider un número––el número de caracteres es la ubicacion desde el principio del búfer.) En Emacs Lisp, + se puede utilizar para sumar el valor numérico de los marcadores como números.

La ‘p’ en number-or-marker-p es la encarnación de una práctica iniciada en los primeros días de la programación Lisp. La ‘p’ significa ‘predicado’. En la jerga usada por los primeros investigadores de Lisp, un predicado se refiere a una función para determinar si alguna propiedad es verdadera o falsa. Entonces la ‘p’ nos dice que number-or-marker-p es el nombre de una función que determina si el argumento dado es un número o un marcador. Otros símbolos Lisp que finalizan en ‘p’ incluyen zerop, una función que comprueba si su argumento tiene el valor de cero, y listp, una función que comprueba si su argumento es una lista.

Finalmente, la última parte del mensaje de error es el símbolo hola. Este es el valor del argumento que se paso a +. Si a la suma se le hubiera pasado el tipo correcto de objeto, el valor habría sido un número, como 37, en lugar de un símbolo como hola. Pero entonces no habrías recibido el mensaje de error.

La función message

Al igual que +, la función message toma un número de argumentos variable. Se utiliza para enviar mensajes al usuario y es tan útil que la describiremos aqui.

Se imprime un mensaje en el área de eco. Por ejemplo, puedes imprimir un mensaje en tu área de eco evaluando la siguiente lista:

(message "¡Este mensaje aparece en el área de eco!")

Toda la cadena entre comillas dobles es un unico argumento y se imprime en su totalidad. (Obseva que en este ejemplo, el mensaje mismo aparece en el área de eco entre comillas dobles; esto se debe a que ves el valor devuelto por la función message. En la mayoría de programas que escribiras, el texto se imprimira en el área de eco como un efecto secundario de message, sin las comillas. Consulta la Sección multiplicar-por-siete interactivo, para ver en detalle un ejemplo de esto.)

Sin embargo, si hay un ‘%s’ en la cadena de caracteres entre comillas, la función message no imprime el ‘%s’ como tal, si no que mira el siguente argumento a continuacion de la cadena. Evalúa el segundo argumento e imprime el valor en la ubicación de la cadena donde está el ‘%s’.

Puedes ver esto colocando el cursor después de la siguiente expresión y presionar C-x C-e:

(message "El nombre de este búfer es: %s." (buffer-name))

En Info, "El nombre de este búfer es: *info*." aparecerá en el área de eco. La función buffer-name devuelve el nombre del búfer como una cadena, que la función message inserta en lugar de %s.

Para imprimir un valor como un entero, utiliza ‘%d’ de la misma forma que ‘%s’. Por ejemplo, para imprimir un mensaje en el área de eco que indique el valor de fill-column, evalúa lo siguiente:

(message "El valor de fill-column es %d." fill-column)

En mi sistema, cuando evalúo esta lista, "El valor de fill-column es 72" aparece en mi área de eco4.

Si hay más de un ‘%s’ en la cadena entre comillas, el valor del primer argumento después de la cadena se imprime en la posición del primer ‘%s’ y el valor del segundo argumento se imprime en la posición del segundo ‘%s’, y así sucesivamente.

Por ejemplo, si evalúas lo siguiente,

(message "¡Hay %d %s en la oficina!"
         (- fill-column 14) "elefantes rosas")

aparecerán un mensaje un poco caprichoso en el área de eco. En mi sistema dice "¡Hay 58 elefantes rosas en la oficina!"

Se evalúa la expresión (- fill-column 14) y el número resultante se inserta en lugar del ‘%d’; y la cadena entre comillas dobles, "elefantes rosas", se trata como un unico argumento y se inserta en lugar del ‘%s’. (Es decir, una cadena entre comillas dobles se evalúa así misma, como un número.)

Por último, aquí está un ejemplo algo complejo que no solo ilustra el cálculo de un número, sino que también muestra como se puede usar una expresión dentro de una expresión para generar el texto que sustituira el ‘%s’:

(message "Él vió %d %s"
         (- fill-column 36)
         (concat (substring
                  "Los rápidos zorros marrones saltaron." 12 18)
                 " rojos"
                 " saltando."))

En este ejemplo, message tiene tres argumentos: la cadena, "Él vió %d %s", la expresión, (- fill-column 32), y la expresion que comienza con la función concat. El valor resultante de la evaluación de (- fill-column 32) se inserta en lugar del ‘%d’; y el valor devuelto por la expresión que inicia con concat se inserta en lugar del ‘%s’.

Cuando fill-column es 70 y evaluas la expresión, aparecera el mensaje "Él vió 38 zorros rojos saltando." en tu área de eco.

Establecer el valor de una variable

Hay varias formas de asignar un valor a una variable. Una de ellas es utilizar la función set o la función setq. Otra forma es utilizar let (Consulta la Seccion let). (La jerga para este proceso es ligar (bind) una variable a un valor.)

Las siguientes secciones no solo describen cómo operan set y setq, también ilustran como se pasan los argumentos.

Usando set

Para establecer el valor del símbolo flores a la lista '(rosa violeta margarita tulipan), evalúa la siguiente expresión colocando el cursor después de la expresión y presiona C-x C-e.

(set 'flores '(rosa violeta margarita tulipan))

La lista (rosa violeta margarita tulipan) aparecerá en el área de eco. Esto es la que devuelve la función set. Como efecto secundario, el símbolo flores esta ligado a la lista; es decir, el símbolo flores, puede verse como una variable, que entrega la lista como su valor. (Por cierto, este proceso, ilustra como un efecto secundario del intérprete Lisp, al establecer el valor, puede ser el principal efecto que nos interesa a los humanos. Esto se debe a que cada función Lisp debe devolver un valor si no obtiene un error, pero solo tendrá un efecto secundario si está diseñada para tenerlo.)

Después de evaluar la expresión set, se puede evaluar el símbolo flores y devolvera el valor que se acaba de establecer. Aquí está el símbolo. Coloca el cursor al final de este y presiona C-x C-e.

flores

Al evalúar flores, aparece la lista (rosa violeta margarita tulipan) en el área de eco.

Por cierto, si se evalúa 'flores, la variable con una cita frente a ella, lo que verás en el área de eco es el símbolo en sí mismo, flores. Aquí está el símbolo citado, asi que puedes intentarlo:

'flores

Ten en cuenta también, que cuando se utiliza set, es necesario citar ambos argumentos, a menos que quieras que sean avaluados. Como no queremos que se evalue ninguno de los dos argumentos, se citan tanto la variable flores, como la lista (rosa violeta margarita tulipan). (Cuando se utiliza set sin citar su primer argumento, el primer argumento se evalúa antes de realizar cualquier otra cosa. Si se hace esto y flores no tenía ya un valor, obtendras un mensaje de error de tipo, ‘El valor de símbolo como variable esta vacío’; por otro lado, si flores regresa un valor después de ser evaluada, set intentaría establecer el valor que fue devuelto. Hay situaciones donde esto es justo lo que la función a de hacer, pero estas situaciones son poco frecuentes.)

Usando setq

Como una cuestión práctica, casi siempre se cita el primer argumento de set. La combinación de set y un primer argumento citado es tan común que tiene su propio nombre: la forma especial setq. Esta forma especial es similar a set excepto que el primer argumento se citado automáticamente, por lo que no es necesario escribir la marca de cita. Ademas, como una conveniencia adicional, setq permite asignar varias variables diferentes a diferentes valores, todo en una sola expresión.

Para establecer el valor de la variable carnívoros a la lista '(leon tigre leopardo) usando setq, se utiliza la siguiente expresión:

(setq carnivoros '(leon tigre leopardo))

Esto es exactamente igual que usar set excepto que el primer argumento se cita automáticamente por setq. (La ‘q’ en setq significa quote (cita).)

Con set, la expresión se vería asi:

(set 'carnivoros '(leon tigre leopardo))

Además, setq se puede utilizar para asignar diferentes valores a diferentes variables. El primer argumento se une al valor del segundo argumento, el tercer argumento se une a al valor del cuarto argumento, y así sucesivamente. Por ejemplo, podría utilizar lo siguiente para asignar una lista de árboles al símbolo arboles y una lista de herbívoros al símbolo herbivoros:

(setq arboles '(pino abeto roble arce)
      herbivoros '(gacela antilope cebra))

(La expresión podría también haber estado en una unica línea, pero podría no caber en una página; y a los humanos les resulta más fácil leer listas con un formato agradable.)

Aunque he estado usando el término ‘asignar’, hay otra forma de pensar respecto a el funcionamiento de set y setq; y consiste en decir que set y setq hacen que el símbolo apunte a la lista. Esta ultima forma de pensar es muy común y en los proximos capítulos nos encontraremos con al menos un símbolo que tiene ‘puntero’ como parte de su nombre. El nombre se elige porque el símbolo tiene un valor, específicamente una lista, unida a el; o, expresado de otra manera, el símbolo se establece como “puntero” a la lista.

Conteo

He aquí un ejemplo que muestra cómo utilizar setq en un contador. Es posible usar esto para contar cuantas veces se repite una parte de un programa. En primer lugar se necesita establecer una variable a cero; luego sumar uno al número cada vez que el programa se repita. Para hacer esto, se requiere una variable que sirva como contador, y dos expresiones: una expresión setq inicial que asigne la variable contador a cero; y una segunda expresión setq que incremente el contador cada vez se evalue.

(setq contador 0)                ; Llamemos a esto el inicializador.

(setq contador (+ contador 1))   ; Este es el incremento.

contador                         ; Este es el contador.

(El texto que sigue al ‘;’ son los comentarios. Consulta la Seccion Cambiar una definición de función.)

Si evalúas la primera de estas expresiones, el inicializador, (setq contador 0), y luego evalúas la tercera expresión, contador, el número 0 aparecerá en el área de eco. Si a continuación se evalúa la segunda expresión, el incremento, (setq contador (+ contador 1)), el contador tendrá el valor 1. Así que si evalúas de nuevo contador, el número 1 aparecerá en el área de eco. Cada vez que se evalúa la segunda expresión, el valor del contador se incrementara.

Al evalúar el incremento, (setq contador (+ contador 1)), el intérprete Lisp evalúa en primer lugar la lista interna; esta es la suma. Para evaluar esta lista, se debe evaluar la variable contador y el número 1. Cuando evalúa la variable contador, recibe su valor actual. Se pasa este valor y el número 1 a + que los suma. La suma se devuelve como el valor de la lista interior y pasa a setq que establece la variable contador a este nuevo valor. Por lo tanto, el valor de la variable contador, cambia.

Resumen

Aprender Lisp es como subir una colina en la que la primera parte es la mas empinada. Ahora has subido la parte más difícil; lo que queda se vuelve más fácil a medida que avanzas hacia adelante.

En resumen,

Ejercicios

Unos cuantos ejercicios simples:

Practicando la Evaluación

Antes de aprender a escribir una definición de función en Emacs Lisp, es útil dedicar un poco de tiempo a evaluar varias expresiones que ya han sido escritas. Estas expresiones serán listas con funciones como su primer (y con frecuencia único) elemento. Dado que algunas de las funciones asociadas con búfers son a la vez simples e interesantes, empezaremos por ellas. En esta sección, vamos a evaluar algunas. En otra sección, estudiaremos el código de varias otras funciones relacionadas con búfers, para ver la forma cómo fueron escritas.

Siempre que se le da un comando de edición a Emacs Lisp, como el comando para mover el cursor o para desplazarse por la pantalla, se está evaluando una expresión, cuyo primer elemento es una función. Así es cómo funciona Emacs.

Al presionar las teclas, haces que el interprete Lisp evalue una expresion y asi es como obtienes resultados. Incluso escribir texto plano implica evalúar una función de Emacs Lisp, en este caso, se utiliza self-insert-command, que simplemente inserta el caracter que has escrito. Las funciones que se evalúan presionando atajos de teclado se llaman funciones interactivas, o comandos; la forma de hacer que una funcion sea interactiva se ilustrara en el capítulo sobre cómo escribir definiciones de funcion. Consulta la Seccion Crear una Función Interactiva.

Además de presionar comandos de teclado, hemos visto una segunda manera de evaluar una expresión: colocar el cursor después de una lista y presionar C-x C-e. Esto es lo que haremos en el resto de esta sección. Hay otras maneras de evaluar una expresión; estas serán descritas a medida que llegemos a ellas.

Ademas de utilizarlas para prácticar la evaluación, las funciones que se muestran en las siguientes secciones son importantes por derecho propio. Un estudio de estas funciones deja claro la distinción entre búfers y ficheros, cómo cambiar a un búfer, y como determinar una ubicación dentro de el.

Nombres de búfer

Las dos funciones, buffer-name y buffer-file-name, muestran la diferencia entre un fichero y un búfer. Cuando se evalúa la siguiente expresión, (buffer-name), el nombre del buffer aparece en el area eco. Al evaluar (buffer-file-name), el nombre del fichero al que hace referencia el búfer aparece en el área de eco. Por lo general, el nombre devuelto por (buffer-name) es el mismo que el nombre del fichero al que hace referencia, y el nombre devuelto por (buffer-file-name) es la ruta completa del fichero.

Un fichero y un búfer son dos entidades diferentes. Un fichero es la información grabada de manera permanente en el ordenador (a menos que se elimine). Un búfer, por otro lado, es información dentro de Emacs que desaparecerá al final de la sesión de edición (o cuando mates el búfer). Por lo general, un búfer contiene información que se ha copiado desde un fichero; decimos que el búfer está visitando ese fichero. Esta copia es en la que se trabaja y modifica. Los cambios en el búfer no modifican el fichero, hasta guardar el búfer. Al guardar el búfer, el búfer se copia en el fichero y por lo tanto se guarda de forma permanente.

Si está leyendo esto dentro de GNU Emacs, puedes evaluar cada una de las siguientes expresiones colocando el cursor después de estas y pulsando C-x C-e.

(buffer-name)

(buffer-file-name)

Cuando hago esto en el búfer *info*, el valor devuelto evaluando (buffer-name) es "*info*", y el valor devuelto evaluando (buffer-file-name) es nil.

Por otro lado, mientras escribo este documento, el valor devuelto por la evaluación de (buffer-name) es "introduction.texinfo", y el valor evaluando (buffer-file-name) es "/gnu/work/intro/introduction.texinfo".

El primero es el nombre del búfer y el segundo es el nombre del fichero. En Info, el nombre del búfer es "*info*". Info no apunta a ningún fichero, por lo que el resultado de evaluar (buffer-file-name)] es nil. El símbolo nil proviene del latin, significa ‘nada’; en este caso, significa que el búfer no está asociado con ningun fichero. (En Lisp, nil también se utiliza con el significado de ‘falso’ y es sinómino para la lista vacía, ().)

Al escribir esto, el nombre de mi búfer es "introduction.texinfo". El nombre del fichero al que apunta es "/gnu/work/intro/introduction.texinfo".

(En las expresiones, los paréntesis le indican al intérprete Lisp que trate a buffer-name y buffer-file-name como funciones; sin los paréntesis, el intérprete intentaría evaluar los símbolos como variables. Consulta la Sección Variables.)

A pesar de la distinción entre ficheros y búfers, con frecuencia encontraras personas referirse a un fichero cuando se refieren a un búfer y viceversa. De hecho, la mayoría de la gente dice, “Estoy editando un fichero”, en lugar de decir, “Estoy editando un búfer que pronto voy a guardar en un fichero”. Esto casi siempre queda claro a partir del contexto de lo que la gente quiere decir. No obstante, al tratar con programas de ordenador, es importante tener en cuenta la distinción, ya que el ordenador no es tan inteligente como una persona.

Por cierto, la palabra ‘búfer’, viene del significado de la palabra como un cojín que amortigua la fuerza de una colisión. En los primeros ordenadores, un búfer amortiguaba la interacción entre los ficheros y la unidad central de procesamiento del ordenador. Los tambores o cintas que contenian un fichero y la unidad de procesamiento eran equipos muy diferentes entre si, que trabajaban a su propia velocidad, por rafagas. El búfer hizo posible que ambos trabajaran juntos de manera efectiva. Con el tiempo, el búfer pasó de ser un intermediario, un lugar de almacenamiento temporal, a ser el lugar donde se realiza el trabajo. Esta transformación se parace bastante a la de un pequeño puerto que se convierte en una gran ciudad: una vez fué simplemente el lugar donde la carga se almacenaba temporalmente antes de ser cargada en los barcos; despues se convirtio en un centro comercial y cultural por derecho propio.

No todos los búfers están asociados con ficheros. Por ejemplo, el búfer *scratch* no visita ningun fichero. Del mismo modo, un búfer *Help* no está asociado a ningun fichero.

En los viejos tiempos, cuando se carecia de un fichero ~/.emacs y se iniciaba una sesión Emacs escribiendo unicamente el comando emacs, sin nombrar ningun fichero, Emacs iniciaba con el búfer *scratch* visible. Hoy en día, veras una pantalla de bienvenida. Puedes seguir uno de los comandos sugeridos en dicha pantalla, visitar un fichero, o presionar la barra de espacio para acceder al búfer *scratch*.

Si cambias al búfer *scratch*, escribe (buffer-name), coloca el cursor al final de la expresión, y presiona C-x C-e para evaluar la expresión. El nombre *scratch* será devuelto y aparecerá en el área de eco. *scratch* es el nombre del búfer. Al escribir y evaluar (buffer-file-name) en el búfer *scratch*, aparecerá nil en el área de eco, igual que cuando evalúas (buffer-file-name) en Info.

Por cierto, si estas en el búfer *scratch* y quieres que el valor devuelto por una expresión aparezca en el búfer en sí y no en el área de eco, presiona C-u C-x C-e en lugar de C-x C-e. Esto hace que el valor devuelto aparezca después de la expresión. El búfer se verá así:

(buffer-name)"*scratch*"

No se puede hacer esto en Info ya que Info es de solo lectura y no se permitirá cambiar el contenido del búfer. Pero puedes hacer esto en cualquier búfer que puedas editar; y cuando escribes código o documentación (como este libro), esta caracteristica es muy útil.

Obtención de Búfers

La función buffer-name devuelve el nombre del búfer; para obtener el búfer en sí, se necesita una función diferente: la función current-buffer. Si utilizas esta función en el código, lo que se obtiene es el búfer en sí.

Un nombre y el objeto o entidad al que se refiere el nombre son diferentes entre si. Tu no eres tu nombre, eres una persona a la que se refieren los demas por tu nombre. Si pides hablar con Jorge y alguien te entrega una tarjeta con las letras ‘J’, ‘o’, ‘r’, ‘g’, y ‘e’ escritas, podrias divertirte, pero no estarías satisfecho. No quieres hablar con el nombre, sino con la persona a la que se refiere el nombre. Un búfer es similar: el nombre del búfer scratch es *scratch*, pero el nombre no es el búfer. Para obtener un búfer en sí, es necesario utilizar una función como current-buffer.

Sin embargo, hay una ligera complicación: si evalúas current-buffer en una expresión por sí sola, como haremos aquí, lo que se ve es una representación impresa del nombre del búfer sin el contenido del búfer. Emacs funciona de esta forma por dos razones: el búfer puede contener miles de líneas––demasiado largo para mostrarse convenientemente; y, otro búfer puede tener el mismo contenido pero un nombre diferente, y es importante distinguir entre ellos.

Aquí hay una expresión que contiene la función:

(current-buffer)

Si evalúas esta expresión en Info de la manera habitual, aparecerá #<buffer *info*> en el área de eco. El formato especial indica que se está devuendo el búfer en sí, en lugar de solo su nombre.

Por cierto, si bien puedes escribir un número o símbolo en un programa, no se puede hacer esto con la representación impresa del búfer: la única manera de optener un búfer en sí mismo es con una función como current-buffer.

Una función relacionada es other-buffer. Esta devuelve el último bufer seleccionado distinto al que te encuentras actualmente, no una representación impresa de su nombre. Si recientemente has ido y vuelto del búfer *scratch*, other-buffer devolverá ese búfer.

Puedes ver esto evaluando la expresión:

(other-buffer)

Verás que #<buffer *scratch*> aparece en el área de eco, o el nombre de cualquier otro búfer al que allas cambiado anteriormente reciente5

Cambiando búfers

La función other-buffer realmente proporciona un búfer cuando se utiliza como argumento de una función que requiera uno. Podemos ver esto usando other-buffer y switch-to-buffer para cambiar a un búfer diferente.

Pero primero, una breve introducción a la función switch-to-buffer. Cuando cambiaste de ida y vuelta de Info al búfer *scratch* para evaluar (buffer-name), probablemente pulsaste C-x b y luego escribiste *scratch*6 en el minibuffer cuando se solicito el nombre del buffer al que querias cambiar. El atajo, C-x b, hace que el intérprete Lisp evalúe la función interactiva switch-to-buffer. Como hemos dicho anteriormente, así es como funciona Emacs: diferentes atajos de teclado llaman o ejecutan diferentes funciones. Por ejemplo, C-f llama a forward-char, M-e llama a forward-sentence, etcétera.

Al escribir switch-to-buffer en una expresión, y darle un búfer al que cambiar, podemos cambiar de búfer tal y como lo hace C-x b.

(switch-to-buffer (other-buffer))

El símbolo switch-to-buffer es el primer elemento de la lista, por lo que el intérprete Lisp lo tratará como una función y llevara a cabo las instrucciones adjuntas al mismo. Pero antes de hacer esto, el intérprete observara que other-buffer está dentro de paréntesis y trabajara en este símbolo primero. other-buffer es el primer (y en este caso, el único) elemento de esta lista, por lo que el intérprete llama o ejecuta la función. Esto devuelve un búfer distinto al actual. A continuación, el intérprete ejecuta switch-to-buffer, pasando, como argumento, el búfer devuelto, que es al que Emacs cambiara. Si estás leyendo esto en Emacs, prueba ahora. Evalúa la expresión. (Para regresar, presiona C-x b RET.)7

En los ejemplos de las secciones siguentes, veras con más frecuencia la función set-buffer que switch-to-buffer. Esto se debe a la diferencia entre los programas de ordenador y los seres humanos: los humanos tienen ojos y esperan ver el búfer en el que están trabajando en la terminal de su ordenador. Esto es tan evidente, que casi no hace falta decirlo. Sin embargo, los programas no tienen ojos. Cuando un programa de ordenador trabaja en un búfer, el búfer no necesita ser visible en la pantalla.

switch-to-buffer está diseñado para humanos y hace dos cosas diferentes: cambia el búfer a el que Emacs dirige la atención; y cambia el búfer mostrado en la ventana al nuevo búfer. set-buffer, por otro lado, solo hace una cosa: cambia la atención del programa de ordenador a un búfer diferente. El búfer en la pantalla permanece sin cambios (por supuesto, normalmente no pasa nada hasta que el comando termina de ejecutarse).

Además, acabamos introduciendo otro termino tecnico, la palabra llamada. Cuando se evalúa una lista en la que el primer símbolo es una función, se llama a esa función. El uso del término viene de la noción de la función como una entidad que puede algo hacer por ti si la ‘llamas’––al igual que un fontanero es una entidad que puede arreglar una fuga si lo llamas.

Tamaño del búfer y ubicación del punto

Finalmente, veamos algunas funciones bastante simples, como buffer-size, point, point-min, y point-max. Estas proporcionan información sobre el tamaño de un búfer y la ubicación del punto dentro de el.

La función buffer-size te dice el tamaño del búfer actual; es decir, la función devuelve un conteo del número de caracteres en el buffer.

(buffer-size)

Puedes evaluar esto de la forma habitual, coloca el cursor después de la expresión y presiona C-x C-e.

En Emacs, la posición actual del cursor se llama punto. La expresión (point) devuelve un número que indica donde esta situado el cursor como un conteo del número de caracteres desde el principio del búfer hasta el punto.

Puedes ver el conteo de caracteres del punto para este búfer evaluando la siguiente expresión de la forma habitual:

(point)

Mientras escribo esto, el valor de point es 65724. La función point se utiliza con frecuencia en algunos de los ejemplos mas adelante en este libro.

El valor del punto depende, por supuesto, de su ubicacion dentro del búfer. Si evaluas el punto en este lugar, el número será mayor:

(point)

Para mí, el valor del punto en esta posición es 66043, lo que significa que hay 319 caracteres (incluyendo espacios) entre las dos expresiones. (Sin duda, verás números diferentes, ya que lo habré editado desde que evalue el punto por primera vez.)

La función point-min es similar a point, pero devuelve el valor mínimo permitido del punto en el búfer actual. Este es el numero 1 a menos que narrowing esté en efecto. (Narrowing (Reduccion) es un mecanismo mediante el cual puedes restringirte a ti mismo, o a un programa, a operar solo en un parte de un búfer. Consulta la Sección Reducir y Extender.) Del mismo modo, la función point-max devuelve el valor máximo permitido del punto en el búfer actual.

Ejercicio

Busca un fichero en el que trabarjar y avanza hasta su mitad. Encuentra el nombre de búfer, el nombre del fichero, su tamaño, y la posición en el fichero.

Cómo escribir definiciones de funcion

Cuando el intérprete Lisp evalúa una lista, mira si el primer símbolo tiene una definición de funcion adjunta; o, dicho de otro modo, si el símbolo apunta a una definición de función. Si lo hace, el ordenador ejecuta las instrucciones de la definición. Un símbolo que tiene una definición de función se llamada, simplemente, una función (aunque hablando con propiedad, la definición es la función y el símbolo hace referencia a ella.)

Todas las funciones se defininen en términos de otras funciones, a excepción de algunas funciones primitivas que estan escritas en el lenguaje de programación C. Cuando escribas definiciones de funcion, debes escribirlas en Emacs Lisp y utilizar otras funciones como bloques de construcción. Algunas de las funciones que utilizaras estaran a su vez escritas en Emacs Lisp (quizás por tí) y otras serán primitivas escritas en C. Las funciones primitivas se usan exactamente igual que las escritas en Emacs Lisp y se comportan de igual forma. Están escritas en C para que podamos ejecutar facilmente GNU Emacs en cualquier ordenador que tenga la potencia suficiente y pueda ejecutar C.

Permíteme volver a enfatizar esto: cuando se escribe código en Emacs Lisp, no se distinge entre el uso de funciones escritas en C y el uso de funciones escritas en Emacs Lisp. La diferencia es irrevelante. Menciono la distinción solo porque es interesante conocerla. De hecho, a menos de que investigues, no sabras si una función ya escrita esta en Emacs Lisp o en C.

La forma especial defun

En Lisp, un símbolo como mark-whole-buffer tiene un código adjunto que le dice al ordenador que hacer cuando se invoca la función. Este código se denomina la definición de función y se crea evaluando una expresión Lisp que comienza con el símbolo defun (que es una abreviatura para define function (definir función)). Debido a que defun no evalúa sus argumentos de la manera habitual, se le llama una forma especial.

En las siguientes secciones, veremos algunas definiciones de función presentes dentro del código fuente de Emacs, como mark-whole-buffer. En esta sección, describiremos una definición de función sencilla para que puedas ver su aspecto. Esta definición de función usa aritmética porque es un ejemplo simple. A algunas personas les disgustan los ejemplos que usan aritmética; sin embargo, si eres una persona asi, no te desesperes. Casi ningun codigo a estudiar en el resto de esta introducción implica aritmética o matemáticas. Los ejemplos involucran principalmente texto de una forma u otra.

Una definición de función tiene hasta cinco partes despues de la palabra defun:

  1. El nombre del símbolo al se debe vincular la definición de función.

  2. Una lista de los argumentos que se pasan a la función. Si no hay argumentos que pasar a la función, tendremos una lista vacía, ().

  3. Documentación que describe la función. (Técnicamente opcional, pero muy recomendable.)

  4. Opcionalmente, una expresión para hacer que la función sea interactiva, de manera que se puede utilizarla presionando M-x seguido del nombre de la función; o pulsando una tecla o atajo de teclado.

  5. El código que indica a el ordenador qué hacer: el cuerpo de la definición de función.

Es útil pensar que las cinco partes de una definición de función estan organizadas como en una plantilla, con ranuras para cada parte:

(defun nombre-de-función (argumentos)
  "documentación-opcional…"
  (interactive informacion-de-paso-de-argumentos)     ; opcional
  cuerpo)

A modo de ejemplo, aquí está el código de una función que multiplica su argumento por 7. (Este ejemplo no es interactivo. Consulta la Sección Crear una Función Interactiva, para obtener esa información.)

(defun multiplicar-por-siete (numero)
  "Multiplica NUMERO por siete."
  (* 7 numero))

Esta definición comienza con un paréntesis y el símbolo defun seguido por el nombre de la función.

El nombre de la función es seguido por una lista que contiene los argumentos que seran pasados a la función. Esta lista se llama lista de argumentos. En este ejemplo, la lista solo tiene un elemento, el símbolo numero. Cuando se utiliza la función, el símbolo sera asociado al valor que se utiliza como argumento de la función.

En lugar de elegir la palabra numero para el nombre del argumento, podría haber escogido cualquier otro nombre. Por ejemplo, la palabra multiplicando. Escogi la palabra ‘numero’ porque indica qué tipo de valor se pretende para este espacio; pero podría haber elegido ‘multiplicando’ para indicar el papel que jugara el valor en el funcionamiento de la función. Podría haberlo llamado foogle, pero habría sido una mala elección, ya que no comunicaria a los humanos qué significa. La elección del nombre es responsabilidad del programador y debe elegirse para hacer que el significado de la función quede claro.

De hecho, puedes elegir cualquier nombre que desees para un símbolo en una lista de argumentos, incluso el nombre de un símbolo utilizado en alguna otra función: el nombre que utilices en una lista de argumentos es privado para esa definición particular. En esta definición, el nombre hace referenecia a una entidad diferente a cualquiera que utilice el mismo nombre fuera de la definición de función. Supón que tu familia te apodan ‘Enano’; cuando tus familiares digan ‘Enano’, queren hacer referencia a ti. Pero fuera de tu familia, en una película, por ejemplo, el nombre ‘Enano’ se refiere a alguien más. Debido a que el nombre en una lista de argumentos es privado de la definición de la función, se puede cambiar el valor de un símbolo dentro del cuerpo de una función sin cambiar su valor fuera de la función. El efecto es similar al producido por la expresión let. (Consulta la sección let.)

La lista de argumentos es seguida por la cadena de documentación que describe la función. Esto es lo ves cuando presionas C-h f y escribes el nombre de una función. Por cierto, cuando escribas una cadena de documentación como esta, debes confeccionar la primera linea como una frace completa ya que algunos comandos, como apropos, solo imprimen la primer línea de una cadena de documentación de varias líneas. Ademas, no debes indentar la segunda línea de la cadena de documentación, si tienes una, porque se ve extraño al utilizar C-h f (describe-function). La cadena de documentación es opcional, pero es tan útil, que debería incluirse en casi cualquier función que escribas.

La tercer línea del ejemplo consiste en el cuerpo de la definición de función. (La mayoría de las definiciones de funcion, claro esta, son más extensas que esto.) En esta función, el cuerpo es la lista, (* 7 numero), que indica multiplicar el valor de numero por 7. (En Emacs Lisp, * es la función para multiplicar, al igual que + es la función para sumar.

Cuando se utiliza la función multiplicar-por-siete, el argumento numero se evalúa al número que desees usar. He aquí un ejemplo que muestra como utilizar multiplicar-por-siete; ¡pero no trates de evaluar esto primero!.

(multiplicar-por-siete 3)

El símbolo numero, especificado en la definición de función en la siguiente sección, se da o “une a” el valor 3 en el uso real de la función. Observa que aunque numero estaba dentro de paréntesis en la definición de función, el argumento pasado a la función multiplicar-por-siete no está entre paréntesis. Los paréntesis se escriben en la definición de función para que el ordenador pueda averiguar donde termina la lista de argumentos y donde inicia el resto de la definición de función.

Si evalúas este ejemplo, es probable que obtengas un mensaje de error. (¡adelante, intentalo!) Esto se debe a que hemos escrito la definición de función, pero aún no se ha comunicado al ordenador sobre la definición––aun no hemos instalado (o ‘cargado’) la definición de función en Emacs. Instalar una función es el proceso de comunicar al intérprete Lisp la definición de la función. La instalación se describe en la siguiente sección.

Instalar una definición de función

Si estás leyendo esto dentro en Emacs, puedes probar la función multiplicar-por-siete evaluando en primer lugar la definición de función y luego evaluando (multiplicar-por-siete 3). Hay una copia de la definición a continuación. Coloca el cursor después del último paréntesis de la definición de función y presiona C-x C-e. Al hacer esto, multiplicar-por-siete aparecerá en el área de eco. (Esto significa que cuando se evalua una definición de función, el valor devuelto es el nombre de la función definida.) Al mismo tiempo, esta acción instala la definición de función.

(defun multiplicar-por-siete (numero)
  "Multiplica NUMERO por siete."
  (* 7 numero))

Al evaluar este defun, estas instanlado multiplicar-por-siete en Emacs. La función ahora es una parte de Emacs tanto como forward-word o cualquier otra función de edición que utilices. (multiplicar-por-siete permanecera instalado hasta que salgas de Emacs. Para volver a cargar el código automáticamente cada vez que inicie Emacs, revisa la sección Instalar Código Permanentemente.)

Puedes ver el efecto de instalar multiplicar-por-siete evaluando el siguiente ejemplo. Coloca el cursor después de la siguiente expresión y presiona C-x C-e. El número 21 aparacerá en el área de eco.

(multiplicar-por-siete 3)

Si lo deseas, puedes leer la documentación de la función presiona C-h f (describe-function) y luego escribe el nombre de la función, multiplicar-por-siete. Al hacer esto, aparecerá una ventana *Help* en tu pantalla diciendo:

multiplicar-por-siete is a Lisp function.
(multiplicar-por-siete NUMERO)

Multiplica NUMERO por siete.

(Para volver a una sola ventana en tu pantalla, presiona C-x 1.)

Cambiar una definición de función

Si quieres modificar el código de multiplicar-por-siete, simplemente reescribelo. Para instalar la nueva versión en lugar de la anterior, evalúa nuevamente la definición de la función. Esta, es la forma de modificar el código en Emacs. Es muy sencillo,

A modo de ejemplo, puedes cambiar la función multiplicar-por-siete para sumar el número a sí mismo siete veces en lugar de multiplicar el número por siete. Esto produce el mismo resultado, pero por una ruta diferente. Al mismo tiempo, añadiremos un comentario al codigo; un comentario es texto que el intérprete Lisp ignora, pero un lector humano puede encontrar útil o esclarecedor. El comentario es, que esta es la “segunda versión”.

(defun multiplicar-por-siete (numero)       ; Segunda versión.
  "Multiplica NUMERO por siete."
  (+ numero numero numero numero numero numero numero))

El comentario sigue a un punto y coma, ‘;’. En Lisp cualquier cosa en una línea despues de un punto y coma es un comentario. El fin de la línea es el fin del comentario. Para extender un comentario por dos o más líneas, inicia cada línea con un punto y coma.

Consulta las Secciónes Empieza por un fichero .emacs y Comentarios en El Manual de Referencia de GNU Emacs Lisp, para más informacion sobre los comentarios.

Puedes instalar esta versión de multiplicar-por-siete evaluándo esta del mismo modo en que evaluaste la primer función: coloca el cursor después del último paréntesis y presiona C-x C-e.

En resumen, asi es como puedes escribir código en Emacs Lisp: escribes una función; la instalas; la pruebas; luego haces correcciones o mejoras y la vuelves a instalar.

Crear una función interactiva

Puedes hacer a una función interactiva colocando una lista que inicia con la forma especial interactive inmediatamente después de la documentación. Un usuario puede invocar una función interactiva con M-x seguido del nombre de la función; o con un atajo de teclado ligado a esta, por ejemplo, presionando C-n para next-line o C-x h para mark-whole-buffer.

Cusionamente, cuando se llama a una función interactiva interactivamente, el valor devuelto no se muestra automáticamente en el área de eco. Esto se debe a que a menudo se llama a una función interactiva por sus efectos secundarios, como avanzar hacia adelante una palabra o una línea, y no por el valor devuelto. Si el valor devuelto se mostrara en el área de eco cada vez que presionas una tecla, seria muy molesto.

Tanto el uso de la forma especial interactive y la forma de mostrar un valor en el área de eco se pueden ilustrar creando una versión interactiva de multiplicar-por-siete.

Aquí está el código:

(defun multiplicar-por-siete (numero)       ; Versión Interactiva.
  "Multiplica NUMERO por siete."
  (interactive "p")
  (message "El resultado es %d" (* 7 numero)))

Para instalar este código, coloca el cursor después del ultimo parentesis y presiona C-x C-e. El nombre de la función aparecerá en el área de eco. A continuacion, puedes utilizar este código al presionar C-u segudo de un número, luego M-x multiplicar-por-siete y finalmente RET. En el área de eco aparecerá la frase ‘El resultado es …’ seguida por el producto.

Hablando en terminos mas generales, puedes invocar una función como ésta de dos maneras:

  1. Escribiendo un argumento prefijo que contenga el número a pasar, y luego presionar M-x y el nombre de la función, como ocurre con C-u 3 M-x forward-sentence; o,

  2. Pulsando cualquier tecla o atajo de teclado ligado a la función, como ocurre con C-u 3 M-e.

Ambos ejemplos operan de forma identica moviendo el punto hacia adelante tres oraciones. (Ya que multiply-by-seven no esta ligado a un atajo, no se puede utilizar como ejemplo.)

(Consulta la Seccion Algunos Atajos de teclado, para aprender como vincular un comando a una atajo.)

Un argumento prefijo se pasa a una función interactiva pulsando la tecla META seguida de un número, por ejemplo, M-3 M-e, o con C-u y luego un número, por ejemplo, C-u 3 M-e (si presionas C-u sin ningun número, el valor por defecto es 4).

multiplicar-por-siete interactivo

Veamos es uso de la forma especial interactive y luego la función message en la versión interactiva de multiplicar-por-siete. Recordaras que la definición de función tiene el siguiente aspecto:

(defun multiplicar-por-siete (numero)       ; Versión Interactiva.
  "Multiplica NUMERO por siete."
  (interactive "p")
  (message "El resultado es %d" (* 7 numero)))

En esta función, la expresión, (interactive "p"), es una lista de dos elementos. La "p" le indica a Emacs que debe pasar el argumento prefijo a la función y utilizar este valor para el argumento de la función.

El argumento debe ser un número. Esto significa que el símbolo numero estara asociado a un número en la línea:

(message "El resultado es %d" (* 7 numero))

Por ejemplo, si tu argumento prefijo es 5, el intérprete Lisp evaluará la línea como si fuera:

(message "El resultado es %d" (* 7 5))

(Si estás leyendo esto en GNU Emacs, puedes evaluar esta expresión por ti mismo.) Primero, el intérprete evaluará la lista interna, que es (* 7 5). Esto devuelve un valor de 35. A continuación, se evaluará la lista externa, pasando los valores del segundo y posteriores elementos de la lista a la función message.

Como hemos visto, message es una función Emacs Lisp especialmente diseñada para enviar un mensaje de una línea al usuario. (Consulta la Seccion La función message.) En resumen, la función message imprime su primer argumento en el área de eco tal cual, excepto para las ocurrencias de ‘%d’, o ‘%s’ (y varias otras secuencias de control que no hemos mencionado). Cuando aparece una secuencia de control, la función busca el segundo argumento o los siguientes e imprime el valor del argumento en la ubicación donde se encuartra la secuencia de control.

En la función interactiva multiplicar-por-siete, la cadena de control es ‘%d’, que requiere un número, y el valor devuelto por la evaluación de (* 7 5) es el número 35. Por consiguiente, el número 35 se imprime en lugar de ‘%d’ y el mensaje es ‘El resultado es 35’.

(Observa que cuando llamas a la función multiplicar-por-siete, el mensaje se imprime sin comillas, pero cuando llamas a message, el texto se imprime entre comillas. Esto se debe a que el valor devuelto por message aparece en el área de eco cuando se evalúa una expresión cuyo primer elemento es message; pero si se integra en una función, message imprime el texto como un efecto secundario sin las comillas.)

Diferentes opciones para interactive

En el ejemplo, multiplicar-por-siete utilizo "p" como el argumento para interactive. Este argumento le indica a Emacs que interpretara cualquer cosa que escribas despues de C-u o del comando META como un numero que sera el argumento a pasar a la funcion. Emacs tiene más de veinte caracteres predefinidos para usar con interactive. En casi todos los casos, una de estas opciones te permitira pasar la información correcta de forma interactiva a una función. (Consulta la Seccion Codigo de Caracteres para interactive en El Manual de Referencia de GNU Emacs Lisp).

Considera la función zap-to-char. Su expresión interactiva es

(interactive "p\ncZap to char: ")

La primer parte del argumento de interactive es ‘p’, con el que ya estás familiarizado. Este argumento le dice a Emacs que interprete un ‘prefijo’, como un número que se pasa a la función. Puedes especificar un prefijo presionando C-u seguido de un número o igualmente con META seguido por un número. El prefijo es un número de caracteres especifico. De este modo, si el prefijo es tres y el caracter especificado es ‘x’, entonces se borrará todo el texto hasta e incluido el tercer ‘x’ siguiente. Si no se establece un prefijo, entonces borras todo el texto hasta e incluido el carácter especificado, pero no más.

La ‘c’ le dice a la función el nombre del carácter que va a eliminar.

Más formalmente, una función con dos o más argumentos puede tener información que pasa a cada argumento añadiendo partes a la cadena que sigue a interactive. Al hacer esto, la información se pasa a cada argumento en el mismo orden que se especifica en la lista interactive. En la cadena, cada parte está separada de la siguente parte por un ‘\n’, que significa nueva línea. Por ejemplo, a ‘p’ puede continuar un ‘\n’ y un ‘cZap to char: ’. Esto hace que Emacs pase el valor del argumento prefijo (si lo hay) y el carácter.

En este caso, la definición de función se parece a la siguiente, donde arg y char son los símbolos que interactive vincula al argumento prefijo y el caracter especificado:

(defun nombre-de-funcion (arg char)
  "documentacion…"
  (interactive "p\ncZap to char: ")
  cuerpo-de-funcion)

(El espacio después de los dos puntos en el prompt hace que se vea mejor. Consulta la Seccion La Definición de copy-to-buffer, para ver un ejemplo.)

Cuando una función no toma argumentos, interactive no requiere ninguno. Una función asi contiene unicamente la expresión (interactive). La función mark-whole-buffer es asi.

Alternativamente, si los códigos de letras especiales no son adecuados para tu aplicación, puedes pasar tus propios argumentos a interactive como una lista.

Consulta la Seccion La Definición de append-to-buffer, para ver un ejemplo. Consulta la Sección Usando interactive en El Manual de GNU Emacs Lisp, para una explicación más completa sobre esta técnica.

Instalar Código Permanentemente

Cuando instalas una definición de función evaluandola, permanecera instalada hasta que salgas de Emacs. La proxima vez que inicies una nueva sesión de Emacs, la función no sera instalada a menos que evalúes de nuevo la definición.

En algún momento, podrias necesitar que el código se instale automáticamente cada vez que inicies una nueva sesión de Emacs. Hay varias formas de hacer esto:

Finalmente, si tienes código que cualquier usuario de Emacs pueda querer, puedes publicarlo en una red de computadores o enviar una copia a la Free Software Foundation. (Al hacer esto, por favor licencia el código y su documentación bajo una licencia que permita a otras personas ejecutar, copiar, estudiar, modificar, y redistribuir el código y te proteja de quien tome tu trabajo.) Si envias una copia de tu código a la Free Software Foundation, y te proteges apropiadamente a ti mismo y a los demas, puede ser incluido en la siguiente version de Emacs. En gran parte, esta es la forma como ha crecido Emacs a través de los años, gracias a las de donaciones.

let

La expresión let es una forma especial de Lisp que tendras que utilizar en la mayoría de las definiciones de función.

let se utiliza para unir o enlazar un símbolo a un valor de tal manera que el intérprete no confunda la variable con otra variable del mismo nombre que no forme parte de la función.

Para entender por qué la forma especial let es necesaria, considera la situación donde eres dueño de una casa que generalmente vinculas con ‘la casa’, como en la frase, “La casa necesita pintura”. Si visitas a un amigo y tu anfitrion hace referencia a ‘la casa’, es probable que se refiera a su casa, no a la tuya, es decir, a una casa diferente.

Si tu amigo se refiere a su casa y tu crees que el se esta refiriendo a tu casa, puedes encontrarte en una situacion confusa. Lo mismo podría suceder en Lisp si una variable que se usa dentro de una función tiene el mismo nombre que una variable que se utiliza dentro de otra función, y no se pretende que ambas se refieran al mismo valor. La forma especial let permite evitar este tipo de confusión.

La forma especial let evita confusiones. let crea un nombre para una variable local que eclipsa cualquier uso del mismo nombre fuera de la expresión let. Esto es como entender que cuando tu anfitrion haga referencia a ‘la casa’, el quiere referirse a su casa, no la tuya. (Los símbolos usados en las listas de argumentos funcionan de la misma manera. Consulta la Sección La Forma Especial defun.)

Las variable locales creadas por una expresión let retienen su valor solo dentro de la expresión let (y dentro de las expresiones llamadas dentro de la expresión let); las variables locales no tiene efecto fuera de la expresión let.

Otra forma de persar sobre let es que es como un setq que es temporal y local. Los valores establecidos por let se deshacen automáticamente cuando let finaliza. La configuración solo afecta a las expresiones que se encuentran dentro de los limites de la expresión let. En la jerga informatica, diríamos que “la union de un simbolo solo es visible en las funciones llamadas en la forma let; en Emacs Lisp, el alcance es dinámico, no léxico.”

let puede crear más de una variable a la vez. Ademas, let da a cada variable que crea un valor inicial, ya sea un valor especificado por tí, o nil. (En la jerga, eso se llama ‘enlazar la variable al valor’.) Después de que let ha creado y enlazado las variables, ejecuta el código en el cuerpo del let y devuelve el valor de la última expresión en el cuerpo, como el valor de toda la expresión let. (‘Ejecutar’ es un término tecnico que significa evaluar una lista; proviene del significando de la palabra ‘llevar a la practica’ (Oxford English Dictionary). Puesto que se evalua una expresión para realizar una acción, ‘ejecutar’ ha evolucionado como un sinónimo para ‘evaluar’.)

Partes de una expresión let

Una expresión let es una lista de tres partes. La primer parte es el símbolo let. La segunda parte es una lista, llamada varlist (lista de variables), cada uno de cuyos elementos es un símbolo por sí mismo o una lista de dos elementos, cuyo primer elemento es un símbolo. La tercer parte de la expresión let es el cuerpo de let. El cuerpo normalmente consiste de una o más listas.

Una plantilla de la expresión let se veria asi:

(let varlist cuerpo)

Los símbolos de la lista de varibles (varlist) son las variables a las que la forma especial let da valores de inicio. A los símbolos por si mismos se les da el valor inicial nil; y cada símbolo que sea el primer elemento de una lista de dos elementos se vincula al valor que se devuelve cuando el interprete Lisp evalúa el segundo elemento.

Por lo tanto, una varlist podria verse asi: (hilo (agujas 3)). En este caso, en una expresión let, Emacs une el símbolo hilo a un valor inicial de nil, y une el símbolo agujas a un valor inicial de 3.

Cuando escribes una expresión let, lo que haces es poner las expresiones apropiadas en los espacios de la plantilla de la expresión let.

Si la varlist está compuesta de listas de 2 elementos, como suele ser el caso, la plantilla de la expresión let se ve asi:

(let ((variable valor)
      (variable valor)
      )
  cuerpo)

Ejemplo de Expresión let

La siguiente expresión crea y da valores iniciales a las dos variables cebra y tigre. El cuerpo de la expresión let es una lista que llama a la función message.

(let ((cebra 'rayas)
      (tigre 'fiero))
  (message "Un tipo de animal tiene %s y otro es %s."
           cebra tigre))

Aquí, la varlist es ((cebra 'rayas) (tigre 'fiero)).

Las dos variables son cebra y tigre. Cada variable es el primer elemento de una lista de dos elementos y cada valor es el segundo elemento de su lista de dos elementos. En la varlist, Emacs une la variable cebra al valor rayas8, y la variable tigre al valor fiero. En este ejemplo, ambos valores son símbolos precedidos por una cita. Los valores podrían muy bien haber sido otra lista o una cadena. El cuerpo de let sigue después de la lista que contiene las variables. En este ejemplo, el cuerpo es una lista que usa la función message para imprimir una cadena en el área de eco.

Puedes evaluar el ejemplo de la forma habitual, colocando el cursor después del último paréntesis y presionando C-x C-e. Al hacer esto, aparecerá lo siguiente en el área de eco:

"Un tipo de animal tiene rayas y otro es fiero."

Como hemos visto anteriormente, la función message imprime su primer argumento, excepto ‘%s’. En este ejemplo, el valor de la variable cebra se imprime en la ubicacion del primer ‘%s’ y el valor de la variable tigre se imprime en la ubicacion del segundo ‘%s’.

Variables sin inicializar en un sentencia let

Si no se unen las variables en una sentencia let con valores de inicio específicos, automáticamente seran enlazados a un valor de inicio nil, como en la siguiente expresión:

(let ((abedul 3)
      pino
      abeto
      (roble 'algunos))
  (message "Aquí están %d variables con %s, %s, y el valor %s."
           abedul pino abeto roble))

Aquí, la varlist es ((abedul 3) pino abeto (roble 'algunos)).

Si evalúas esta expresión de la forma habitual, aparecerá lo siguiente en el área de eco:

"Aquí están 3 variables con nil, nil, y el valor algunos."

En este ejemplo, Emacs une el símbolo abedul al número 3, une los símbolos pino y abeto a nil, y el símbolo roble al valor algunos.

Observa que en la primer parte de let, las variables pino y abeto son unicamente átomos que no están rodeados por paréntesis; esto es porque estan siendo ligados a nil, la lista vacía. Pero roble su une a algunos por que es parte de la lista (roble 'algunos). De manera similar, abedul se une al número 3 en una lista con este número. (Ya que un número se evalúa a sí mismo, no es necesario citar el numero. Ademas, el número se imprime en el mensaje utilizando un ‘%d’ en lugar de un ‘%s’.) Las cuatro variables como grupo se ponen en una lista para delimitarlas del cuerpo de let.

La forma especial if

Además de let y defun, esta la forma especial condicional if. Esta forma se utiliza para indicar al ordenador que tome decisiones. Puedes escribir definiciones de función sin necesidad de utilizar if, pero se usa con bastante frecuencia, y es lo suficientemente importante como para incluirla aquí. Se utiliza, por ejemplo, en el código de la función beginning-of-buffer.

La idea básica detras de if, es que “si (if) una prueba es verdadera entonces (then) se evalua la expresión”. Si la prueba no es verdadera, la expresión no se evalua. Por ejemplo, podrías tomar una decisión como, “si hace sol y calor, entonces ve a la playa!”

Al escribir una expresión if en Lisp no se utiliza la palabra ‘entonces’; la prueba y la acción son los elementos segundo y tercero de la lista cuyo primer elemento es if. No obstante, la parte de la prueba en una expresión if a menudo se llamada parte-si (if-part) y el segundo argumento a menudo se llamada parte-entonces (then-part).

Ademas, cuando se escribe una expresión if, la prueba-verdadero-o-falso normalmente se escribe en la misma línea que el símbolo if, pero la acción a llevar a cabo si la prueba es verdadera, “parte-entonces”, se escribe en la segunda y suguientes líneas. Esto hace que la expresión if sea mas fácil de leer.

(if prueba-verdadero-o-falso
    accion-a-realizar-si-la-prueba-es-verdadera)

La prueba-verdadero-o-falso es una expresión que evalua el intérprete Lisp.

Aquí hay un ejemplo que puedes evaluar. La prueba consiste en si el número 5 es mayor que el número 4. Puesto que es asi, se imprime el mensaje ‘¡5 es mayor que 4!’.

(if (> 5 4)                           ; parte-si
    (message "¡5 es mayor que 4!"))   ; parte-entonces

(La función > comprueba si su primer argumento es mayor que su segundo argumento y devuelve verdadero si lo es.)

Por supuesto, en un caso real, la prueba en una expresión if no será fija eternamente, como en la expresión (> 5 4). En su lugar, al menos una de las variables utilizadas en la prueba estara unida a un valor de antemano desconocido. (Si el valor se conociera de antemano, ¡no necesitariamos realizar la prueba!)

Por ejemplo, el valor puede estar unido a un argumento de una definición de función. En la siguiente definición de función, el tipo de animal es un valor que se pasa a la función. Si el valor unido a caracteristica es fiero, entonces se imprime el mensaje, ‘¡Es un tigre!’; de otro modo, se devolvera nil.

(defun tipo-de-animal (caracteristica)
  "Imprime el mensaje en el área de eco dependiendo de CARACTERISTICA.
Si la CARACTERISTICA es el símbolo ‘fiero’,
entonces advierte que es un tigre."
  (if (equal caracteristica 'fiero)
      (message "¡Es un tigre!")))

Si estás leyendo esto dentro de GNU Emacs, puedes evaluar la definición de función de la forma habitual para instalarla en Emacs, y luego puedes evaluar las siguientes dos expresiones para ver los resultados:

(tipo-de-animal 'fiero)

(tipo-de-animal 'cebra)

Al evaluar (tipo-de-animal 'fiero), veras el siguiente mensaje impreso en el área de eco: "¡Es un tigre!"; y cuando se evalúa (tipo-de-animal 'cebra) verás nil impreso en el área de eco.

La función tipo-de-animal en detalle

Veamos en detalle la función tipo-de-animal.

La definición de función tipo-de-animal se escribio llenando los espacios de dos plantillas, una para la definición de función como un todo, y otra para una expresión if.

La plantilla para toda función no interactiva es:

(defun nombre-de-funcion (lista-de-argumentos)
  "documentacion…"
  cuerpo)

Las partes de la función que coinciden con esta plantilla tienen el siguiente aspecto:

(defun tipo-de-animal (caracteristica)
  "Imprime el mensaje en el área de eco dependiendo de CARACTERISTICA.
Si la CARACTERISTICA es el símbolo ‘fiero’,
entonces advierte que es un tigre."
  cuerpo: la expresion if)

El nombre de función es tipo-de-animal; se le pasa el valor de un argumento. A la lista de argumentos le sigue una cadena de documentación multilínea. La cadena de documentación se incluiye en el ejemplo porque es un buen hábito escribir un cadena de documentación para cada definición de función. El cuerpo de la definición de función consiste en la expresión if.

La plantilla de una expresión if se ve así:

(if prueba-verdadero-o-falso
    accion-a-realizar-si-la-prueba-devuelve-verdadero)

En la función tipo-de-animal, el código para el if se ve así:

(if (equal caracteristica 'fiero)
    (message "¡Es un tigre!")))

Aquí, está la expresión prueba-verdadero-o-falso

(equal caracteristica 'fiero)

En Lisp, equal es una función que determina si su primer argumento es igual al segundo. El segundo argumento es el símbolo citado 'fiero y el primer argumento es el valor del símbolo característica––en otras palabras, el argumento pasado a esta función.

En la primer prueba de tipo-de-animal, el argumento fiero se pasa a tipo-de-animal. Ya que fiero es igual a fiero, la expresión, (equal caracteristica 'fiero), devuelve el valor verdadero. Cuando esto sucede, if evalúa el segundo argumento o la parte-entonces del if: (message "¡Es un tigre!").

Por otro lado, en la segunda prueba de tipo-de-animal, se pasa el argumento cebra a tipo-de-animal. cebra no es igual a fiero, por lo que la parte-entonces no se evalua y la expresión if devuelve nil).

Expresiones if–then–else

Una expresión if puede tener un tercer argumento opcional, llamado parte-de-otro-modo (else-part), en caso de que la prueba-verdadero-o-falso devuelva falso. Cuando esto sucede, el segundo argumento o parte-entonces de la expresión if global no se evalúa, pero el tercer argumento o parte-de-otro-modo se evalúa. Podrías pensar en esto como la alternativa del día nublado para la decisión “si es cálido y soleado, entonces ir a la playa!, de otro modo leer un libro!”

La palabra “else” (de otro modo) no se escribe en el código Lisp; la parte-de-otro-modo de una expresión if viene después de la parte-entonces. En Lisp, la parte-de-otro-modo suele escribirse al inicio de una nueva linea y tiene menos indentacion que la parte-entonces:

(if prueba-verdadero-o-falso
    accion-a-realizar-si-la-prueba-devuelve-verdadero
  accion-a-realizar-si-la-prueba-devuelve-falso)

Por ejemplo, evaluar la siguiente expresión if imprime el mensaje ‘¡4 no es mayor que 5!’:

(if (> 4 5)                                   ; parte-if
    (message "¡4 falsamente es mayor que 5!") ; parte-then
  (message "¡4 no es mayor que 5!"))          ; parte-else

Observa que los diferentes niveles de indentación hacen fácil distinguir la parte-entonces (por brevedad, y mezclando ingles con español, apartir de ahora (parte-then)) de la parte-de-otro-modo (por brevedad, y mezclando ingles con español, apartir de ahora (parte-else)). (GNU Emacs tiene varios comandos que indentan expresiones if automáticamente. Consulta la Seccion GNU Emacs te ayuda a escribir listas.)

Podemos ampliar la función tipo-de-animal para incluir una parte-else simplemente incorporando una parte adicional a la expresión if.

Puedes ver las consecuencias de hacer esto si evalúas la siguiente versión de la función tipo-de-animal y luego evaluas las dos expresiones siguientes que pasan argumentos diferentes a la función.

(defun tipo-de-animal (caracteristica)
  "Imprime un mensaje en el área de eco dependiendo de la CARACTERISTICA.
Si la CARACTERISTICA es el símbolo ‘fiero’,
entonces advierte que es un tigre.
de otro modo dice que no es fiero"
  (if (equal caracteristica 'fiero)
      (message "¡Es un tigre!")
    (message "!No es feroz!")))

(tipo-de-animal 'fiero)

(tipo-de-animal 'cebra)

Cuando evalues (tipo-de-animal 'fiero), verás el siguiente mensaje en el área de eco: "¡Es un tigre!"; cuando evalues (tipo-de-animal 'cebra), verás "¡No es feroz!".

(Por supuesto, si característica fuera muy-feroz, se imprimiria el mensaje "¡No es feroz!"; ¡y sería un error! Cuando escribas código, debes tener en cuenta la posibilidad de que algun argumento como este sea probado por if y acorde a ello escribir tu programa.

Verdad y Falsedad en Emacs Lisp

Hay un aspecto importante para probar la verdad en una expresión if. Hasta ahora, hemos hablado de ‘verdadero’ y ‘falso’ como valores de predicados como si fueran nuevos tipos de objetos Emacs Lisp. De hecho, solo nuestro viejo amigo nil es ‘falso’. Cualquier otra cosa––cualquiera en absoluto––es ‘verdadero’.

La expresión que prueba si algo es verdadero se interpreta como true (verdadero) si el resultado de la evaluacion es no nil. En otras palabras, el resultado de la prueba se considera verdadero si el valor devuelto es un número como 47, una cadena como "hola", o un símbolo (distinto de nil) como flores, o una lista (siempre y cuando no este vacía) o incluso ¡un búfer!

Antes de ilustrar una prueba para verdadero, necesitamos una explicación de nil.

En Emacs Lisp, el símbolo nil tiene dos significados. En primer lugar, significa que la lista está vacía. En segundo lugar, significa falso y es el valor devuelto cuando una prueba-verdadero-o-falso obtiene falso. nil puede escribirse como una lista vacia, (), o como nil. En lo referente al interprete Lisp () y nil son lo mismo. Los humanos, sin embargo, tienden a usar nil para falso y () para lista vacía.

En Emacs Lisp, cualquier valor que no sea nil––si no es una lista vacía––se considera verdadero. Esto significa que si una evaluación devuelve algo que no es una lista vacía, una expresión if probara ser verdadera. Por ejemplo, si un número se coloca en el lugar de la prueba, se evaluara y devolverá su valor, ya que eso es lo que hacen los números cuando se evalúan. En esta condicion, la expresión if pasara su prueba como verdadero. La expresión pasara su prueba como falsa solo cuando nil, o una lista vacía, son devueltas al evaluar la expresión.

Puedes ver esto evaluando las dos expresiones en los ejemplos siguientes.

En el primer ejemplo, el número 4 se evalua como la prueba en la expresión if y se devuelve a si mismo; en consecuencia, se evalua y devuelve la parte-then de la expresión: aparece ‘verdadero’ en el área de eco. En el segundo ejemplo, nil indica falso; en consecuencia, se evalua y devuelve la parte-else de la expresión: aparece ‘falso’ en el área de eco.

(if 4
    'verdadero
  'falso)

(if nil
    'verdadero
  'falso)

Por cierto, si algún otro valor de utilidad no está disponible para una prueba que devuelve verdadero, entonces el intérprete Lisp devolvera el símbolo t para verdadero. Por ejemplo, la expresión (> 5 4) devuelve t cuando se evalúa, como puedes comprobar si evaluas:

(> 5 4)

Por otra parte, esta función devuelve nil si la prueba es falsa.

(> 4 5)

save-excursion

La función save-excursion es la cuarta y última forma especial que discutiremos en este capítulo.

En los programas Emacs Lisp utilizados para la edición, la función save-excursion es muy común. Guarda la posición del punto y marca, ejecuta el cuerpo de la función, y luego restaura el punto y marca a sus posiciones previas si sus posiciones fueron cambiadas. Su proposito principal es impedir que el usuario se sorprenda y perturbe por el movimiento inesperado del punto o marca.

Sin embargo, antes de hablar de save-excursion, puede ser útil examinar primero que son el punto y la marca en GNU Emacs. Punto es la posición actual del cursor. Donde sea que se encuentre el cursor, estara el punto. De forma más precisa, en los terminales donde el cursor aparece sobre un carácter, el punto se encuentra inmediatamente antes del carácter. En Emacs Lisp, el punto es un numero entero. El primer carácter en un búfer es el número uno, el segundo es el número dos, y así sucesivamente. La función point devuelve la posición actual del cursor como un número. Cada búfer tiene su propio valor para el punto.

La marca es otra posición en el búfer; su valor se puede establecer con un comando como C-SPC (set-mark-command). Si se ha establecido una marca, puedes utilizar el comando C-x C-x (exchange-point-and-mark) para hacer que el cursor salte a la marca y establecer la marca como la posición anterior del punto. Además, si se establece otra marca, la posición de la marca anterior se guarda en el anillo de marcas. Muchas posiciones de marca se pueden guardar de esta manera. Se puede mover el cursor a una marca guardada presionando C-u C-SPC una o más veces.

La parte del búfer entre el punto y la marca se denomina la región. Numerosos comandos trabajan en la región, incluyendo center-region, count-lines-region, kill-region y print-region.

La forma especial save-excursion guarda las ubicaciones del punto y la marca y restaura estas posiciones después de que el interprete Lisp evalua el código dentro del cuerpo de la forma especial. De este modo, si el punto se encontraba al inicio de un texto y algún código moviera el punto al final del búfer, save-excursion volvera a poner el punto donde estaba antes, luego que las expresiones en el cuerpo de la funcion fueran evaluadas.

En Emacs, una función se mueve con frecuencia de punto a otro como parte de su funcionamiento interno, aunque el usuario no lo espere. Por ejemplo, count-lines-region mueve el punto. Para evitar que el usuario sea molestado por saltos inesperados y (desde el punto de vista del usuario) innecesarios, save-excursion se utiliza a menudo para conservar el punto y la marca en la posición esperada por el usuario. El uso de save-excursion es bueno para mantener el orden.

Para asegurarse que la casa se mantiene limpia, save-excursion restaura los valores del punto y la marca incluso si algo va mal con el código en su cuerpo (o, para ser más preciso y usar el lenguaje tecnico apropiado, “en caso de salida anormal”). Esta caracteristica es muy útil.

Además de registrar los valores del punto y marca, save-excursion mantiene un registro del buffer actual, y tambien lo restaura. Esto significa que puedes escribir código que cambie de bufer y save-excursion lo devuelva al bufer original. Asi es como se utiliza save-excursion en append-to-buffer. (Consulta la Seccion La Definición de append-to-buffer.)

Plantilla para una Expresión save-excursion

La plantilla de código usando save-excursion es simple:

(save-excursion
  cuerpo)

El cuerpo de la función es una o más expresiones que serán evaluadas de forma secuencial por el intérprete Lisp. Si hay más de una expresión en el cuerpo, el valor de la última será devuelto como el valor de la función save-excursion. Las otras expresiones en el cuerpo solo se evaluan por sus efectos secundarios; y save-excursion en misma solo se utiliza por su efecto secundario (que es restaurar las posiciones del punto y la marca).

La siguiente plantilla explica save-excursion, con más detalle:

(save-excursion
  primera-expresion-en-el-cuerpo
  segunda-expresion-en-el-cuerpo
  tercera-expresion-en-el-cuerpo
   
  ultima-expresion-en-el-cuerpo)

Una expresión, por supuesto, puede ser un símbolo por sí mismo o una lista.

En el código Emacs Lisp, una expresión save-excursion a menudo ocurre dentro del cuerpo de una expresión let. Tiene un aspecto algo asi:

(let varlist
  (save-excursion
    cuerpo))

Repaso

En los últimos capítulos se han introducido un buen número de funciones y formas especiales. A continuacion se describen brevemente, junto con algunas funciones similares que aun no se han mencionado.

eval-last-sexp

Evalúa la última expresión simbólica antes de la posición actual del punto. El valor se imprime en el área de eco a menos que la función se invoque con un argumento; en este caso, la salida se imprime en el búfer actual. Este comando normalmente esta ligado a C-x C-e.

defun

Define una función. Esta forma especial consta de hasta cinco partes: el nombre, una plantilla para los argumentos que se pasan a la función, la documentacion, una declaración interactiva opcional, y el cuerpo de la definición.

Por ejemplo, en las primeras versiones de Emacs, la definición de la función back-to-indentation era la siguiente. (Es ligeramente más compleja ahora que busca el primer caracter que no es espacio en lugar del primer caracter visible.)

(defun back-to-indentation ()
  "Mueve el punto al primer caracter visible de la linea."
  (interactive)
  (beginning-of-line 1)
  (skip-chars-forward " \t"))
interactive

Anuncia al intérprete que la función se puede usar de forma interactiva. Esta forma especial puede ir seguida de una cadena con una o más partes que pasan la información a los argumentos de la función, de forma secuencial. Estas partes pueden también decir al intérprete que solicite información. Las partes de la cadena estan separadas por saltos de linea, ‘\n’.

Los codigos de caractere comúnes son:

  • b: El nombre de un búfer existente.

  • f: El nombre de un fichero existente

  • p: El argumento prefijo numérico. (Observa que esta ‘p’ es minúscula.)

  • r: El punto y la marca, como dos argumentos numéricos, el más pequeño primero. Este es el unico codigo de una letra que especifica dos argumentos sucesivos en lugar de uno.

Consulta la Seccion Codigo de caracteres para ‘interactive’ en El Manual de Referencia de GNU Emacs Lisp, para obtener una lista completa de los codigos de caracteres.

let

Declara una lista de variables para utilizarla dentro del cuerpo de let y darle a cada variable un valor inicial, ya sea nil o un valor específicado; luego evalua el resto de las expresiones en el cuerpo del let y devuelve el valor de la última expresion. Dentro del cuerpo del let, el intérprete Lisp no ve los valores de las variables con el mismo nombre declaradas fuera de let.

Por ejemplo,

(let ((foo (buffer-name))
      (bar (buffer-size)))
  (message "Este buffer es %s y tiene %d caracteres."
           foo bar))
save-excursion

Registra los valores de punto, marca y del búfer actual antes de evaluar el cuerpo de esta forma especial. Tras evaluar su cuerpo, restaura los valores de punto, marca y búfer.

Por ejemplo,

(message "Estamos %d caracteres dentro de este buffer."
         (- (point)
            (save-excursion
              (goto-char (point-min))
              (point))))
if

Evalúa el primer argumento de la función; si es verdadero, evalúa el segundo argumento; de otra forma evalúa el tercer argumento, si lo hay.

La forma especial if se llamada condicional. Hay otros condicionales en Emacs Lisp, pero if es quizás el más utilizado.

Por ejemplo,

(if (= 24 emacs-major-version)
    (message "Esta es la version 24 de Emacs")
  (message "Esta no es la version 24 de Emacs"))
<, >, <=, >=

La función < prueba si su primer argumento es menor que el segundo. >, prueba si el primer argumento es mayor que el segundo. Del mismo modo, <= prueba si el primer argumento es menor o igual al segundo y >= si el primer argumento es mayor o igual al segundo. En todos los casos, ambos argumentos deben ser números o marcadores (los marcadores indican posiciones en búfers).

=

La función = prueba si dos argumentos, ambos números o marcadores, son iguales.

equal, eq

Prueban si dos objetos son el mismo. equal utiliza un signicado de la palabra ‘mismo’ y eq utiliza otro: equal devuelve verdadero si los dos objetos tienen una estructura y contenidos similares, por ejemplo, dos copias del mismo libro. Por otro lado, eq, devuelve verdadero si ambos argumentos son realmente el mismo objeto.

string<, string-lessp, string=, string-equal

La función string-lessp prueba si su primer argumento es menor que el segundo. Un nombre alternativo mas corto para la misma función (un defalias) es string<.

Los argumentos para string-lessp deben ser cadenas o símbolos; la ordenación es lexicográfica, por lo que mayusculas o minusculas son significativas. Los nombres impresos de símbolos se utilizan en lugar de los símbolos mismos.

Una cadena vacía, ‘""’, una cadena sin caracteres, es más pequeña que cualquier cadena de caracteres.

string-equal proporciona la prueba correpondiente para la igualdad. Su nombre alternativo es string=. No hay funciones de prueba de cadenas que correspondan a >, >= o <=.

message

Imprime un mensaje en el área de eco. El primer argumento es una cadena que puede contener, ‘%s’, ‘%d’, o ‘%c’ para imprimir el valor de los argumentos que siguen a la cadena. El argumento utilizado por ‘%s’ debe ser una cadena o un simbolo, el argumeto utilizado por ‘%d’ debe ser un número. El argumento usado por ‘%c’ debe ser un codigo númerico ASCII; se imprimira como el caracter con ese código ASCII. (No se han mencionado otras secuencias porcentuales %-).

setq, set

La función setq asigna al valor de su primer argumento el valor del segundo argumento. El primer argumento se cita automáticamente por setq. Hace lo mismo para los pares sucesisvos de argumentos. La función, set, toma solo dos argumentos y evalúa ambos antes de asignar el valor devuelto por su primer argumento al valor devuelto por el segundo argumento.

buffer-name

Sin un argumento, devuelve el nombre del búfer, como una cadena.

buffer-file-name

Sin un argumento, devuelve el nombre del fichero del búfer que se esta visitando.

current-buffer

Devuelve el búfer en el que Emacs se encuentra activo; Puede que no sea el búfer que es visible en la pantalla.

other-buffer

Devuelve el ultimo búfer seleccionado mas recientemente (distinto del buffer pasado a other-buffer como un argumento y distinto del búfer actual).

switch-to-buffer

Selecciona un búfer de Emacs para activarlo y mostralo en la ventana actual para que los usuarios puedan verlo. Normalmente ligado a C-x b.

set-buffer

Cambiar la atención de Emacs a un búfer en el que se ejecutaran los programas. No altera lo que muestra la ventana.

buffer-size

Devuelve el número de caracteres en el búfer actual.

point

Devuelve el valor de la posicion actual del cursor, como un entero contando el número de caracteres desde el inicio del búfer.

point-min

Devuelve el valor mínimo admisible del punto en el búfer actual. Esto es 1, a menos que narrowing esté activo

point-max

Devuelve el valor máximo admisible del punto en el búfer actual. Este es el fin del búfer, a menos que narrowing este activo.

Ejercicios

Algunas funciones relacionadas al búfer

En este capítulo estudiamos en detalle varias de las funciones usadas en GNU Emacs. Esto se denomina “walk-through” (recorrido) (visita o demostracion de un area o tarea). Estas funciones se utilizan como ejemplos de código Lisp, pero no son ejemplos imaginarios; con excepción del primero, con la definición de función simplificada, estas funciones muestran el código real usado en GNU Emacs. Se puede aprender mucho de estas definiciones. Las funciones aquí descritas están relacionadas a los búfers. Mas tarde, estudiaremos otras funciones.

Buscar Más Información

En este recorrido, describire cada nueva función a medida que lleguemos a ella, a veces en detalle y a veces brevemente. Si estás interesado, puedes obtener la documentación completa de cualquier función de Emacs Lisp en cualquier momento presionando C-h f y luego ingresando el nombre de la función (y luego RET). Del mismo modo, se puede obtener la documentación completa de una variable con C-h v, luego el nombre de la variable (y despues RET).

Ademas, describe-function te dira la ubicacion de la definición de la función.

Coloca el punto sobre el nombre del fichero que contiene la función y presiona la tecla RET. En este caso, RET significa push-button en lugar de ‘return’ o ‘enter’. Emacs te llevara directamente a la definición de la función.

De manera más general, si quieres ver una función en su fichero fuente original, puedes utilizar la función find-tag para saltar a la misma. find-tag trabaja con una amplia variedad de lenguajes, no solo Lisp, y C, y tambien funciona con texto en general. Por ejemplo, find-tag saltará a los distintos nodos del codigo fuente de este documento. La función find-tag depende de ‘tablas de etiquetas’ que registran las ubicaciones de las funciones, variables, y otros elementos a los que find-tag salta.

Para usar el comando find-tag, presiona M-. (es decir, presiona la tecla punto mientras presionas la tecla META, o presiona ESC y luego la tecla punto), a continuacion, en el prompt, escribe el nombre de la función para ver su código fuente, por ejemplo mark-whole-buffer, y luego pulsa RET. Emacs cambiará de búfer y mostrará el código fuente de la función en la pantalla. Para regresar a tu búfer actual, presiona C-x b RET. (En algunos teclados, la tecla META se etiqueta como ALT.)

Dependiendo de la configuracion de los valores iniciales predeterminados de tu copia de Emacs, puede ser necesario especificar la posición de tu ‘tabla de etiquetas’, que es un fichero llamado TAGS. Por ejemplo, si estás interesado en el codigo fuente de Emacs, lo mas probable es que la tabla de etiquetas que deseas, si ya ha sido creada, este en un subdirectorio del directorio /usr/local/share/emacs; de este modo se usaría el comando M-x visit-tags-table y se especificaria una ruta como /usr/local/share/emacs/24.1.1/lisp/TAGS. Si la tabla de etiquetas no ha sido creada, tendrás que crearla por tí mismo. Estara en un fichero como /usr/local/src/emacs/src/TAGS.

Para crear un fichero TAGS en un directorio específico, cambia a ese directorio en Emacs utilizando el comando M-x cd, o lista el directorio con C-x d (dired). A continuacion ejecuta el comando compile, con etags *.el asi:

M-x compile RET etags *.el RET

Para más información, consulta la Seccion Crea tu propio fichero TAGS.

Después de que te familiarices con Emacs Lisp, encontrarás que utilizas con frecuencia find-tag como metodo para navegar por el código fuente; y crearás tus propias tablas TAGS.

Por cierto, los ficheros que contienen código Lisp se denominan convencionalmente librerías. La metáfora se deriva que una librería especializada, como una librería de leyes o una librería de ingeniería, en lugar de una librería general. Cada librería, o fichero, contiene funciones que se relacionan con un tema o actividad particular, por ejemplo abbrev.el para manejar abreviaturas y atajos, y help.el para la ayuda en linea. (Algunas veces varias librerías proporcionan código para una sola actividad, como los distintos ficheros rmail… que proporcionan codigo para leer correo electrónico.) En El Manual de GNU Emacs, verás frases como “El comando C-h p te permite buscar las librerias estándar de Emacs Lisp por palabras clave del tema.”

Una definición simplificada de beginning-of-buffer

El comando beginning-of-buffer es una buena función para empezar, ya que es probable que estes familiarizado con ella y es fácil de entender. Utilizado como un comando interactivo, beginning-of-buffer mueve el cursor al inicio del búfer, dejando la marca en la posición anterior. Esta generalmente ligado a M-<.

En esta sección, discutiremos una versión reducida de la función que muestra como se utiliza con mayor frecuencia. Esta función reducida funciona como esta escrita, pero no contiene código para una opcion compleja. En otra sección, describiremos la función completa. (Consulta la Seccion Definición completa de beginning-of-buffer.

Antes de mirar el código, consideremos lo que debe contener la definición de función: debe incluir una expresión que haga que la función sea interactiva para que puede llamarse tecleando M-x beginning-of-buffer o pulsando algun atajo como M-<; debe incluir código para dejar una marca en la posición original del búfer; y debe incluir código para mover el cursor al inicio del búfer.

Aquí está el texto completo la versión reducida de la función:

(defun beginning-of-buffer-simplificado ()
  "Mueve el punto al inicio del buffer;
deja la marca en la posicion anterior."
  (interactive)
  (push-mark)
  (goto-char (point-min)))

Como todas las definiciones de función, esta definición tiene cinco partes que siguen la forma especial defun:

  1. El nombre: en este ejemplo, beginning-of-buffer-simplificado.

  2. Una lista de argumentos: en este ejemplo, una lista vacía, (),

  3. La cadena de documentación.

  4. La expresión interactiva.

  5. El cuerpo.

En esta definición de función, la lista de argumentos está vacía; esto significa que esta función no requiere ningun argumento. (Cuando veamos la definición de la función completa, veremos que se puede pasar un argumento opcional.)

La expresión interactiva le indica a Emacs que se pretende utilizar la función de forma interactiva. En este ejemplo, interactive no tiene un argumento porque beginning-of-buffer-simplificado no lo requiere.

El cuerpo de la función esta formado por dos líneas:

(push-mark)
(goto-char (point-min))

La primera de estas líneas es la expresión, (push-mark). Cuando el interprete Lisp evalua esta expresión, pone una marca en la posición actual del cursor, donde sea que este. La posición de esta marca se guarda en el anillo de marcas.

La siguiente línea es (goto-char (point-min)). Esta expresión hace saltar el cursor hasta el punto mínimo en el búfer, es decir, al inicio del búfer (o al inicio de la porción accesible del búfer si narrowed esta activo. Consulta la Seccion Reducir y Extender.)

El comando push-mark establece una marca en el sitio donde se encontraba el cursor antes de que la expresión (goto-char (point-min)) lo moviera al principio del bufer. En consecuencia, puedes, si quieres, volver a donde estabas originalmente presionando C-x C-x.

¡Esto es todo lo que hay en la definición de función!

Cuando leas código como este y te encuentres con una función desconocida, como goto-char, puedes averiguar que es lo que hace mediante el comando describe-function. Para usar este comando, presiona C-h f, luego escribe el nombre de la función y presiona RET. El comando describe-function imprimirá la cadena de documentacion de la función en una ventana *Help*. Por ejemplo, la documentación de goto-char es:

Establece el punto a POSICION, un numero o marcador.
El inicio del buffer es la posicion (point-min), el final es (point-max).

El argumento de la función es la posición deseada.

(En el caso de describe-function el prompt te facilita el símbolo adelante o debaje del cursor, lo que te puede evitar escribir el nombre de la funcion al colocar el cursor encima o después de la función y luego presionar C-h f RET.)

La definición de la función end-of-buffer se escribe de la misma forma que la definición beginnig-of-buffer excepto que el cuerpo de la función contiene la expresión (goto-char (point-max)) en lugar de (goto-char (point-min))

La definición de mark-whole-buffer

La función mark-whole-buffer no es mas difícil de entender que la función beginning-of-buffer-simplificado. En este caso, sin embargo, veremos la función completa, no una versión reducida.

La función mark-whole-buffer no se utiliza con tanta frecuencia como la función beginning-of-buffer, pero no es menos útil: marca un búfer completo como una región colocando el punto al inicio y una marca al final del búfer. Generalmente se enlaza a C-x h.

En GNU Emacs 22, el código de la función completa se ve asi:

(defun mark-whole-buffer ()
  "Coloca el punto al inicio y la marca el final del buffer.
Probablemente no deberias utilizar esta funcion en programas Lisp;
Por lo general es un error que una funcion Lisp utilice cualquier subrutina
que utilice o establesca la marca."
  (interactive)
  (push-mark (point))
  (push-mark (point-max) nil t)
  (goto-char (point-min)))

Al igual que todas las demas funciones, la función mark-whole-buffer encaja dentro de la plantilla de una definición de funcion. La plantilla luce asi:

(defun nombre-de-funcion (lista-de-argumentos)
  "documentacion…"
  (expresion-interactiva)
  cuerpo)

Asi es cómo funciona la función: el nombre de la función es mark-whole-buffer; le sigue una lista de argumentos vacía, ‘()’, lo que significa que la función no requiere argumentos. La documentación viene despues.

La siguiente línea es una expresión (interactive) que indica a Emacs que la función se utilizara de forma interactiva. Estos detalles son similares a la función beginning-of-buffer-simplificado descrita en la sección anterior

Cuerpo de mark-whole-buffer

El cuerpo de la función mark-whole-buffer consiste en tres líneas de código:

(push-mark (point))
(push-mark (point-max) nil t)
(goto-char (point-min))

La primera de estas líneas es la expresión, (push-mark (point)).

Esta línea hace exactamente el mismo trabajo que la primera línea del cuerpo de la función beginning-of-buffer-simplificado, en la que solo se escribe (push-mark). En ambos casos, el intérprete Lisp coloca una marca en la posición actual del cursor.

No sé por qué en la expresión mark-whole-buffer se escribe (push-mark (point)) y en la expresión beginning-of-buffer se escribe (push-mark). Quizás quien escribió el código no conocia que los argumentos de push-mark son opcionales y que si no se pasa un argumento a push-mark, la función establece automáticamente la marca en la posicion del punto por defecto. O quizás la expresión fué escrita de manera que fuera paralela a la estructura de la siguiente línea. En cualquier caso, la línea hace que Emacs determine la posición del punto y coloque una marca allí.

En versiones anteriores de GNU Emacs, la siguiente línea de mark-whole-buffer era (push-mark (point-max)). Esta expresión establece una marca en el punto del búfer que tiene el número más alto. Este será el final del búfer (o, si el búfer tiene activo narrowing, el final de la porción accesible del búfer. Consulta la Seccion Reducir y Extender, para más informacion sobre norrowing). Después de colocar esta marca, la marca anterior, la establecida en el punto, ya no esta establecida, pero Emacs recuerda su posición, al igual que todas las otras marcas recientes. Esto significa que, si lo deseas, puedes volver a esta posición presionando C-u C-SPC dos veces.

En GNU Emacs 22, (point-max) es ligeramente más complicado. La línea es

(push-mark (point-max) nil t)

La expresión funciona casi igual que antes. Establece una marca en la posicion con el numero mas alto posible en el búfer. Sin embargo, en esta versión, push-mark tiene dos argumentos adicionales. El segundo argumento de push-mark es nil. Esto le indica a la función que debe mostrar un mensaje que diga ‘Mark set’ cuando se coloca la marca. El tercer argumento es t. Esto le indica a push-mark que active la marca cuando el modo Transient Mark está activo. El modo Transient Mark resalta la región activa. Con frecuencia esta desactivado.

Finalmente, la última línea de la función es (goto-char (point-min))). Se escribe exactamente de la misma forma como se escribio beginning-of-buffer. La expresión mueve el cursor al punto mínimo en el búfer, es decir, al inicio del búferr (o al inicio de la porción accesible del búfer). Como resultado de esto, el punto se coloca al inicio del búfer y la marca se encuentra al final del búfer. Por tanto, la región es todo el búfer.

La definición de append-to-buffer

El comando append-to-buffer es más complejo que el comando mark-whole-buffer. Lo que hace es copiar la región (es decir, la parte del búfer entre el punto y la marca) del buffer actual a un búfer específico.

El comando append-to-buffer utiliza la función insert-buffer-substring para copiar la región. insert-buffer-substring se describe por su nombre: toma una cadena de caracteres de una parte de un búfer, una “subcadena”, y la inserta en otro búfer.

La mayor parte de append-to-buffer tiene que ver con establecer las condiciones para que insert-buffer-substring funcione: el código debe especificar tanto el búfer al que ira el texto, la ventana de la que viene y a la que va, como la región que será copiada.

Aquí está el texto completo de la función:

(defun append-to-buffer (buffer start end)
  "Añade al buffer especificado el texto de la region.
Este se inserta en ese buffer antes de su punto.

Cuando se llama desde un programa, se pasan tres argumentes:
BUFFER (o nombre del buffer), START y END.
START y END especifican la porcion del buffer actual a copiar."
  (interactive
   (list (read-buffer "Agregar al buffer: " (other-buffer
                                            (current-buffer) t))
         (region-beginning) (region-end)))
  (let ((oldbuf (current-buffer)))
    (save-excursion
      (let* ((append-to (get-buffer-create buffer))
             (windows (get-buffer-window-list append-to t t))
             point)
        (set-buffer append-to)
        (setq point (point))
        (barf-if-buffer-read-only)
        (insert-buffer-substring oldbuf start end)
        (dolist (window windows)
          (when (= (window-point window) point)
            (set-window-point window (point))))))))

Se puede entender la función si se mira como una serie de plantillas llenas.

La plantilla exterior es para la definición de función. Esta función, se ve asi (con varios espacios ocupados):

(defun append-to-buffer (buffer start end)
  "documentacion…"
  (interactive )
  cuerpo)

La primer línea de la función incluye su nombre y tres argumentos. Los argumentos son el buffer al cual copiar el texto, start y end que son el inicio y el fin de la región del búfer actual que se va a copiar.

La siguiente parte de la función es la documentación, que es clara y completa. Como es convencional, los tres argumentos se escriben en mayúsculas para que los notes con facilidad. Aun mejor, se describen en el mismo orden que en la lista de argumentos.

Observa que la documentación distingue entre un búfer y su nombre. (La función puede manejar cualquiera.)

La expresión interactiva append-to-buffer

Ya que la función append-to-buffer se puede utilizar de forma interactiva, la función debe tener una expresión interactive. (Para una analisis de interactive, consulta la seccion Crear una función interactiva.) La expresión se lee de la siguiente manera:

(interactive
 (list (read-buffer
        "Agregar al buffer: "
        (other-buffer (current-buffer) t))
       (region-beginning)
       (region-end)))

Esta expresión no tiene letras que representen partes, como se describio anteriormente. En su lugar, inicia una lista con estas partes:

La primer parte de la lista es una expresión para leer el nombre de un búfer y devolverlo como una cadena. Es decir read-buffer. La función requiere un prompt como primer argumento, ‘"Agregar al buffer: "’. El segundo argumento le indica al comando que valor proporcionar si no se especifica nada.

En este caso ese segundo argumento es una expresión que contiene la función other-buffer, una excepción, y una ‘t’, que significa verdadero.

El primer argumento de other-buffer, la excepción, es otra función, current-buffer. Esto no va a ser devuelto. El segundo argumento es el símbolo de verdadero, t. Que le dice a other-buffer que puede mostrar búfers visibles (excepto en este caso, que no mostrará el búfer actual, lo cual tiene sentido).

La expresión se ve asi:

(other-buffer (current-buffer) t)

El segundo y tercer argumento de la expresión list son (region-beginning) y (region-end). Estas dos funciones especifican el inicio y el final del texto a añadir.

Originalmente, el comando utilizaba las letras ‘B’ y ‘r’. Toda la expresión interactive se veia así:

(interactive "BAppend to buffer: \nr")

Pero cuando se hacia esto, el valor por defecto del búfer de cambió era invisible. Esto no era lo que se queria.

(El prompt se separo del segundo argumento con una línea nueva, ‘\n’. Seguido por un ‘r’ que le indica a Emacs unir los dos argumentos que siguen al símbolo buffer en la lista de argumentos de la función (es decir, start y end) a los valores del punto y marca. Este argumento funcionaba bien.)

El cuerpo de append-to-buffer

El cuerpo de la función append-to-buffer inicia con let.

Como hemos visto antes (Seccion let), el propósito de una expresión let es crear y dar valores iniciales a una o más variables que solo se usaran dentro del cuerpo del let. Esto significa que esa variable no se confundira con ninguna variable del mismo nombre fuera de la expresión let.

Podemos ver como la expresión let encaja en la función como un todo mostrando una plantilla de append-to-buffer con la expresión let en el esquema:

(defun append-to-buffer (buffer start end)
  "documentacion…"
  (interactive )
  (let ((variable valor))
        cuerpo)

La expresión let tiene tres elementos:

  1. El símbolo let;

  2. Una varlist que contiene, en este caso, una unica lista de dos elementos, (variable valor);

  3. El cuerpo de la expresión let.

En la función append-to-buffer, la varlist es la siguiente:

(oldbuf (current-buffer))

En esta parte de la expresión let, la unica variable, oldbuf se liga al valor devuelto por la expresión (current-buffer). La variable, oldbuf, se utiliza para guardar un registro del búfer en el que se está trabajando y desde el que se va a copiar.

El elemento o elementos de una varlist estan rodeados por un conjunto de paréntesis por lo que el intérprete Lisp puede distinguir la varlist del cuerpo del let. Como consecuencia, la lista de dos elementos dentro de la varlist está rodeada por un conjunto circunscrito de paréntesis. La línea tiene este aspecto:

(let ((oldbuf (current-buffer)))
   )

Los dos paréntesis antes de oldbuf podrían sorprenderte si no fuera porque el primer paréntesis marca el límite de la varlist y el segundo paréntesis marca el inicio de la lista de dos elementos, (oldbuf (current-buffer)).

save-excursion en append-to-buffer

El cuerpo de la expresión let dentro de append-to-buffer consiste en una expresión save-excursion.

La función save-excursion guarda las posiciones de punto y marca, y restaura estas posiciones después de que las expresiones en el cuerpo de save-excursion completan su ejecución. Además, save-excursion no pierde de vista el búfer original, y lo restaura. Asi es como se utiliza save-excursion en append-to-buffer.

Por cierto, vale la pena señalar que una función Lisp normalmente se formatea de modo que todo lo que esta encerrado en un conjunto de multiples lineas se indente más a la derecha que el primer símbolo. En esta definición de función, let se indenta más que defun, y save-excursion se indenta más que let y asi sucesivamente:

(defun 
  
  
  (let
    (save-excursion
      

Esta convención de formato hace que sea fácil ver que líneas en el cuerpo de save-excursion estan rodeadas por los parentesis asociados con save-excursion, de la misma manera que save-excursion esta rodeada por los paréntesis asociados con let:

(let ((oldbuf (current-buffer)))
  (save-excursion
    
    (set-buffer )
    (insert-buffer-substring oldbuf start end)
    ))

El uso de la función save-excursion puede verse como un proceso de rellenar las ranuras de una plantilla:

(save-excursion
  primer-expresion-en-el-cuerpo
  segunda-expresion-en-el-cuerpo
   
  ultima-expresion-en-el-cuerpo)

En esta función, el cuerpo de save-excursion contiene solo una expresión, la expresión let*. Ya conoces la función let. La función let* es diferente. Posee un ‘*’ en su nombre. Esto le permite a Emacs colocar cada variable de su varlist en secuencia, una después de otra.

Su caracteristica fundamental es que las siguientes variables en la varlist puedan hacer uso de los valores establecidos por Emacs anteriormente en la varlist. Consulta la Seccion La expresión let*.

Vamos a omitir funciones como let* y nos centraremos en dos: la función set-buffer y la función insert-buffer-substring.

En los viejos tiempos, la expresión set-buffer era simplemente:

(set-buffer (get-buffer-create buffer))

pero ahora es

(set-buffer append-to)

append-to esta ligado al (get-buffer-create-buffer) anterior en la expresión let*. Esta union extra no sería necesaria excepto que append-to se utiliza despues en la varlist como un argumento para get-buffer-window-list.

La definición de la función append-to-buffer inserta texto desde el búfer en el que te encuentras actualmente al buffer que se indique. Sucede que insert-buffer-substring copia texto desde otro búfer al búfer actual, justo al reves––es por ello que la definición append-to-buffer inicia con un let que une el símbolo local oldbuf al valor devuelto por current-buffer.

La expresión insert-buffer-substring es la siguiente:

(insert-buffer-substring oldbuf start end)

La función insert-buffer-substring copia una cadena desde el búfer especificado como su primer argumento e inserta la cadena dentro del búfer actual. En este caso, el argumento de insert-buffer-substring es el valor de la variable creada y vinculada por let, es decir, el valor de oldbuf, que era el búfer actual cuando se dio el comando append-to-buffer.

Después de que insert-buffer-substring ha hecho su trabajo, save-excursion restaurará la acción al búfer original y append-to-buffer habrá hecho su trabajo.

Escrito en forma esqueletal, los funcionamientos del cuerpo se ven asi:

(let (unir-oldbuf-al-valor-del-buffer-actual)
  (save-excursion                       ; guarda un registro del buffer.
    cambio-de-buffer
    insertar-subcadena-desde-oldbuf-a-buffer)

  regresar-al-buffer-original-al-terminar
dejar-que-el-siginificado-local-de-oldbuf-desaparacesca-al-terminar

En resumen, append-to-buffer funciona de la siguiente manera: guarda el valor del búfer actual en la variable oldbuf. Obtiene el nuevo búfer (creando uno si es necesario) y cambia la atención de Emacs a este. Usando el valor de oldbuf, inserta la región del texto desde el búfer antiguo dentro del nuevo búfer; y luego usando save-excursion, regresa al búfer original.

Al observar append-to-buffer, se ha explorado una función bastante compleja. Muestra como usar let y save-excursion, y como cambiar y volver desde otro buffer. Muchas definiciones de función usan let, save-excursion, y set-buffer de esta manera.

Repaso

Aquí está un breve resumen de las diferentes funciones descritas en este capítulo.

describe-function, describe-variable

Imprimen la documentación de una función o variable. Convencionalmente unidas a C-h f y C-h v.

find-tag

Encuentra el fichero que contiene el codigo de una función o variable y cambia a dicho buffer, colocando el punto al inicio del elemento. Convencionalmente ligado a M-. (esto es un punto luego de la tecla META).

save-excursion

Guarda la posicion de punto y marca y restaura sus valores tras evaluar los argumentos de save-excursion. Ademas, recuerda el buffer actual y regresa a el.

push-mark

Establece la marca a una posicion y registra el valor de la marca anterior en el anillo de marcas. La marca es una ubicacion en el búfer que mantendra su posición relativa, incluso si se añade o borra texto del búfer.

goto-char

Establece el punto a la ubicacion especificada por el valor del argumento, que puede ser un número, una marca, o una expresión que devuelve el número de una posición, como (point-min).

insert-buffer-substring

Copia una región de texto desde un búfer que se pasa a la función como argumento e inserta la región dentro del búfer actual.

mark-whole-buffer

Marca el búfer completo como una región. Normalmente unido a C-x h.

set-buffer

Cambia la atención de Emacs a otro búfer, pero no se muestra el cambio en la ventana. Se utiliza cuando un programa y no un humano trabaja en un búfer distinto.

get-buffer-create, get-buffer

Busca un búfer con nombre o crea uno si el búfer con ese nombre no existe. La función get-buffer devuelve nil si el nombre del búfer no existe.

Ejercicios

Algunas Funciones Más Complejas

En este capítulo, nos basamos en lo que hemos aprendido en los capítulos anteriores al analizar funciones más complejas. La función copy-to-buffer ilustra el uso de dos expresiones save-excursion en una definición, mientras que la función insert-buffer ilustra el uso de un asterisco en una expresión interactive, el uso de or, y la importante distinción entre un nombre y el objeto al que el nombre hace referencia.

La definición de copy-to-buffer

Después de comprender cómo trabaja append-to-buffer, es fácil entender copy-to-buffer. Esta función copia texto dentro de un búfer, pero en lugar de agregarlo al segundo búfer, sustituye todo el texto en el segundo búfer.

El cuerpo de copy-to-buffer tiene este aspecto,


(interactive "BCopy to buffer: \nr")
(let ((oldbuf (current-buffer)))
  (with-current-buffer (get-buffer-create buffer)
    (barf-if-buffer-read-only)
    (erase-buffer)
    (save-excursion
      (insert-buffer-substring oldbuf start end)))))

La función copy-to-buffer tiene una expresión interactive mas sencilla que append-to-buffer.

La definición dice:

(with-current-buffer (get-buffer-create buffer) 

En primer lugar, mira la expresión más interna; que se evalua primero. Esta expresión inicia con get-buffer-create buffer. La función le indica al ordenador que utilice el búfer con el nombre específicado como aquel que quieres copiar, o si tal búfer no existe, que lo cree. Luego, la función with-current-buffer evalúa su cuerpo con este búfer temporal.

(Esto demuestra otra forma de cambiar la atención del ordenador pero no la del usuario. La función append-to-buffer muestro como hacer lo mismo con save-excursion y set-buffer. with-current-buffer es un mecanismo nuevo, y posiblemente mas sencillo.)

La función barf-if-buffer-read-only envía un mensaje de error diciendo que el búfer es de solo lectura si no se puede modificar.

La siguiente línea tiene como único contenido la función erase-buffer. Este función borra el búfer.

Finalmente, las últimas dos líneas contienen la expresión save-excursion con insert-buffer-substring como cuerpo. La expresión insert-buffer-substring copia el texto desde el búfer en el que te encuentras (y no has visto al ordenador cambiar su atención, por lo que no sabes que ese búfer ahora se llama oldbuf).

Por cierto, esto es lo que se entiende por ‘reemplazo’. Para reemplazar el texto Emacs borra el texto anterior y luego inserta un texto nuevo.

A grandes rasgos, el cuerpo de copy-to-buffer se ve asi:

(let (enlazar-oldbuf-al-valor-del-bufer-actual)
    (con-el-bufer-al-que-estas-copiando
      (pero-no-borrar-o-copiar-a-un-bufer-de-solo-lectura)
      (erase-buffer)
      (save-excursion
        insertar-la-subcadena-desde-oldbuf-en-el-bufer)))

La definición de insert-buffer

insert-buffer es otra función relacionada con búfers. Este comando copia otro búfer dentro del búfer actual. Es lo contrario a append-to-buffer o copy-to-buffer, dado que copia una región de texto desde el búfer actual a otro búfer.

Aquí examinamos en el código original. El código se simplifico en 2003 y es mas dificil de entender.

(Consulta la Seccion Nuevo Cuerpo para insert-buffer, para ver una discusión del nuevo cuerpo.)

Además, este código ilustra el uso de interactive con un búfer que podría ser read-only (de solo lectura) y la importante distinción entre el nombre de un objeto y el objeto al que realmente hace referencia.

Aquí está el código anterior:

(defun insert-buffer (buffer)
  "Insertar despues del punto el contenido de BUFFER.
Coloca la marca despues del texto insertado.
BUFFER puede ser un buffer o el nombre de un buffer."
  (interactive "*bInsert buffer: ")
  (or (bufferp buffer)
      (setq buffer (get-buffer buffer)))
  (let (start end newmark)
    (save-excursion
      (save-excursion
        (set-buffer buffer)
        (setq start (point-min) end (point-max)))
      (insert-buffer-substring buffer start end)
      (setq newmark (point)))
    (push-mark newmark)))

Al igual que con otras definiciones de función, se puede usar una plantilla para ver un esquema de la función:

(defun insert-buffer (buffer)
  "documentacion…"
  (interactive "*bInsert buffer: ")
  cuerpo)

La expresión interactiva en insert-buffer

En insert-buffer, el argumento de la declaración interactive tiene dos partes, un asterisco, ‘*’, y ‘bInsert buffer: ’.

Un búfer de solo lectura

El asterisco se utliza cuando el búfer actual es un búfer de solo lectura––un búfer que no se puede modificar. Si se llama a insert-buffer cuando el búfer actual es de solo lectura, se imprime un mensaje en el area de eco y el terminal puede emitir un beep o parpadear; no se permitira insertar nada en el búfer actual. El asterisco no tiene que ser seguido por un salto de linea para separarlo del siguiente argumento.

b’ en una expresión interactiva

El siguiente argumento de la expresión interactiva inicia con una letra ‘b’ minúscula. (Esto es diferente del código para append-to-buffer, que utiliza una ‘B’ mayúscula. Consulta la Seccion La Definición de append-to-buffer.) La ‘b’ minúscula le indica al intérprete Lisp que el argumento de insert-buffer debe ser un buffer existente o su nombre. (La opcion ‘B’ mayúscula permite la posibilidad de que el búfer no exista.) Emacs te pedira el nombre del búfer, ofreciendo un búfer por defecto, con el autocompletado de nombre habilitado. Si el búfer no existe, recibiras un mensaje que dice “No match” (no concuerda); tu terminal tambien puede emitir un beep.

El nuevo código simplificado genera una lista para interactive. Este utiliza las funciones barf-if-buffer-read-only y read-buffer con las que ya estamos familiarizados y la forma especial progn con la que no lo estamos. (Se describira mas adelante).

El cuerpo de la función insert-buffer

El cuerpo de la función insert-buffer tiene dos partes principales: una expresión or y una expresión let. El propósito de la expresión or es asegurar que el argumento buffer esta ligado a un búfer y no solo al nombre de un búfer. El cuerpo de la expresión let contiene el código que copia el otro búfer dentro del búfer actual.

A grades rasgos, las dos expresiones encajan asi en la función insert-buffer:

(defun insert-buffer (buffer)
  "documentacion…"
  (interactive "*bInsert buffer: ")
  (or 
      
  (let (varlist)
      cuerpo-de-let )

Para entender como la expresión or asegura que el argumento buffer esta unido a un búfer y no al nombre de un búfer, primero es necesario entender la función or.

Antes de hacer esto, permíteme reescribir esta parte de la función utilizando if de esta manera puedes ver como se hace de una manera que resulte familiar.

insert-buffer con un if en lugar de un or

El trabajo a realizar es asegurarse de que el valor de buffer sea un búfer en sí mismo y no el nombre de un búfer. Si el valor es el nombre, entonces debe optenerse el búfer en sí.

Puedes imaginarte a tí mismo en una conferencia donde un acomodador está observando una lista con tu nombre en ella y mirándote: el acomodador sabe “asociar” tu nombre, no a tí; pero cuando el acomodador te encuentra y te toma el brazo, el acomodador lo “asocia” a tí.

En Lisp, se podría describir esta situación así:

(if (not (tomar-al-invitado))
    (encontrar-y-tomar-del-brazo-al-invitado))

Queremos hacer lo mismo con un búfer––si no tenemos el búfer en sí, queremos conseguirlo.

Usando un predicado llamado bufferp que nos informa si tenemos un búfer (en lugar de su nombre), podemos escribir el código de esta manera:

(if (not (bufferp buffer))              ; parte-if
    (setq buffer (get-buffer buffer)))  ; parte-then

Aquí, la prueba verdadero-o-falso de la expresión if es (not (bufferp buffer)); y la parte then es la expresión (setq buffer (get-buffer buffer)).

En la prueba, la función bufferp devuelve verdadero si su argumento es un búfer––sino falso si el argumento es el nombre del búfer. (El último carácter del nombre de la función bufferp es el carácter ‘p’; como vimos anteriormente, tal uso de ‘p’ es una convención que indica que la función es un predicado, que es un término que significa que la función determinará si alguna propiedad es verdadera o falsa. Consulta la Seccion Usando el tipo incorrecto de objeto como argumento.)

La función not precede a la expresión (bufferp buffer), así la prueba verdadero-o-falso es la siguente:

(not (bufferp buffer))

not es una función que devuelve verdadero si su argumento es falso y falso si su argumento es verdadero. Por lo que si (bufferp buffer) devuelve verdadero, la expresión not devuelve falso y viceversa: lo que es “no verdadero” es falso y lo que es “no falso” es verdadero.

Usando esta prueba, la expresión if funciona de la siguiente manera: cuando el valor de la variable buffer es realmente un búfer en lugar de su nombre, la prueba verdadero-o-falso devuelve falso y la expresión if no evalúa la parte-then. Esto está bien, ya que no tenemos que hacer nada para la variable buffer si realmente es un búfer.

Por otro lado, cuando el valor de buffer no es un buffer en sí, pero si el nombre de un buffer, la prueba verdadero-o-falso devuelve verdadero y se evalua la parte-then de la expresión. En este caso, la parte-then es (setq buffer (get-buffer buffer)). Esta expresión utiliza la función get-buffer para devolver un buffer real, dado su nombre. Luego setq asigna la variable buffer reemplazando su valor anterior (que era el nombre del búfer).

El or en el cuerpo

El propósito de la expresión or en la función insert-buffer es asegurar que el argumento buffer está ligado a un búfer y no solo al nombre de uno. La sección previa muestra como se podría haber hecho el trabajo usando una expresión if. Sin embargo, la función insert-buffer utiliza or. Para entender esto, es necesario entender como funciona or.

Una función or puede tener cualquier número de argumentos. Evalúa un argumento a la ves y devuelve el valor del primero de sus argumentos que no es nil. Ademas, y esta es una caracteristica crucial de or, no evalúa ningun argumento posterior después de regresar el primer valor no-nil.

La expresión or se ve asi:

(or (bufferp buffer)
    (setq buffer (get-buffer buffer)))

El primer argumento de or es la expresión (bufferp buffer). Esta expresión devuelve verdadero (un valor no-nil) si el búfer es realmente un búfer, y no solo el nombre de un búfer. En la expresión or, si este es el caso, la expresión or devuelve este valor verdadero y no evalúa la siguiente expresión––y esto esta bien para nosotros, ya que no queremos hacer nada con el valor de buffer si realmente es un búfer.

Por otro lado, si el valor de (bufferp buffer) es nil, que lo sera si el valor de buffer es el nombre de un buffer, el intérprete Lisp evalúa el siguiente elemento de la expresión. Esta es la expresión (setq buffer (get-buffer buffer)). Esta expresión devuelve un valor no-nil, que es el valor al que establece la variable buffer––y este valor es un búfer en sí, no el nombre de un búfer.

El resultado de todo esto es que el símbolo buffer siempre esta ligado a un búfer en sí mismo y no al nombre de uno. Todo esto es necesario porque la función set-buffer en la línea siguiente solo funciona con un buffer en sí, no con el nombre de un búfer.

A proposito, usando or, la escena con el acomodador se escribiria así:

(or (tomar-al-invitado) (encontrar-y-tomar-del-brazo-al-invitado))

La expresión let en insert-buffer

Después de asegurarse que la variable buffer se refiere a un buffer en sí y no solo al nombre de uno, la función insert-buffer continúa con una expresión let. Esta especifica tres variables locales, start, end y newmark y las une al valor inicial nil. Estas variables se utilizan dentro del resto de let y ocultan temporalmente cualquier otra ocurrencia de variables con el mismo nombre en Emacs hasta el final de let.

El cuerpo de let contiene dos expresiones save-excursion. Primero, veremos la expresión interna save-excursion en detalle. La expresión se ve asi:

(save-excursion
  (set-buffer buffer)
  (setq start (point-min) end (point-max)))

La expresión (set-buffer buffer) cambia la atención de Emacs del búfer actual al buffer desde donde se copiara el texto. En ese búfer las variables start y end se asignan al inicio y al fin del búfer, usando los comandos point-min y point-max. Observa que tenemos aquí un ejemplo de cómo setq es capaz de asignar dos variables en la misma expresión. El primer argumento de setq se establece al valor del segundo, y el tercer argumento se establece al valor del cuarto.

Después de evaluar el interior del cuerpo de save-excursion, save-excursion restaura el búfer original, pero start y end retienen los valores del inicio y fin del búfer del que se copiara el texto.

La expresión extena de save-excursion luce asi:

(save-excursion
  (expresion-interior-de-save-excursion
     (ir-al-nuevo-bufer-y-establecer-start-y-end)
  (insert-buffer-substring buffer start end)
  (setq newmark (point)))

La función insert-buffer-substring copia el texto dento del búfer actual desde la región indicada por start y end en el buffer. Puesto que la totalidad del segundo búfer se encuentra entre start y end, todo dentro del segundo búfer se copia el bufer que se esta editando. A continuacion, el valor del punto, que estara al final del texto insertado, se registra en la variable newmark.

Después de evaluar el cuerpo del save-excursion externo, el punto y la marca se vuelven a colocar en su posicion original.

Sin embargo, es conveniente ubicar una marca al fin del texto recien insertado y ubicar el punto al principio. La variable newmark registra el fin del texto insertado. En la última línea de la expresión let, la expresión (push-mark newmark) asigna una marca a esta posición. (La posición anterior de la marca aun es accesible; se registra en el anillo de marcas y puedes volver a ella con C-u C-SPC.) Mientras tanto, el punto se encuentra al principio del texto insertado, que es donde estaba antes de llamar a la función de insercion, cuya posición fue guardada por la primer save-excursion.

La expresión let completa se ve asi:

(let (start end newmark)
  (save-excursion
    (save-excursion
      (set-buffer buffer)
      (setq start (point-min) end (point-max)))
    (insert-buffer-substring buffer start end)
    (setq newmark (point)))
  (push-mark newmark))

Al igual que la función append-to-buffer, la función insert-buffer utiliza let, save-excursion y set-buffer. Además, la función ilustra una forma de utilizar or. Todas estas funciones son bloques de construccion que vamos a encontrar y utilizar una y otra vez.

Nuevo cuerpo para insert-buffer

El cuerpo en la versión 22 de GNU Emacs es más confuso que el original.

Se compone de dos expresiones

(push-mark
 (save-excursion
   (insert-buffer-substring (get-buffer buffer))
   (point)))

nil

excepto que, y esto es lo que confunde a los novatos, se hace un trabajo muy importante al interior de la expresión push-mark.

La función get-buffer devuelve un búfer con el nombre proporcionado. Notaras que la función no se llama get-buffer-create; no crea un búfer si no existe ya. El búfer devuelto por get-buffer, un búfer existente, se pasa a insert-buffer-substring, que inserta todo el búfer (ya que no se especifica ninguna cosa más).

La posición en la que se inserta el buffer se registra por push-mark. Despues la función devuelve nil, el valor de su último comando. Dicho de otra manera, la función insert-buffer existe solo para producir un efecto secundario, insertando otro buffer, no para devolver ningun valor.

Definición completa de beginning-of-buffer

Ya se ha discutido la estructura básica de la función beginning-of-buffer. (Consulta la Seccion Una definición simplificada de beginning-of-buffer). Esta sección describe la parte compleja de la definición.

Como se ha descrito anteriormente, cuando se invoca a beginning-of-buffer sin argumento, mueve el cursor al inicio del búfer (en realidad, al inicio de la porción accesible del búfer), dejando la marca en la posición anterior. Sin embargo, cuando el comando se invoca con un número entre uno y diez, la función considera que ese número es una fracción del tamaño del búfer, medido en decimas, y Emacs mueve el cursor a dicha fracción del reccorrido desde el inicio del búfer. Por lo tanto, puedes llamar a esta función con el comando de teclado M-<, que moverá el cursor al principio del búfer, o con un comando de teclado como C-u 7 M-< que moverá el cursor a un 70% del recorrido a través del búfer. Si se utiliza un número mayor a diez como argumento, se movera al final del búfer.

La función beginning-of-buffer puede llamarse con o sin argumentos. El uso del argumento es opcional.

Argumentos opcionales

A menos que se diga lo contrario, Lisp espera que una función con un argumento en su definición de función se llamada con un valor para ese argumento. Si esto no ocurre, se obtiene un error y un mensaje que dice ‘Wrong number of arguments’ (Número de argumentos erróneo).

Sin embargo, los argumentos opcionales son una caracteristica de Lisp: se utiliza una palabra clave particular para decirle al intérprete Lisp que un argumento es opcional. La palabra clave es &optional. (El ‘&’ al frente de ‘optional’ es parte de la palabra clave.) En una definición de función, si un argumento va despues de la palabra clave &optional, no es necesario pasar ningún valor a ese argumento al llamar a la función.

Por lo tanto la primera línea de la definición de función de beginning-of-buffer tiene este aspecto:

(defun beginning-of-buffer (&optional arg)

En lineas generales, toda la función luce asi:

(defun beginning-of-buffer (&optional arg)
  "documentacion…"
  (interactive "P")
  (or (es-el-argumento-una-cons-cell argumento)
      (and ambos-transient-mark-mode-y-mark-active-son-verdadero)
      (push-mark))
  (let (determina-el-tamano-y-lo-establece)
  (goto-char
    (si-hay-un-argumento
        averigua-donde-ir
      de-otro-modo-va-a
      (point-min))))
   do-nicety

La función es similar a la función beginning-of-buffer-simplificado excepto que la expresión interactive tiene "P" como argumento y la función goto-char es seguida por una expresión if-then-else que calcula donde poner el cursor si hay un argumento que no es un cons cell.

(Puesto que no explico que es un cons cell en muchos capítulos, por favor, considera ignorar la función consp. Consulta la Seccion Cómo se implementan las listas, y la Seccion Cons Cell y Tipos de Listas en El Manual de Referencia GNU Emacs Lisp).

La "P" en la expresión interactive le indica a Emacs que pase un argumento prefijo, si lo hay, en su forma plana sin procesar. Un argumento prefijo se crea presionando la tecla META seguida por un número, o pulsando C-u y luego un número. (Si no escribes un número, C-u por defecto pasa un cons cell con un 4. Una "p" minúscula en la expresión interactive hace que la función convierta un argumento prefijo a un número.)

La prueba verdadero-o-falso de la expresión if parece compleja, pero no lo es: comprueba si arg tiene un valor que no es nil y si es un cons cell. (Esto es lo que hace consp; comprueba si su argumento es un cons cell.) Si arg tiene un valor distinto a nil (y no es un cons cell), que será el caso si beginning-of-buffer se llama con un argumento numerico, la prueba verdadero-o-falso devolverá verdadero y se evaluara la parte-then de la expresión if. Por otro lado, si beginning-of-bufer no se llama con un argumento, el valor de arg será nil y se evaluara la parte-else de la expresión if. La parte-else es simplemente point-min, y cuando este es el resultado, toda la expresión goto-char es (goto-char (point-min)), que es cómo vimos la función beginning-of-buffer en su forma simplificada.

beginning-of-buffer con un argumento

Cuando se llama a beginning-of-buffer con un argumento, se evalua una expresión que calcula que valor pasar a goto-char. A primera vista esta expresion es bastante compleja. Incluye una expresión if y mucha aritmética. Se ve así:

(if (> (buffer-size) 10000)
    ;; Evita el desbordamiento en buffers de gran tamaño!
    (* (prefix-numeric-value arg)
       (/ size 10))
  (/ (+ 10
        (* size (prefix-numeric-value arg)))
     10))

Como otras expresiones que parecen complejas, la expresión condicional dentro de beginning-of-buffer se puede desenredar viendola como partes de una plantilla, en este caso, con la plantilla para una expresión if-then-else. En forma esquelética, la expresión se ve así:

(if (buffer-es-grande
    divide-el-tamaño-del-buffer-por-10-y-multiplicalo-por-arg
  de-otra-forma-utiliza-el-calculo-alternativo

La prueba verdadero-o-falso de la expresión if interna comprueba el tamaño del buffer. La razón de esto es que la vieja versión de Emacs 18 utilizaba números que no superaban los ocho millones y en el siguiente calculo, el programador temía que Emacs pudiera intentar usar numeros demasiado grandes si el búfer era extenso. El término ‘desbordamiento’, que se menciona en el comentario, significa que los números son demaciado grandes. Las versiones más recientes de Emacs utilizan números mas grandes, pero este código no ha sido tocado, aunque solo sea porque la gente ahora mira búfers que son mucho, mucho mas grandes que antes.

Hay dos casos: si el búfer es grande, o si no lo es.

Qué ocurre en un búfer de gran tamaño

En beginning-of-buffer, la expresión if interna prueba si el tamaño del búfer es mayor a 10000 caracteres. Para ello, se utiliza la función > y el calculo de size que proviene de la expresión let.

En los viejos tiempos, se utilizaba la función buffer-size. No solo se llamo varias ocaciones esa funcion, sino que dio el tamaño de todo el búfer, no la parte accesible. El calculo tiene mucho más sentido cuando se maneja solo la parte accesible. (Consulta la Seccion Reducir y Extender, para obtener más información sobre como centrar la atención en una parte ‘accesible’.)

La linea se ve asi:

(if (> size 10000)

Cuando el búfer es grande, se evalua la parte-then de la expresión if. Se lee así (después de formatearlo para facilitar la lectura):

(*
  (prefix-numeric-value arg)
  (/ size 10))

Esta expresión es una multiplicación, con dos argumentos para la función *.

El primer argumento es (prefix-numeric-value arg). Cuando se usa "P" como argumento para interactive, el valor pasado a la función como argumento es un “argumento prefijo en bruto”, y no un número. (Es un número en una lista). Para realizar el calculo, se necesita una conversión, y prefix-numeric-value hace el trabajo.

El segundo argumento es (/ size 10). Esta expresión divide el valor numérico por diez––el valor numérico del tamaño de la porción accesible del búfer. Esto produce un número que indica cuántos caracteres componen una decima parte del tamaño del búfer. (En Lisp, / se utiliza para la división, igual que * se utiliza para la multiplicación.)

En la expresión de multiplicación como un todo, esta cantidad se multiplica por el valor del argumento prefijo––la multiplicación es la siguiente:

(* valor-numerico-del-argumento-prefijo-arg
   numero-de-caracteres-en-una-decima-parte-de-la-porcion-accesible-del-buffer)

Si, por ejemplo, el argumento prefijo es ‘7’, el valor de una decima parte será multiplicado por 7 para dar una posición del 70% del trayecto.

El resultado de todo esto es que si la porción accesible del búfer es grande, la expresión goto-char es la siguiente:

(goto-char (* (prefix-numeric-value arg)
              (/ size 10)))

Esto coloca el cursor donde lo queremos.

Lo que sucede en un búfer pequeño

Si el búfer contiene menos de 10,000 caracteres, se lleva a cabo un calculo ligeramente diferente. Podrías pensar que esto no es necesario, ya que el primer calculo podría hacer el trabajo. Sin embargo, en un búfer pequeño, el primer método puede no colocar el cursor exactamente en la línea deseada; el segundo método hace un mejor trabajo.

El código es el siguiente:

(/ (+ 10 (* size (prefix-numeric-value arg))) 10))

Para averiguar que ocure en este código debemos descubrir como se anidan las funciones entre paréntesis. Es mas fácil de leer si se reformatea cada expresión, indentando la expresión que contiene:

  (/
   (+ 10
      (*
       size
       (prefix-numeric-value arg)))
   10))

Observando los paréntesis, vemos que la operación mas profunda es (prefix-numeric-value arg), que convierte el argumento prefijo en bruto a un número. En la siguiente expresión, este número se multiplica por el tamaño de la porción accesible del búfer:

(* size (prefix-numeric-value arg))

Esta multiplicación crea un número que puede ser mayor al tamaño del buffer––siete veces mayor si el argumento es 7, por ejemplo. Luego se suma diez a éste numero y finalmente el número se divide por 10 para proporcionar un valor que es un carácter más grande que la posición porcentual en el búfer.

El número que resulta de todo esto se pasa a goto-char y el cursor se mueve a ese punto.

Funcion beginning-of-buffer Completa

Aquí está el texto completo de la función beginning-of-buffer:

(defun beginning-of-buffer (&optional arg)
  "Mueve el punto al principio del bufer;
deja la marca en la posicion anterior.
Con el prefijo \\[universal-argument],
no establece la marca en la posicion anterior.
Con un argumento numerico N,
coloca el punto N/10 del camino desde el inicio.

Si el bufer tiene activo narrowing,
este comando utiliza el principio y el tamaño
de la parte accesible del bufer.

No utilice este comando en programas Lisp!
\(goto-char (point-min)) es mas rapido y evita
tocar la marca."
  (interactive "P")
  (or (consp arg)
      (and transient-mark-mode mark-active)
      (push-mark))
  (let ((size (- (point-max) (point-min))))
    (goto-char (if (and arg (not (consp arg)))
                   (+ (point-min)
                      (if (> size 10000)
                          ;; Evita el desbordamiento en los búfers de gran tamaño!
                          (* (prefix-numeric-value arg)
                             (/ size 10))
                        (/ (+ 10 (* size (prefix-numeric-value arg)))
                           10)))
                 (point-min))))
  (if arg (forward-line 1)))

Excepto por dos pequeños puntos, la discusión anterior muestra cómo funciona esta función. El primer punto se refiere a un detalle en la cadena de documentación, y el segundo se refiere a la última línea de la función.

En la cadena de documentación, hay una referencia a una expresión:

\\[universal-argument]

Se usa Un ‘\\’ antes del primer corchete de esta expresión. Este ‘\\’ le indica al intérprete Lisp que sustituya cualquier clave qué se encuentre dentro de ‘[…]’ por su convinacion de teclado actual. En el caso de universal-argument, suele ser C-u, pero podría ser distinta. (Consulta la Sección Consejos para Cadenas de Documentación en El Manual de Referencia de GNU Emacs Lisp, para más información.)

Finalmente, la última línea del comando beginning-of-buffer dice que hay que mover el punto al inicio de la siguiente línea si el comando se invoca con un argumento:

(if arg (forward-line 1)))

Esto coloca el cursor al inicio de la primer línea despues de la posicion inidicada en decimas en el búfer. Esto significa que el cursor siempre se localiza al menos las decimas del recorrido solicitadas del búfer, esta es una sutiliza, quizás, no necesaria, pero que, si no ocurriera, seguro suscitaria quejas.

Por otro lado, tambien significa que si se especifica el comando con C-u, pero sin un número, es decir, si el ‘argumento prefijo en bruto’ simplemente es un cons cell, entonces el comando te coloca al inicio de la segunda línea … no sé si se pretende esto o si nadie ha tratado el código para evitar que esto suceda.

Repaso

He aquí un breve resumen de algunos de los temas cubiertos en este capítulo.

or

Evalúa cada argumento en secuencia, y devuelve el valor del primer argumento que no es nil, si ninguno devuelve un valor que no sea nil, devuelve nil. En resumen, devuelve el primer valor verdadero de los argumento; devuelve un valor verdadero si uno o cualquiera de los otros es verdadero.

and

Evalúa cada argumento en secuencia, y si alguno es nil, devuelve nil; si ninguno es nil, devuelve el valor del último argumento. En resumen, devuelve un valor verdadero solo si todos los argumentos son verdaderos; devuelve un valor verdadero si uno y cada uno de los otros son verdadero.

&optional

Una palabra clave utilizada para indicar que un argumento en una definición de función es opcional; esto significa que la función se puede evaluar sin el argumento, si se desea.

prefix-numeric-value

Convierte el ‘argumento prefijo en bruto’ producido por (interactive "P") en un valor numérico.

forward-line

Mueve el punto hacia adelante al principio de la siguiente línea, o si el argumento es mayor a uno, hacia delante varias líneas. Si no puede avanzar tanto hacia delante como se supone, forward-line va hacia adelante tan lejos como pueda y luego devuelve un conteo del número de líneas adicionales que debia avanzar poro no pudo.

erase-buffer

Elimina todo el contenido del búfer actual.

bufferp

Devuelve t si su argumento es un búfer; de otro modo devuelve nil.

Ejercicio con el argumento opcional

Escribe una función interactiva con un argumento opcional que comprueve si su argumento, un número, es mayor o igual o menor que el valor de fill-column, y lo informe, en un mensaje. Sin embargo, si no se pasa un argumento a la función, utiliza 56 como valor por defecto.

Reducir y Extender

La reduccion (narrowing) es una funcionalidad de Emacs que hace posible que puedas focalizarte en una parte específica de un búfer, y trabajar sin cambiar accidentalmente otras partes. La reduccion normalmente se deshabilita ya que puede confundir a los principiantes.

Con la reduccion, el resto del búfer se hace invisible, como si no estuviera alli. Esto es una ventaja si, por ejemplo, se quiere reemplazar una palabra en una parte del búfer pero no en otra: limitas la parte que deseas y el reemplazo se lleva a cabo solo en esa sección, no en el resto del búfer. Las búsquedas solo funcionarán con la región reducida, no fuera de ella, de esta forma si estás reparando una parte de un documento, puedes mantener fuera la parte que no necesitas. (El atajo asociado a narrow-to-region es C-x n n.)

Sin embargo, la reduccion hace invisible el resto del búfer, lo que puede asustar a quien invoca inadvertidamente la reduccion y piensa que ha eliminado una parte de su fichero. Ademas, el comando undo (usualmente ligado a C-x u) no desactiva la reduccion (ni debe hacerlo), por lo que las personas pueden llegar a desesperarse si no saben que pueden devolver el resto del buffer a la visibilidad cor el comando widen que es C-x n w.)

La reduccion es igual de util para el intérprete Lisp como para un humano. Con frecuencia, una función Emacs Lisp está diseñada para trabajar solo en parte de un búfer; o por el contrario, una función Emacs Lisp necesita trabajar en todo un búfer que ha sido reducido. La función what-line, por ejemplo, elimina la reduccion de un búfer, si este tiene alguna reduccion y al terminar su trabajo, restaura la reduccion. Por otro lado, la función count-lines utiliza la reduccion para restringirse a sí misma solo a la porción del búfer en la que se está interesado y luego restaura la situación anterior.

La forma especial save-restriction

En Emacs Lisp, se puede utilizar la forma especial save-restriction para llevar un registro de cualquier reduccion en efecto. Cuando el intérprete Lisp se encuentra con save-restriction, ejecuta el código en el cuerpo de la expresión save-restriction, y luego deshace cualquier cambio en la reduccion causada por el código. Si, por ejemplo, el búfer es reducido y el código que sigue al comando save-restriction se deshace de la reduccion, save-restriction devuelve el búfer a su región reducida. En el comando what-line, cualquier reduccion del búfer puede anularse por el comando widen inmediatamente despues del comando save-restriction. Se restaura cualquier reduccion original justo antes de finalizar la función.

La plantilla para una expresión save-restriction es simple:

(save-restriction
  cuerpo )

El cuerpo de save-restriction es una o más expresiones que seran evaluadas de forma secuencial por el intérprete Lisp.

Finalmente, un punto a tener en cuenta: cuando utilices save-excursion y save-restriction a la vez, una detras de la otra, debes utilizar save-excursion primero. Si lo escribes en orden inverso, es posible que no registre la reduccion en el búfer al que emacs debe cambiar Emacs despues de llamar a save-excursion. Por lo tanto, cuando escribas a la vez save-excursion y save-restriction debe ser así:

(save-excursion
  (save-restriction
    cuerpo))

En otras circunstancias, cuando no se escriban a la vez, las formas especiales save-excursion y save-restriction deben escribirse en el orden adecuado para la función.

Por ejemplo,

(save-restriction
  (widen)
  (save-excursion
  cuerpo))

what-line

El comando what-line informa el número de la línea en la que el cursor esta colocado. La función ilustra el uso de los comandos save-restriction y save-excursion. Aquí está el texto original de la función:

(defun what-line ()
  "Imprime el numero de linea actual (en el bufer) del punto."
  (interactive)
  (save-restriction
    (widen)
    (save-excursion
      (beginning-of-line)
      (message "Line %d"
               (1+ (count-lines 1 (point)))))))

(En versiones recientes de GNU Emacs, la función what-line se ha ampliado para decirte el numero de linea en un búfer reducido, asi como el número de línea en un búfer extendido. La versión reciente es más compleja que la versión que se muestra aqui. Si te sientes aventurero, puede que quieras verla despues de averiguar como funciona esta version. Probablemente necesites utilizar C-h f (describe-function). La nueva versión utiliza un condicional para determinar si se ha reducido el búfer.

(También, utiliza line-number-at-pos, que entre otras expresiones sencillas, como (goto-char (point-min)), mueve el punto al inicio de la línea actual con (forward-line 0) en lugar de beginning-of-line.)

La función what-line que se muestra aqui tiene una línea de documentación y es interactiva, como es de esperar. Las dos líneas siguientes utilizan las funciones save-restriction y widen.

La forma especial save-restriction observa cualquier reduccion activa en el buffer actual y restaura la reduccion despues de evaluar el código de su cuerpo.

La forma especial save-restriction es seguida por widen. Esta función deshace cualquier reduccion que pudiera haber tenido el búfer actual cuando se llama a what-line. (La reduccion que estaba alli es la reduccion que save-restriction recuerda.) Esta ampliación hace posible que los comandos para el conteo de lineas cuenten desde el inicio del búfer. De lo contrario, se habría limitado a contar dentro de la región accesible. Cualquier reduccion original se restaura justo antes de completar la funcion por la forma especial save-restriction.

La llamada a widen es seguida por save-excursion, que guarda la posición del cursor (es decir, el punto) y la marca, y los restaura después de que el código en el cuerpo de save-excursion utiliza la función beginning-of-line para mover el punto.

(Ten en cuenta que la expresión (widen) ocurre entre las formas especiales save-restriction y save-excursion. Cuando escribas las dos expresiones save-… consecutivamente, escribe save-excursion primero.)

Las dos últimas líneas de la función what-line son funciones para contar el número de líneas en el búfer y luego imprimir el número en el área de eco.

(message "Line %d"
         (1+ (count-lines 1 (point)))))))

La función message imprime un mensaje de una línea en la parte inferior de la pantalla de Emacs. El primer argumento esta dentro de comillas y se imprime como una cadena de caracteres. Sin embargo, contiene una expresión ‘%d’ para imprimir el siguiente argumento. ‘%d’ imprime el argumento como un decimal, por lo que el mensaje dirá algo como ‘Línea 243’.

El número que se imprime en lugar del ‘%d’ se calcula por la última línea de la función:

(1+ (count-lines 1 (point)))

Lo que esto hace es contar las líneas desde la primer posición del búfer indicada por el 1, hasta (point), y luego sumar uno a este número. (La función 1+ suma uno a su argumento.) Se suma uno porque la línea 2 tiene solo una línea antes de ella, y count-lines cuenta solo las líneas antes de la línea actual.

Después que count-lines termina su trabajo, y se imprime el mensaje en el área de eco, la función save-excursion restaura el punto y marca a sus posiciones originales; y save-restriction restaura la reduccion original, si la hubiera.

Ejercicio con Reduccion

Escribe una función que muestre los primeros 60 caracteres del búfer actual, incluso si has reducido el búfer a la mitad de modo que la primer línea sea inaccesible. Restaura el punto, marca y la reduccion. Para este ejercicio, necesitas utilizar todo un popurri de funciones, incluyendo save-restriction, widen, goto-char, point-min, message, y buffer-substring.

(buffer-substring es una función que aun no se menciona, tendrás que investigarla por tu cuenta; o quizás tengas que utilizar buffer-substring-no-properties o filter-buffer-substring …, u otras funciones. Las propiedades de texto son una funcionalidad que no sera discutida aquí. Consulta la Seccion Propiedades de Texto en El Manual de Referencia de Emacs Lisp.)

Además, ¿realmente se necesita goto-char o point-min? ¿O se puede escribir la función sin ellas?

car, cdr, cons: Funciones fundamentales

En Lisp, car, cdr, y cons son funciones fundamentales. La función cons se utiliza para construir listas, y las funciones car y cdr se utilizan para desmontarlas.

En el recorrido a través de la función copy-region-as-kill, veremos cons, asi como dos variantes de cdr, llamadas setcdr y nthcdr. (Consulta la Sección copy-region-as-kill.)

El nombre de la función cons no es irazonable: es una abreviatura de la palabra ‘construct’ (construir). Por otra parte, el origen de los nombres car y cdr, es esoterico: car es un acrónimo de la frase ‘Contents of the Address part of the Register’ (Contenidos de las Direcciónes parte del Registro); y cdr (pronunciado ‘could-er’) es un acrónimo de la frase ‘Contents of the Decrement part of the Register’ (Contenidos de la parte de Decremento del Registro). Estas frases se refieren a piezas específicas de hardware en el ordenador en el que se desarrollo el Lisp original. Ademas de ser obsoletas, las frases han sido completamente irrelevantes por más de 25 años para cualquiera que piense en Lisp. No obstante, aunque algunos pocos académicos valientes han empezado a utilizar nombres más razonables para estas funciones, los viejos términos aun continuan en uso. En particular, dado que los términos se utilizan en el código fuente de Emacs Lisp, los usaremos en esta introducción.

car y cdr

El car de una lista es sencillamente, el primer elemento de la lista. De este modo, el car de la lista (rosa violeta margarita tulipan) es rosa.

Si estás leyendo esto dentro de GNU Emacs, puedes verlo evaluando lo siguiente:

(car '(rosa violeta margarita tulipan))

Después de evaluar la expresión, aparecerá rosa en el área de eco.

Claramente, un nombre más razonable para la función car sería first y esto es con frecuencia lo que se sugiere.

car no elimina el primer elemento de la lista; solo informa de lo que es. Después de aplicar car a una lista, la lista sigue siendo la misma que antes. En la jerga, car es ‘no destructiva’. Esta caracteristica resulta ser importante.

El cdr de una lista es el resto de la lista, es decir, la función cdr devuelve la parte de la lista que sigue al primer elemento. Por lo tanto, mientras que el car de la lista '(rosa violeta margarita tulipan) es rosa, el resto de la lista, el valor devuelto por la función cdr, es (violeta margarita tulipan).

Puedes ver esto evaluando lo siguiente del modo habitual:

(cdr '(rosa violeta margarita tulipan))

Al evaluar esto, aparece (violeta margarita tulipan) en el área de eco.

Al igual que car, cdr no elimina los elementos de la lista––simplemente devuelve un informe de lo que son el segundo y los elementos siguientes.

A propositio, en el ejemplo, se cita la lista de las flores. De otra forma, el intérprete Lisp intentaría evaluar la lista llamando a rosa como una función. En este ejemplo, no queremos hacer esto.

Claramente, un nombre más razonable para cdr sería rest (resto).

(Hay una lección aquí: Cuando des nombre a nuevas funciones, considera muy cuidadosamente lo que estes haciendo, ya que puedes adherirte a los nombres mas tiempo del esperado. La razón por la que este documento perpetúa estos nombres se debe a que el código fuente de Emacs Lisp los utiliza, y de no usarlos, tendrias dificultades para leer el codigo; por favor, intenta evitar usar estos términos por tu cuenta. Las personas que vengan después te lo agradecerán.

Cuando se aplican car y cdr a una lista compuesta por símbolos, como la lista (pino abeto roble arce), el elemento de la lista devuelto por la función car es el símbolo pino sin ningun paréntesis alrededor. pino es el primer elemento en la lista. Sin embargo, el cdr de la lista es una lista en si misma, (abeto roble arce), como puedes observar al evaluar las siguientes expresiones:

(car '(pino abeto roble arce))

(cdr '(pino abeto roble arce))

Por otro lado, en una lista de listas, el primer elemento es en sí mismo una lista. car devuelve este primer elemento como una lista. Por ejemplo, la siguiente lista contiene tres sub-listas, una lista de carnívoros, una lista de herbívoros y una lista de mamíferos:

(car '((leon tigre leopardo)
       (gacela antilope cebra)
       (ballena delfin foca)))

En este ejemplo, el primer elemento de car de la lista es la lista de carnívoros, (leon tigre leopardo), y el resto de la lista es ((gacela antilope cebra) (ballena delfin foca)).

(cdr '((leon tigre leopardo)
       (gacela antilope cebra)
       (ballena delfin foca)))

vale la pena decir nuevamente que car y cdr son no destructivos––es dicir, no modifican ni cambian las listas a las que se aplican. Esto es muy importante para la forma en que se utilizan.

En el primer capítulo, en la discusión sobre los átomos, dije que en Lisp, “ciertos tipos de átomos, como un arreglo, pueden ser separados en partes; pero el mecanismo para hacer esto es diferente del mecanismo para separar una lista. Para Lisp, los átomos de una lista son indivisibles.” (Consulta la Seccion Átomos Lisp.) Las funciones car y cdr se utilizan para dividir listas y se consideran fundamentales en Lisp. Ya que no se puede dividir o tener acceso a las partes de un arreglo, un arreglo se considera un átomo. Por otro lado, la otra función fundamental, cons, puede armar o construir una lista, pero no un arreglo. (Los arreglos se manejan mediante funciones especificas de arreglos. Consulta la Seccion Arreglos en el El Manual de Referencia de GNU Emacs Lisp.)

cons

La función cons construye listas; que es lo opuesto a car y cdr. Por ejemplo, se puede utilizar cons para hacer una lista de cuatro elementos de la lista de tres elementos, (abeto roble arce):

(cons 'pino '(abeto roble arce))

Después de evaluar esto, veras

(pino abeto roble arce)

aparecer en el área de eco. cons produce una nueva lista en la que el elemento es seguido por los elementos de la lista original.

Con frecuencia decimos que ‘cons coloca un nuevo elemento al principio de una lista; que agrega o empuja el elemento en la lista’, pero esta frase puede ser engañosa, ya que cons no modifica una lista existente, sino que crea una nueva.

Al igual que car y cdr, cons es no destructivo.

cons debe tener una lista a unir.9 No puedes iniciar de la nada absoluta. Si estás construyendo una lista, es necesario proporcionar al menos una lista vacía al inicio. Aquí hay una serie de expresiones cons que construyen una lista de flores. Si está leyendo esto en GNU Emacs, puedes evaluar cada una de las expresiones para corroborar el resultado.

> (cons 'tulipan ())
(tulipan)
> (cons 'margarita '(tulipan))
(margarita tulipan)
> (cons 'violeta '(margarita tulipan))
(violeta margarita tulipan)
> (cons 'rosa '(violeta margarita tulipan))
(rosa violeta margarita tulipan)

En el primer ejemplo, la lista vacía se muestra como () y se construye una lista compuesta por tulipan seguida por la lista vacía. Como puedes ver, la lista vacía no se muestra en la lista que se construyo. Todo lo que ves es (tulipan). La lista vacía no cuenta como un elemento de una lista porque no hay nada en una lista vacía. En terminos generales, una lista vacía es invisible.

El segundo ejemplo, (cons 'margarita '(tulipan)) construye una nueva lista de dos elemento colocando margarita delante de tulipan; y el tercer ejemplo construye una lista de tres elementos colocando violeta delante de margarita y tulipan.

Descubrir la longitud de una lista: length

Puedes averiguar cuántos elementos hay en una lista utilizando la función Lisp length, como en los siguientes ejemplos:

> (length '(tulipan))
1
> (length '(margarita tulipan))
2
> (length (cons 'violeta '(margarita tulipan)))
3

En el tercer ejemplo, la función cons se utiliza para construir una lista de tres elementos que se pasa como argumento a la función length.

También podemos utilizar length para contar el número de elementos en una lista vacía:

> (length ())
0

Como era de esperar, el número de elementos en una lista vacía es cero.

Un experimento interesante es averiguar qué ocurre si se intenta encontrar la longitud de ninguna lista; es decir, si se intenta llamar a length sin darle un argumento, ni siquiera una lista vacía:

(length )

La que se ve, si evalúas esto, es el mensaje de error

Lisp error: (wrong-number-of-arguments length 0)

Esto significa que la función recibe un número incorrecto de argumentos, cero, cuando se espera otro número de argumentos. En este caso, se espera un argumento, el argumento es una lista cuya longitud mide la función. (Ten en cuenta que una lista es un argumento, incluso si la lista tiene muchos elementos en su interior.)

La parte del mensaje de error que dice ‘length’ es el nombre de la función.

nthcdr

La función nthcdr esta asociada a la función cdr. Lo que hace es tomar el cdr de una lista repetidamente.

Si tomas el cdr de la lista (pino abeto roble arce), te devuelve la lista (abeto roble arce). Si repites esto al retorno, devolverá la lista (roble arce). (Por supuesto, repetir cdr en la lista original solo dará el cdr original, ya que la función no cambia la lista. Necesitas evaluar el cdr del cdr y así sucesivamente.) Si esto contiúa, finalmente se devuelve una lista vacía, que en este caso, en vez de mostrarse como () se muestra como nil.

Para comprobarlo, aquí hay una serie de cdrs repetidos.

> (cdr '(pino abeto roble arce))
(abeto roble arce)
> (cdr '(abeto roble arce))
(roble arce)
> (cdr '(roble arce))
(arce)
> (cdr '(arce))
nil
> (cdr 'nil)
nil
> (cdr ())
nil

También puedes hacer varios cdrs sin imprimir los valores intermedios, de esta forma:

> (cdr (cdr '(pino abeto roble arce)))
(roble arce)

En este ejemplo, el intérprete Lisp primero evalúa la lista mas interna. La lista mas interna se cita, por lo que solo pasa la lista tal cual al cdr interno. Este cdr pasa una lista formada por el segundo y los subsiguientes elementos de la lista al cdr externo, que produce una lista compuesta del tercer y los subsiguientes elementos de la lista original. En este ejemplo, la función cdr se repite y devuelve una lista que consiste en la lista original sin sus primeros dos elementos.

La función nthcdr hace lo mismo que repetir la llamada a cdr. En el siguiente ejemplo, el argumento 2 se pasa a la función nthcdr, junto con la lista, y el valor devuelto es la lista sin sus dos primeros elementos, que es exactamente lo mismo que repetir dos veces cdr en la lista:

> (nthcdr 2 '(pino abeto roble arce))
(roble arce)

Utilizando la lista original de cuatro elementos, podemos ver qué ocurre cuando se pasan varios argumentos numéricos a nthcdr, incluyendo 0, 1, y 5:

> ;; Deja la lista como estaba.
> (nthcdr 0 '(pino abeto roble arce))
(pino abeto roble arce)
> ;; Regresa una copia sin el primer elemento.
> (nthcdr 1 '(pino abeto roble arce))
(abeto roble arce)
> ;; Regresa una copia de la lista sin tres elementos.
> (nthcdr 3 '(pino abeto roble arce))
(arce)
> ;; Regresa una copia sin los cuatro elementos.
> (nthcdr 4 '(pino abeto roble arce))
nil
> ;; Regresa una copia sin todos los elementos.
> (nthcdr 5 '(pino abeto roble arce))
nil

nth

La función nthcdr toma el cdr de una lista repetidamente. La función nth toma el car del resultado devuelto por nthcdr. Devuelve el enesimo elemento de la lista.

Por lo tanto, si nth no estubiera definido en C por velocidad, su definición sería:

(defun nth (n list)
  "Devuelve el N-esimo elemento de la lista.
N cuenta apartir de cero. Si LIST no es tan largo, devuelve nil."
  (car (nthcdr n list)))

(Originalmente, nth se definio en Emacs Lisp dentro de subr.el, pero su definición fué rehecha en C en la decada de 1980.)

La función nth devuelve un solo elemento de una lista. Esto puede ser muy conveniente.

Observa que los elementos estan numerados apartir del cero, no de uno. Es decir, el primer elemento de una lista, su car es el elemento cero. Esto se llama contar ‘basado en cero’ y con frecuencia molesta a las personas que estan acostumbradas a que el primer elemento de una lista sea el número uno, que es ‘basado en uno’.

Por ejemplo:

> (nth 0 '("uno" "dos" "tres"))
"uno"
> (nth 1 '("uno" "dos" "tres"))
"dos"

Vale la pena mencionar que nth, al igual que nthcdr y cdr, no modifica la lista original––la función es no destructiva. Esto contrasta fuertemente con las funciones setcar y setcdr.

setcar

Como podrías adivinar por sus nombres, las funciones setcar y setcdr establecen el car o el cdr de una lista a un nuevo valor. Ambos cambian realmente la lista original, a diferencia de car y cdr que dejan la lista original como estaba. Una forma de averiguar cómo funcionan es experimentar. Vamos a empezar con la función setcar.

Primero, podemos crear una lista y luego asignar el valor de una variable a la lista, usando la función setq. Aquí hay una lista de animales:

(setq animales '(antilope jirafa leon tigre))

Si estás leyendo esto dentro de GNU Emacs, puedes evaluar esta expresión de la forma habitual, coloca el cursor después de la expresión y presiona C-x C-e. (Estoy haciendo esto aqui mismo, mientras lo escribo. Esta es una de las ventajas de tener el intérprete construido dentro del entorno informatico. Por cierto, cuando no hay nada en la línea después del paréntesis final, como un comentario, el punto puede estar en la siguiente línea. De este modo, si tu cursor está en la primera columna de la siguiente línea, no es necesario moverlo. En realidad, Emacs permite cualquier cantidad de espacios en blanco después del paréntesis final.)

Cuando evaluamos la variable animales, vemos que está unida a la lista (antilope jirafa leon tigre):

> animales
(antilope jirafa leon tigre)

Dicho de otro modo, la variable animales apunta a la lista (antilope jirafa leon tigre).

A continuacion, evalua la función setcar mientras le pasas dos argumentos, la variable animales y el símbolo citado hipopotamo; esto se hace escribiendo una lista de tres elementos (setcar animales 'hipopotamo). Evaluala de la forma habitual:

(setcar animales 'hipopotamo)

Después de evaluar esta expresión, evalúa la variable animales de nuevo. Veras que la lista de animales ha cambiado:

> animales
(hipopótamo jirafa leon tigre)

El primer elemento de la lista, antilope fue reemplazado por hipopotamo.

Así podemos ver que setcar no agrega un nuevo elemento a la lista como haria cons; Se reemplaza antílope con hipopótamo; esto cambia la lista.

setcdr

La función setcdr es similar a la función setcar, excepto que la función reemplaza el segundo y subsiguientes elementos de una lista en lugar del primer elemento.

(Para ver cómo cambiar el último elemento de una lista, mira directamente en la Seccion La función kill-new, que utiliza las funciones nthcdr y setcdr.)

Para ver cómo funciona esto, asigna el valor de la variable a una lista de animales domesticados evaluando la siguiente expresión:

(setq animales-domesticados '(caballo vaca oveja cabra))

Si evalúas la lista, debe devolverte la lista (caballo vaca oveja cabra):

> animales-domesticados
(caballo vaca oveja cabra)

Luego, evalúa setcdr con dos argumentos, el nombre de la variable que tiene una lista como su valor, y la lista a la que se establecera el cdr de la primera lista;

(setcdr animales-domesticados '(gato perro))

Si evalúas esta expresión, la lista (gato perro) aparecerá en el área echo. Este es el valor devuelto por la función. El resultado que nos interesa es el “efecto secundario”, que podemos ver evaluando la variable animales-domesticados:

> animales-domesticados
(caballo gato perro)

En efecto, la lista cambia de (caballo vaca oveja cabra) a (caballo gato perro). El cdr de la lista cambia de (vaca oveja cabra) a (gato perro).

Ejercicio

Construye una lista de cuatro pájaros evaluando varias expresiones con cons. Descubre que ocurre cuando aplicas cons a una lista sobre si misma. Reemplaza el primer elemento de la lista de cuatro pájaros con un pez. Reemplaza el resto de esta lista con una lista de otros peces.

Cortar y Almacenar Texto

Cada vez que se corta texto de un búfer con un comando ‘kill’, se almacena en una lista y puedes traerlo de vuelta con un comando ‘yank’.

(El uso de la palabra ‘kill’ (matar) en Emacs para procesos que específicamente no destruyen los valores de las entidades es un accidente histórico desafortunado. Una palabra mucho más apropiada seria ‘clip’ (recortar) ya que eso es lo que hacen los comandos ‘kill’; recortan el texto de un búfer y lo almacenan para que puede traerse de vuelta. Con frecuencia me he sentido tentado a sustituir globalmente todas las apariciones de ‘kill’ en el codigo de Emacs con ‘clip’ y todas las apariciones de ‘killed’ (destruido) con ‘clipped’ (cortado).)

Cuando el texto se corta de un búfer, se almacena en una lista. Los fragmentos de texto se almacenan en la lista de forma sucesiva, por lo que la lista podría verse así:

("una pieza de texto" "pieza anterior")

La función cons se puede utilizar para crear una nueva lista a partir de un trozo de texto (un ‘átomo’, para usar la jerga) y una lista existente, como esta:

(cons "otra pieza"
      '("una pieza de texto" "pieza anterior"))

Si evaluas esta expresión, aparecera una lista de tres elementos en el área de eco:

("otra pieza" "una pieza de texto" "pieza anterior")

Puedes recuperar cualquier pieza de texto que desees, con las funciones car y nthcdr. Por ejemplo, en el siguiente código, nthcdr 1 … devuelve la lista con el primer elemento eliminado; y car devuelve el primer elemento de ese resto––el segundo elemento de la lista original:

> (car (nthcdr 1 '("otra pieza"
                 "una pieza de texto"
                 "pieza anterior")))
"una pieza de texto"

Por supuesto, las funciones reales en Emacs son más complejas que esto. El código para el corte y recuperarcion de texto tiene que ser escrito de modo que Emacs pueda determinar qué elemento en la lista se quiere––el primero, segundo, tercero o cualquier otro. Además, cuando se llega al final de la lista, Emacs deberia darte el primer elemento de la lista, en lugar de nada en absoluto.

La lista que contiene los trozos de texto se llama kill ring (anillo de la muerte). En este capítulo se hace una descripción del anillo de la muerte y como se utiliza en un primer vistazo a la función zap-to-char. Esta función utiliza (o ‘llama’) a una función que invoca a otra función que manipula el anillo de la muerte. Por la tanto, antes de llegar a las montañas, debemos escalar las colinas.

En un capítulo posterior se describe cómo se recupera el texto que se corta de un buffer. Consulta la Sección Traer de regreso el texto.

zap-to-char

La función zap-to-char apenas ha variado entre la versión 19 y 22 de GNU Emacs. Sin embargo, zap-to-char llama a otra función, kill-region, que ha tenido una importante reescritura.

La función kill-region en Emacs 19 es compleja, pero no utiliza código que sea importante en este momento. Nos Lo saltaremos.

La función kill-region en Emacs 22 es más fácil de leer que la misma función en Emacs 19 e introduce un concepto muy importante, la gestion de errores. Caminaremos a través de la función.

Pero primero, veamos la función interactiva zap-to-char.

La función zap-to-char elimina el texto en la región entre la ubicacion del cursor (es decir, del punto) hasta e incluyendo la siguiente aparicion de un caracter específicado. El texto que zap-to-char elimina se pone en el anillo de la muerte; y se puede recuperar escribiendo C-y (yank). Si el comando recive un argumento, elimina el texto a través de ese número de ocurrencias. Por lo tanto, si el cursor estuviera al inicio de esta frase y el carácter fuera ‘s’, se eliminaria ‘Por lo tanto, s’. Si el argumento fuera dos, se eliminaria ‘Por lo tanto, si el curs’, hasta e incluiendo la ‘s’ en ‘cursor’.

Si no se encuentra el carácter específicado zap-to-char dirá “Search failed” (Búsqueda fallida), te indicaria el caracter que escribiste, y no eliminara ningun texto.

Para determinar la cantidad de texto a eliminar zap-to-char utiliza una función de búsqueda. Las búsquedas se utilizan ampliamente en código que manipula el texto, y vamos a centrar la atención en ellos, asi como en el comando de eliminacion.

Aquí está el texto completo de la función en la versión 22:

(defun zap-to-char (arg char)
  "Corta e incluye la aparicion de la enesima ocurrencia (ARG) del caracter CHAR.
Se ignora entre mayusculas y minusculas si ‘case-fold-search’ es no-nil en
el buffer actual.
Retrocede si ARG es negativo; error si CHAR no se encuentra."
  (interactive "p\ncZap to char: ")
  (if (char-table-p translation-table-for-input)
      (setq char (or (aref translation-table-for-input char) char)))
  (kill-region (point) (progn
                         (search-forward (char-to-string char) nil nil arg)
                         (point))))

La linea de documentacion esta traducida al español. En la version original no se utiliza la palabra ‘Corta’ en su lugar se utiliza ‘kill’.

La expresión interactive

La expresión interactiva en el comando zap-to-char es esta:

(interactive "p\ncZap to char: ")

La parte entre comillas, "p\ncZap to char: ", especifica dos cosas diferentes. Primero, y lo mas sencillo, es la ‘p’. Esta parte se separa de la siguiente parte por una línea nueva, ‘\n’. La ‘p’ significa que a el primer argumento de la función será pasando el valor de un ‘prefijo procesado’. El argumento prefijo se pasa escribiendo C-u y un número, o M- y un número. Si la función se llamada interactivamente sin un prefijo, se pasa 1 a este argumento.

La segunda parte de "p\ncZap to char: " es ‘cZap to char:’. En esta parte, la ‘c’ minuscula indica que interactive espera un prompt y que el argumento será un caracter. El prompt va despues de ‘c’ y es la cadena ‘Zap to char: ’ (con un espacio después de los dos puntos para que se vea bien).

Lo que todo esto hace es preparar los argumentos de zap-to-char para que sean del tipo correcto, y darle al usuario un prompt.

En un búfer de solo lectura, la función zap-to-char copia el texto al anillo de la muerte, pero no lo elimina. El área de eco muestra un mensaje diciendo que el búfer es de solo lectura. Ademas, el terminal puede emitir un pitido o parpadear.

El cuerpo de zap-to-char

El cuerpo de la función zap-to-char contiene el código que mata (es decir, elimina) el texto en la región desde la posición actual del cursor hasta e incluyendo el carácter especificado.

La primera parte del código luce asi:

(if (char-table-p translation-table-for-input)
    (setq char (or (aref translation-table-for-input char) char)))
(kill-region (point) (progn
                       (search-forward (char-to-string char) nil nil arg)
                       (point)))

char-table-p es una función que aun no hemos visto. Determina si su argumento es una tabla de caracteres. Si lo es, establece el caracter pasado a zap-to-char a uno de ellos, si ese carácter existe, o al carácter en sí. (Esto es importante para ciertos caracteres en idiomas no europeos. La función aref extrae un elemento desde un arreglo. Esta funcion es específica para arreglos y no sera descrita en este documento. Consulta la Seccion Arreglos en El Manual de Referencia de GNU Emacs Lisp.)

(point) es la posición actual del cursor.

La siguiente parte del código es una expresión utilizando progn. El cuerpo de progn consiste en llamadas a search-forward y point.

Es fácil comprender cómo funciona progn después de aprender sobre search-forward, asi que veremos search-forward y luego progn.

La Función search-forward

La función search-forward se utiliza para localizar el caracter a borrar en zap-to-char. Si la búsqueda es exitosa, search-forward deja el punto inmediatamente después del último carácter en la cadena objetivo. (En zap-to-char, la cadena objetivo tiene solo un carácter longitud. zap-to-char usa la función char-to-string para asegurar que el computador trata este carácter como una cadena). Si la búsqueda es hacia atrás, search-forward deja el punto justo antes del primer carácter en el objetivo. Ademas, search-forward devuelve t para verdadero. (Por lo tanto, desplazar el punto es un ‘efecto secundario’.)

En zap-to-char, la función search-forward se ve así:

(search-forward (char-to-string char) nil nil arg)

La función search-forward utiliza cuatro argumentos:

  1. El primer argumento es el objetivo, que esta buscando. Debe ser una cadena, como "z".

    Sucede que el argumento pasado a zap-to-char es un solo caracter. Debido a la forma en la que se construyen los computadores, el intérprete Lisp puede tratar un solo caracter de forma distinta a una cadena de caracteres. Dentro del computador, un solo caracter tiene un formato electrónico diferente a una cadena de un caracteres. (Un solo caracter con frecuencia puede grabarse en el computador utilizardo exactamente un byte; pero una cadena puede ser mas larga, y el equipo debe estar listo para ello.) Ya que la función search-forward busca una cadena, el caracter que recive la función zap-to-char como argumento debe convertirse dentro del computador de un formato a otro; de lo contrario, la función search-forward fallará. Se utiliza la función char-to-string para realizar esta conversión.

  2. El segundo argumento limita la búsqueda; se especifica como una posición en el búfer. En este caso, la búsqueda puede ir al final del búfer, por lo que no se establece ningun limite y el segundo argumento es nil.

  3. El tercer argumento le dice a la función lo que debe hacer si la búsqueda falla––puede señalar un error (e imprimir un mensaje) o puede devolver nil. Un nil como el tercer argumento hace que la función señale un error cuando la búsqueda falla.

  4. El cuarto argumento de search-forward es el contador de repeticion––cuántas ocurrencias de la cadena hay que buscar. Este argumento es opcional y si la función se llamada sin contador de repeticion, este argumento pasa el valor 1. Si este argumento es negativo, la búsqueda va hacia atrás.

En formato plantilla, una expresión search-forward tiene este aspecto:

(search-forward "cadena-a-buscar"
                limite-de-busqueda
                que-hacer-si-la-busqueda-falla
                contador-de-repeticion)

A continuacion veremos progn.

La forma especial progn

progn es una forma especial que hace que cada uno de sus argumentos sea evaluado en secuencia y luego devuelve el valor del último. Las expresiones anteriores solo se evaluan por los efectos secundarios que producen. Los valores producidos son descartados.

La plantilla de una expresión progn es muy simple:

(progn
  cuerpo)

En zap-to-char, la expresión progn tiene que hacer dos cosas: poner el punto exactamente en la posición correcta; y devolver la posición del punto para que kill-region sepa hasta donde cortar.

El primer argumento de progn es search-forward. Cuando search-forward encuentra la cadena, la función deja el punto inmediatamente después del último caracter en la cadena objetivo. (En este caso la cadena objetivo tiene solo un carácter de longitud.) Si la búsqueda es hacia atrás, search-forward deja el punto justo antes del primer carácter objetivo. El movimiento del punto es un efecto secundario.

El segundo y último argumento de progn es la expresión (point). Esta expresión devuelve el valor del punto, que en este caso será la ubicacion a la que se ha desplazado por search-forward. (En el codigo, una línea le indicaba a la función que debia ir un carácter atras, si va hacia adelante, se comentó en 1999; yo no recuerdo si esta caracteriscita o funcion incorrecta alguna vez fue parte del codigo distribuido.) El valor de point es devuelto por la expresión progn y se pasa a kill-region como el segundo argumento de kill-region .

Resumiendo zap-to-char

Ahora que hemos visto cómo funcionan search-forward y progn, podemos ver cómo la función zap-to-como trabaja como un todo.

El primer argumento de kill-region es la posición del cursor cuando se da el comando zap-to-char––el valor de punto en ese momento. Dentro de progn, la funcion de búsqueda mueve el punto justo después del caracter a borrar y point devuelve el valor de esa ubicacion. La función kill-region reune estos dos valores de punto, el primero como el inicio de la región y el segundo como el final de la región, y elimina la región.

La forma especial progn es necesaria porque el comando kill-region toma dos argumentos; y fallaría si las expresiones search-forward y point se escribieran en secuencia como dos argumentos adicionales. La expresión progn es solo un argumento para kill-region y devuelve el valor que kill-region necesita para su segundo argumento.

kill-region

La función zap-to-char utiliza la función kill-region. Esta función corta texto de una región y copia ese texto al anillo de la muerte, desde el que puede ser recuperado.

La versión en Emacs 22 de esta funcion utiliza condition-case y copy-region-as-kill, ambas seran explicadas. condition-case es una forma especial importante.

En esencia, la función kill-region llama a condition-case, que toma tres argumentos. En esta función, el primer argumento no hace nada. El segundo argumento contiene el código que hace el trabajo cuando todo va bien. El tercer argumento contiene el código que se llama en caso de error.

Revisaremos el código de condition-case en un momento. Primero, veamos la definición de kill-region, con comentarios añadidos:

(defun kill-region (beg end)
  "Kill (\"corta\") el texto entre el punto y la marca.
Esto elimina el texto del buffer y lo guarda en el anillo de la muerte.
El comando \\[yank] puede recuperarlo desde alli. … "

  ;; • Puesto que el orden importa, primero pasa el punto.
  (interactive (list (point) (mark)))
  ;; • Y dinos si no podemos cortar el texto.
  ;; ‘unless’ es un ‘if’ sin parte-then.
  (unless (and beg end)
    (error "La marca no esta definida, asi que no hay ninguna region"))
  ;; • ‘condition-case’ toma tres argumentos.
  ;;    Si el primer argumento es nil, como aqui,
  ;;    la informacion del error no se almacena
  ;;    para ser utilizado por otra funcion.
  (condition-case nil

      ;; • El segundo argumento de ‘condition-case’ le dice al
      ;;    interprete Lisp que hacer cuando todo va bien.

      ;;    Empieza con una funcion ‘let’ que extrae la cadena y
      ;;    comprueba si existe. Si es asi (eso es lo que comprueba
      ;;    ‘when’), esta llama a una funcion ‘if’ que determina
      ;;    si el comando anterior fue otra llamada a ‘kill-region’;
      ;;    si lo fue, el nuevo texto se añade al texto anterior; si
      ;;    no, se llama a una funcion diferente, se llama a ‘kill-new’.

      ;;    La funcion ‘kill-append’ concatena la cadena nueva y antigua.
      ;;    La funcion ‘kill-new’ inserta el texto dentro de un nuevo
      ;;    elemento en el anillo de la muerte.

      ;;    ‘when’ es un ‘if’ sin una parte-else. El segundo ‘when’
      ;;    comprueba de nuevo si la cadena actual existe; Ademas,
      ;;    comprueba si el comando anterior fue otra llamada a
      ;;    ‘kill-region’. Si una u otra condicion es verdadera,
      ;;    entonces establece que el comando actual sea ‘kill-region’.
      (let ((string (filter-buffer-substring beg end t)))
        (when string                    ;STRING es nil si BEG = END
          ;; agrega esa cadena al anillo de la muerte, de uno forma u otra.
          (if (eq last-command 'kill-region)
              ;;    − ‘yank-handler’ es un argumento opcional de
              ;;    ‘kill-region’ que indica a las funciones ‘kill-append’
              ;;    y ‘kill-new’ como tratar las propiedades añadidas al
              ;;    texto, como ‘negrita’ o ‘cursiva’.
              (kill-append string (< end beg) yank-handler)
            (kill-new string nil yank-handler)))
        (when (or string (eq last-command 'kill-region))
          (setq this-command 'kill-region))
        nil)

    ;;  • El tercer argumento de ‘condition-case’ le dice al interprete
    ;;    que hacer con un error.
    ;;    El tercer argumento tiene una parte de condiciones y una parte cuerpo.
    ;;    Si se cumplen las condiciones (en este caso,
    ;;             si el texto o el buffer son de solo lectura)
    ;;    entonces se ejecuta el cuerpo.
    ;;    La primer parte del tercer argumento es la siguiente:
    ((buffer-read-only text-read-only) ;; la parte-if
     ;; …  la parte-then
     (copy-region-as-kill beg end)
     ;;    A continuacion, tambien como parte de la parte-then, establece
     ;;    this-command, por lo que se establece en un error
     (setq this-command 'kill-region)
     ;;    Finalmente, en la pante-then, envia un mensaje si puede copiar
     ;;    el texto en el anillo de la muerte sin señalar un error, pero
     ;;    no lo hace si no puede.
     (if kill-read-only-ok
         (progn (message "Read only text copied to kill ring") nil)
       (barf-if-buffer-read-only)
       ;; Si el buffer no es de solo lectura, el texto lo es.
       (signal 'text-read-only (list (current-buffer)))))

condition-case

Como se ha visto antes (Consulta la Seccion Generar un Mensaje de Error), cuando el intérprete de Emacs Lisp tiene problemas evaluando una expresión, te proporciona ayuda; en la jerga, esto se llama “Señalar un error”. Normalmente, el computador detiene el programa y te muestra un mensaje.

Sin embargo, algunos programas emprenden acciones complicadas. No deberian simplemente detenerse en un error. En la función kill-region, el error mas probable es que intente cortar texto que sea de solo lectura y no pueda ser eliminado. Así que la función kill-region contiene código para manejar esta circunstancia. Este código, que forma el cuerpo de la función kill-region, se encuentra dentro de una forma especial condition-case.

La plantilla para condition-case tiene este aspecto:

(condition-case
  var
  bodyform
  gestor-de-errores)

El segundo argumento, bodyform es sencillo. La forma especial condition-case hace que el intérprete Lisp evalúe el código en bodyform. Si no ocurre ningún error, la forma especial devuelve el valor del código y produce los efectos secundarios, si los hubiera.

En resumen, la parte bodyform de una expresión condition-case determina qué deberia suceder cuando todo funciona correctamente.

Sin embargo, si ocurre un error, entre otras acciones, la función genera la señal de error que define uno o más nombres de condicion de error.

El gestor de errores es el tercer argumento de condition-case. Un gestor de errores tiene dos partes, un nombre-de-condicion y un cuerpo. Si la parte nombre-de-condicion de un gestor de errores coincide con un nombre de condicion generado por un error, se ejecuta la parte del cuerpo del gestor de errores.

Como es de esperar, la parte nombre-de-condicion de un gestor de errores puede ser un unico nombre de condicion o una lista de nombres de condición.

Ademas, una expresión condition-case completa puede contener más de un gestor de errores. Cuando se produce un error, se ejecuta el primer gestor aplicable.

Por ultimo, el primer argumento de la expresión condition-case, el argumento var, en ocaciones se vincula a una variable que contiene información sobre el error. Sin embargo, si este argumento es nil, como es el caso en kill-region, esa información se descarta.

En resumen, en la función kill-region, el código condition-case funciona de la siguiente manera:

Si no hay errores, ejecuta solo este codigo
    pero, si hay errores, ejecuta este otro codigo.

Macro Lisp

La parte de la expresión condition-case se evalúa con la expectativa de que todo va bien si tiene un when. El código utiliza when para determinar si la variable string apunta a texto que existe.

Una expresión when simplemente es una conveniencia para los programadores. Es un if sin la posibilidad de una cláusula else. En tu mente, puedes reemplazar when con if y entender lo que pasa. Eso es lo que hace el intérprete Lisp.

Técnicamente hablando, when es una macro Lisp. Una macro Lisp te permite definir nuevas construcciones de control y otras caracteristicas de lenguaje. Le indica al intérprete cómo calcular otra expresión Lisp que a su vez calculara el valor. En este caso, la ‘otra expresión’ es una expresión if.

La definición de la función kill-region también tiene una macro unless; es lo contario de when. La macro unless es un if sin la cláusula then.

Para optener más informacion sobre las macros Lisp, consulta la Seccion Macros en El Manual de Referencia de Emacs Lisp. El lenguaje de programación C también proporciona macros. Estos son diferentes, pero también útiles.

Respecto a la macro when, en la expresión condition-case, cuando la cadena tiene contenido, se ejecuta otra expresión condicional. Esto es un if tanto con la parte-then como con la parte-else.

(if (eq last-command 'kill-region)
    (kill-append string (< end beg) yank-handler)
  (kill-new string nil yank-handler))

La parte-then se evalúa si el comando anterior era otra llamada a kill-region; si no, se evalúa la parte-else.

yank-handler es un argumento opcional de kill-region que indica a las funciones kill-append y kill-new como tratar con propiedades añadidas al texto, como ‘negrilla’ o ‘cursiva’.

last-command es una variable que viene con Emacs y que no hemos visto antes. Normalmente, siempre que se ejecuta una función, Emacs establece el valor de last-command al comando anterior.

En este segmento de la definición, la expresión if comprueba si el comando anterior fue kill-region. Si lo fuera,

(kill-append string (< end beg) yank-handler)

concatena una copia del texto recien cortado al texto cortado previamente en el anillo de la muerte.

copy-region-as-kill

La función copy-region-as-kill copia una región de texto de un búfer y (via kill-append o kill-new) lo guarda en el kill-ring.

Si llamas a copy-region-as-kill inmediatamente después de un comando kill-region, Emacs agregara el texto recien copiado al texto copiado previamente. Esto significa que traes el texto, lo obtienes todo, tanto de esta operacion como de la anterior. Por otra parte, si algún otro comando precede a copy-region-as-kill, la función copia el texto dentro de una entrada separada en el anillo de la muerte.

Aquí está el texto completo de la función copy-region-as-kill de la versión 22:

(defun copy-region-as-kill (beg end)
  "Guarda la region como si fuese cortada, pero no la corta.
En el modo Transient Mark, desactiva la marca.
Si ‘interprogram-cut-function’ es no-nil, tambien guarda el texto para un
sistema de ventana cortar y pegar."
  (interactive "r")
  (if (eq last-command 'kill-region)
      (kill-append (filter-buffer-substring beg end) (< end beg))
    (kill-new (filter-buffer-substring beg end)))
  (if transient-mark-mode
      (setq deactivate-mark t))
  nil)

Como de costumbre, esta función puede dividirse en las partes que la componen:

(defun copy-region-as-kill (lista-de-argumentos)
  "documentacion…"
  (interactive "r")
  cuerpo)

Los argumentos son beg y end y la función es interactiva con "r", por lo que los dos argumentos deben referirse al inicio y al final de la región. Si has estado leyendo este documento desde el principio, entender estas partes de una función se esta convirtiendo en algo rutinario.

En la documentación, los comentarios de ‘Transient Mark’ e interprogram-cut-function explican ciertos efectos secundarios.

Después de establecer una marca, un búfer siempre contiene una región. Si lo deseas puedes utilizar el modo Transient Mark para resaltar temporalmente la región. (Nadie quiere resaltar la región todo el tiempo, por lo que el modo Trasient Mark lo resalta solo en los momentos apropiados. Muchas personas desactivan el modo Transient Mark, por lo que la región nunca se resalta.)

Ademas, un sistema de ventanas permite copiar, cortar y pegar entre diferentes programas. En el sistema de ventanas X, por ejemplo, la función interprogram-cut-function es x-select-text, que funciona con el sistema de ventanas equivalente al anillo de la muerte de Emacs.

El cuerpo de la función copy-region-as-kill inicia con una cláusula if. Lo que esta cláusula hace es distinguir entre dos situaciones diferentes: si este comando se ejecuta o no inmediatamente después de un comando kill-region anterior. En el primer caso, la nueva región se concatena al texto copiado previamente. De lo contrario, se inserta al inicio del anillo de la muerte como una pieza de texto separada de la anterior.

Las dos ultimas líneas de la función impiden que la región se ilumine si el modo Transient Mark está activo.

El cuerpo de copy-region-as-kill merece ser discutido en detalle.

El cuerpo de copy-region-as-kill

copy-region-as-kill funciona de un modo parecido a la función kill-region. Ambas están escritas de manera que dos o más cortes en una fila combinan su texto en una sola entrada. Si sacas el texto del anillo de la muerte, se optiene todo en una sola pieza. Ademas, las funciones que cortan hacia adelante desde la posición actual del cursor se añaden al final del texto copiado previamente y los comandos que cortan el texto hacia atrás lo añaden al principio del texto copiado previamente. De esta manera, las palabras del texto permanecen en el orden correcto.

Al igual que kill-region, la función copy-region-as-kill hace uso de la variable last-command que mantiene un seguimiento del comando Emacs anterior.

Normalmente, siempre que se ejecuta una función, Emacs asigna el valor de this-command a la función que se esta ejecutando (que en este caso sería copy-region-as-kill). Al mismo tiempo, Emacs asigna el valor de last-command al valor anterior de this-command.

En la primer parte del cuerpo de la función copy-region-as-kill, una expresión if determina si el valor de last-command es kill-region. Si es así, se evalua la parte-then de la expresión if; utiliza la función kill-append para concatenar el texto copiado en la llamada a esta función con el texto que ya esta en el primer elemento (el car del anillo de la muerte. Por otro lado, si el valor de last-command no es kill-region, entonces la función copy-region-as-kill asigna un nuevo elemento al anillo de la muerte usando la función kill-new.

La expresión if se lee de la siguiente manera; utiliza eq:

(if (eq last-command 'kill-region)
    ;; parte-then
    (kill-append  (filter-buffer-substring beg end) (< end beg))
  ;; parte-else
  (kill-new  (filter-buffer-substring beg end)))

(La función filter-buffer-substring devuelve una subcadena filtrada del búfer, si existe. Opcionalmente––los argumentos no están aquí, por lo que tampoco se hace––la función puede borrar el texto inicial o devolver el texto sin sus propiedades; esta función es un reemplazo para la antigua función buffer-substring, que existia antes de que se implementaran las propiedades del texto.)

La función eq comprueba si su primer argumento es el mismo objeto Lisp que su segundo argumento. La función eq es similar a la función equal que se utiliza para probar la igualdad, pero difiere en que determina si dos representaciones son realmente el mismo objeto dentro de la computadora, pero con nombres diferentes. equal determina si la estructura y el contenido de dos expresiones son iguales.

Si el comando anterior fue kill-region, entonces el intérprete Emacs Lisp llama a la función kill-append

La función kill-append

La función kill-new se ve así:

(defun kill-append (string before-p &optional yank-handler)
  "Inserta STRING al fin del ultimo corte en el anillo de la muerte.
Si BEFORE-P no es nil, anexa STRING al corte.
… "
  (let* ((cur (car kill-ring)))
    (kill-new (if before-p (concat string cur) (concat cur string))
              (or (= (length cur) 0)
                  (equal yank-handler
                         (get-text-property 0 'yank-handler cur)))
              yank-handler)))

La función kill-append es bastante sencilla. Utiliza la función kill-new, que discutiremos con más detalle en un momento.

(Ademas, la función proporciona un argumento opcional llamado yank-handler; cuando se invoca, este argumento le dice a la función cómo tratar con la propiedades añadidas al texto, como ‘negrita’ o ‘cursiva’.)

Tiene una función let* para asignar el valor del primer elemento del anillo de la muerte a cur. (No se por qué la función no utiliza let en su lugar; solo un valor se asigna en la expresión. ¿Tal vez este es un bug que no produce problemas?

Examinemos el condicional proporciana uno de los dos argumentos de kill-new. Utiliza concat para concatenar el nuevo texto al car del anillo de la muerte. Si agrega el texto antes o despues depende del resultado de la expresión if:

(if before-p                            ; parte-if
    (concat string cur)                 ; parte-then
  (concat cur string))                  ; parte-else

Si la región a cortar está antes de la región que se cortó en el último comando, entonces debería ser puesta antes que el material guardado en el corte anterior; y, a la inversa, si el texto que se corto esta despues del que se acaba de cortar, debe añadirse después del texto anterior. La expresión if depende del predicado before-p para decidir si el texto recien guardado debe colocarse antes o después del texto anterior.

El símbolo before-p es el nombre de uno de los argumentos para kill-append. Cuando se evalúa la función kill-append, se asocia al valor devuelto evaluando el argumento actual. En este caso, esta es la expresión (< end beg). Esta expresión no determina directamente si el texto cortado en este comando se localiza antes o después del texto cortado del último comando; lo que hace es determinar si el valor de la variable end es menor que el valor de la variable beg. Si es así, significa que problamemente el usuario se dirige hacia el principio del búfer. Ademas, el resultado de evaluar la expresión del predicado. (< end beg), será verdadero y el texto se concatena antes del texto anterior. Por otro lado, si el valor de la variable end es mayor que el valor del la variable beg, el texto se agregara después del texto anterior.

Cuando el texto recien guardado se antepone, la cadena con el nuevo texto se concatena antes que el texto anterior:

(concat string cur)

Pero si el texto será añadido, será concatenado después del texto anterior:

(concat cur string))

Para entender cómo funciona esto, primero necesitamos revisar la función concat. La función concat enlaza o une dos cadenas de texto. El resultado es una cadena. Por ejemplo:

> (concat "abc" "def")
"abcdef"
> (concat "nuevo "
        (car '("primer elemento" "segundo elemento")))
"nuevo primer elemento"
> (concat (car
        '("primer elemento" "segundo elemento")) " modificado")
"primer elemento modificado"

Ahora podemos dar sentido a kill-append: modifica el contenido del anillo de la muerte. El anillo de la muerte es una lista, en la que cada elemento almacena texto. La función kill-append usa la función kill-new que a su vez utiliza la función setcar.

La función kill-new

La función kill-new se ve asi:

(defun kill-new (string &optional replace yank-handler)
  "Hace que STRING sea el último corte en el anillo de la muerte.
Establece ‘kill-ring-yank-pointer’ para apuntar a el.

Si ‘interprogram-cut-function’ es no nulo, aplícarlo a STRING.
El segundo argumento opcional REPLACE no-nulo significa que STRING
 reemplazará el frente del kill ring, en lugar de agregarse a la lista.
…"
  (if (> (length string) 0)
      (if yank-handler
          (put-text-property 0 (length string)
                             'yank-handler yank-handler string))
    (if yank-handler
        (signal 'args-out-of-range
                (list string "yank-handler specified for empty string"))))
  (if (fboundp 'menu-bar-update-yank-menu)
      (menu-bar-update-yank-menu string (and replace (car kill-ring))))
  (if (and replace kill-ring)
      (setcar kill-ring string)
    (push string kill-ring)
    (if (> (length kill-ring) kill-ring-max)
        (setcdr (nthcdr (1- kill-ring-max) kill-ring) nil)))
  (setq kill-ring-yank-pointer kill-ring)
  (if interprogram-cut-function
      (funcall interprogram-cut-function string (not replace))))

(Observa que la función no es interactiva.)

Como de costumbre, podemos ver esta función en partes.

La definición de función tiene un argumento opcional yank-handler, que cuando se invoca le dice a la función cómo manejar las propiedades añadidas al texto, tales como ‘negrita’ o ‘cursiva’. Nos Saltaremos eso.

La primer línea de la documentación tiene sentido:

Hace que STRING sea el último corte en el anillo de la muerte.

Vamos a saltarnos el resto de la documentación por el momento.

También, vamos a saltar la expresión if inicial y las líneas de código en menu-bar-update-yank-menu. Las explicaremos mas tarde.

Las líneas críticas son estas:

  (if (and replace kill-ring)
      ;; entonces
      (setcar kill-ring string)
    ;; de otra forma
  (push string kill-ring)
    (setq kill-ring (cons string kill-ring))
    (if (> (length kill-ring) kill-ring-max)
        ;; evita desbordar el anillo de la muerte
        (setcdr (nthcdr (1- kill-ring-max) kill-ring) nil)))
  (setq kill-ring-yank-pointer kill-ring)
  (if interprogram-cut-function
      (funcall interprogram-cut-function string (not replace))))

La prueba condicional es (and replace kill-ring). Esto será verdadero cuando se cumplan dos condiciones: el anillo de la muerte tiene algo en el, y la variable replace es verdadera.

Cuando la función kill-append establece replace como verdadero y cuando el anillo de la muerte tiene al menos un elemento en el, se ejecuta la expresión setcar.

(setcar kill-ring string)

La función setcar realmente cambia el primer elemento de la lista kill-ring al valor de string. Eso reemplaza el primer elemento.

Por otro lado, si el anillo de la muerte está vacío, o replace es falso, se ejecuta la parte-else de la condición:

(push string kill-ring)

push pone su primer argumento dentro del segundo. Es similar a la mas antigua

(setq kill-ring (cons string kill-ring))

o la mas reciente

(add-to-list kill-ring string)

Cuando es falso, la expresión primero construye una nueva versión del anillo de la muerte, añadiendo string al anillo como un nuevo elemento (que es lo que hace push). Entonces ejecuta una segunda clausula if. Este segundo if impide que el anillo de la muerte se haga demaciado largo.

Veamos estas dos expresiones en orden.

La línea push de la parte-else asigna el nuevo valor del anillo de la muerte a los resultados de agregar la cadena que esta siendo cortada al viejo anillo de la muerte.

Podemos ver cómo funciona esto con un ejemplo.

Primero,

(setq lista-de-ejemplo '("aqui hay una clausula" "otra clausula"))

Después de evaluar esta expresión con C-x C-e, se puede evaluar lista-de-ejemplo y ver que devuelve:

> lista-de-ejemplo
("aquí hay una claúsula" "otra claúsula")

Ahora, podemos agregar un nuevo elemento a esta lista evaluando la siguiente expresión:

(push "una tercera cláusula" lista-de-ejemplo)

Cuando evaluamos lista-de-ejemplo, encontramos que su valor es:

> lista-de-ejemplo
("una tercera claúsula" "aquí hay una claúsula" "otra claúsula")

Asi pues, la tercer claúsula se añade a la lista con push.

Ahora la segunda parte de la claúsula if. Esta expresión evita que el anillo de la muerte crezca demasiado:

(if (> (length kill-ring) kill-ring-max)
    (setcdr (nthcdr (1- kill-ring-max) kill-ring) nil))

El código comprueba si la longitud del anillo de la muerte es mayor al tamaño máximo permitido. Este es el valor de kill-ring-max (que es 60, por defecto). Si el tamaño del anillo de la muerte es demasiado largo, entonces este código establece el último elemento del anillo de la muerte a nil. Hace esto usando dos funciones, nthcdr y setcdr.

Vimos setcdr anteriormente (ver Seccion setcdr). Esto asigna el cdr de una lista, asi como setcar asigna el car de una lista. En este caso, sin embargo, setcdr no estará configurando el cdr del anillo de la muerte completo; se usa la función nthcdr para asignar el cdr del último elemento del anillo de la muerte––esto significa que puesto que el cdr del penultimo elemento es el ultimo elmento del anillo de la muerte, establecera el último elemento del anillo de la muerte.

La función nthcdr funciona tomando repetidamente el cdr de una lista––toma el cdr del cdr del cdr …. hace esto N veces y devuelve los resultados. (Consulta la Seccion nthcdr.)

De este modo, si teniamos una lista de cuatro elementos que supuestamente debia tener tres elementos de longitud, podriamos asignar el cdr del elemento siguiente al último a nil, y asi acortar la lista. (Si se asigna el último elemento en algún otro valor distinto a nil, que se podría hacer, entonces no se habría acortado la lista. Consulta la Sección setcdr.)

Puedes ver el acortamiento evaluando las siguientes tres expresiones. Primero asigna el valor de arboles a (arce roble pino abedul) luego establece el cdr de su segundo cdr y despues encuentra el valor de arboles.

> (setq arboles '(arce roble pino abedul))
(arce roble pino abedul)
> (setcdr (nthcdr 2 arboles) nil)
nil
> arboles
(arce roble pino)

(El valor devuelto por la expresión setcdr es nil, ya que en esto se establece el cdr.)

Para repetir, en kill-new, la función nthcdr toma el cdr un número de veces, que es uno menos que el tamaño máximo permitido del anillo de la muerte y setcdr establece el cdr de este elemento (que será el resto de los elementos en el anillo de la muerte) en nil. Esto evita que el anillo de la muerte se alargue demaciado.

La penultima expresión en la función kill-new es

(setq kill-ring-yank-pointer kill-ring)

kill-ring-yank-pointer es una variable global que se establece para ser el kill-ring.

Apesar de llamar a kill-ring-yank-pointer un ‘puntero’, es una variable al igual que el anillo de la muerte. Sin embargo, el nombre ha sido elegido para ayudar a los humanos a entender cómo se usa la variable.

Ahora, volviendo a una expresión anterior en el cuerpo de la función:

  (if (fboundp 'menu-bar-update-yank-menu)
       (menu-bar-update-yank-menu string (and replace (car kill-ring))))

Empieza con una expresión if

En este caso, la expresión prueba primero si menu-bar-update-yank-menu existe como una función, y si es así, la llama. La función fboundp devuelve verdadero si el símbolo que se prueba tiene una definición de función que ‘no es nula’. Si el símbolo de la definición de función fuera nulo, recibiríamos un mensaje de error, como lo hicimos cuando se crearon errores intencionadamente (Consulta la Seccion Generar un mensaje de error).

La parte-entonces contiene una expresión cuyo primer elemento es la función and.

La forma especial and evalúa cada uno de sus argumentos hasta que uno de los argumentos devuelve un valor de nil, en cuyo caso la expresión and devuelve nil; sin embargo, si ninguno de los argumentos devuelve una valor nil, se devuelve el resultado de la evaluación del último argumento. (Puesto que tal valor no es nil, en Emacs Lisp se considera verdadero.) En otras palabras, una expresión and devuelve un valor verdadero solo si todos sus argumentos son verdaderos. (Consulta la Seccion Revisar el segundo búfer relacionado.)

La expresión determina si el segundo argumento menu-bar-update-yank-menu es verdadero o no.

menu-bar-update-yank-menu es una de la funciones que permiten utilizar el menu ‘Seleccionar y Pegar’ en el elemento Editar de la barra de menu; usando un ratón, puedes ver las distintas piezas de texto que se han guardado y seleccionar una pieza para pegar.

La última expresión en la función kill-new añade la nueva cadena recien copiada a cualquier facilidad existente para copiar y pegar texto entre diferentes programas que se ejecutan en un sistema de ventanas. En el Sistema de Ventanas X, por ejemplo, la función x-select-text toma la cadena y la almacena en la memoria operada por X. Puede pegar la cadena en otro programa, como Xterm.

La expresión se ve asi:

  (if interprogram-cut-function
      (funcall interprogram-cut-function string (not replace))))

Si interprogram-cut-function existe, entonces Emacs ejecuta funcall, que a su vez llama a su primer argumento como una función y le pasa el resto de argumentos. (Por cierto, hasta donde puedo ver, esta expresión if podría reemplazarse por una expresión and similar a la de la primer parte de la función.)

No vamos a discutir mas sobre sistemas de ventanas y otros programas, sino simplemente indicar que este es un mecanismo que le permite a GNU Emacs trabajar bien y fácilmente con otros programas.

Este código para colocar texto en el anillo de la muerte, ya sea concatenandolo con un elemento existente o como un nuevo, nos brinda la habilidad para traer texto que ha sido cortado del búfer––los comandos de corte (yank). Sin embargo, antes de discutir los comandos de corte, es mejor aprender cómo se implementan las listas en un ordenador. Esto pondra de manifiesto misterios como el uso del término ‘puntero’. Pero antes de eso, nos desviaremos a C.

Disgresión dentro de C

La función copy-region-as-kill (ver Seccion copy-region-as-kill) utiliza la función filter-buffer-substring, que a su vez utiliza la función delete-and-extract-region. Eso elimina el contenido de una región y no se puede recuperar.

A diferencia del otro código discutido aquí, la función delete-and-extract-region no está escrita en Emacs Lisp; está escrita en C y es una de las primitivas del sistema GNU Emacs. Puesto que es muy simple, hare una breve digresión de Lisp y para describirla aquí.

Al igual que muchas de las otras primitivas Emacs, delete-and-extract-region se escribe como una instancia de una macro C, una macro es una plantilla de codigo. La macro completa tiene el siguiente aspecto:

DEFUN ("delete-and-extract-region", Fdelete_and_extract_region,
       Sdelete_and_extract_region, 2, 2, 0,
       doc: /* Borra el texto entre START y END y lo devuelve.  */)
       (Lisp_Object start, Lisp_Object end)
{
  validate_region (&start, &end);
  if (XINT (start) == XINT (end))
    return empty_unibyte_string;
  return del_range_1 (XINT (start), XINT (end), 1, 1);
}

Sin entrar en los detalles del proceso de escritura de la macro, permitame señalar que esta macro comienza con la palabra DEFUN. Se eligio la palabra DEFUN porque el código tiene el mismo propósito que defun en Lisp. (La macro C DEFUN se define en emacs/src/lisp.h.)

La palabra DEFUN tiene siete partes dentro de los paréntesis:

En una macro C, los parámetros formales vienen a continuacion, con una declaracion de que tipo de objeto son, seguido por lo que podría llamarse el ‘cuerpo’ de la macro. Para delete-and-extract-region el ‘cuerpo’ se compone de las cuatro líneas siguientes:

validate_region (&start, &end);
if (XINT (start) == XINT (end))
  return build_string ("");
return del_range_1 (XINT (start), XINT (end), 1, 1);

La función validate_region comprueba si los valores pasados como el principio y fin de la región son del tipo apropiado y estan dentro del rango. Si las posiciones de inicio y fin son las mismas, entonces devuelve una cadena vacía.

La función del_range_1 borra el texto. Es una función compleja que no examinaremos. Actualiza el búfer y hace otras cosas. Sin embargo, vale la pena mirar los dos argumentos pasados a del_range. Estos son XINT (start) y XINT (end).

Por lo que respecta al lenguaje C, start y end son dos enteros que marcan el principio y el fin de la región a eliminar10.

En las primeras versiones de Emacs, estos dos números tenian 32 bits de longitud, pero el código está lentamente siendo generalizado para manejar otras longitudes. Tres de los bits disponibles son usados para especificar el tipo de información; los bits restantes se utilizan como ‘contenido’.

XINT es una macro C que extrae el numero relevante desde una colección mas larga de bits; los otros tres bits se descartan.

El comando en delete-and-extract-region se ve asi:

del_range_1 (XINT (start), XINT (end), 1, 1);

Borra la región entre la posición inicial, start, y la posición final, end.

Desde el punto de vista de la persona que escribe Lisp, Emacs es muy simple; pero oculta en el fondo mucha complejidad para hacer que todo funcione.

Inicializando una variable con defvar

La función copy-region-as-kill esta escrita en Emacs Lisp. Dos funciones dentro de ella, kill-append y kill-new, copian una región en un búfer y la guardan en una variable llamada kill-ring. Esta sección describe cómo se crea e inicializa la variable kill-ring usando la forma especial defvar.

(De nuevo, se señala que el término kill-ring es un mal nombre. El texto que se corta del búfer puede ser traido de vuelta; no es un anillo de cadaveres, sino un anillo de texto resucitable.)

En Emacs Lisp, una variable como kill-ring se crea y se le da un valor inicial usando la forma especial defvar. El nombre proviene de “definir variable”.

La forma especial defvar es similar a setq en la que se establece el valor de una variable. Se diferencia de setq en dos formas: primero solo establece el valor de la variable si la variable no tiene ya un valor. Si la variable ya tiene un valor, defvar no sobreescribe el valor existente. Segundo, defvar tiene una cadena de documentación.

(Otra forma especial, defcustom, está diseñada para variables que las personas personalizan. Tiene más funcionalidades que defvar. (Consulta la Sección Especificar variables usando defcustom.)

Se puede ver el valor actual de una variable, cualquier variable, usando la función describe-variable, que normalmente se invoca escribiendo C-h v. Si presionas C-h v y luego escribes kill-ring (seguido por RET), veras lo que hay actualmente en tu anillo de la muerte––¡puede ser bastante grande! Por el contrario, si no has estado haciendo nada en esta sesión en Emacs, excepto leer este documento, es posible no tener nada en el. Ademas, se verá la documentación de kill-ring:

Documentación:
Lista de secuencias de texto muerto.
Ya que el anillo de la muerte se supone que interactua bien con
el copia-y-pega que ofrecen los sistemas de ventanas, el uso de
esta variable debería interactuar bien con las funciones
‘interprogram-cut-function’ e ‘interprogram-paste-function’.
Las funciones ‘kill-new’, ‘kill-append’, y ‘current-kill’ deben
implementar esta interacción; es posible que desee utilizarlas
en lugar de manipular el anillo de la muerte directamente.

El anillo de la muerte es definido por un defvar del siguiente modo:

(defvar kill-ring nil
  "Lista de secuencia de texto muerto.
…")

En esta definición de variable, a la variable se le da un valor inicial de nil, lo que tiene sentido, ya que si no has guardado nada, no quieres nada de vuelta al dar un comando yank. La cadena de documentación se escribe igual que la cadena de documentación de un defun. Al igual que con la cadena de documentacion del defun, la primera linea de la documentación deberia ser una frase completa, ya que algunos comandos, como apropos, imprimen solo la primer línea de documentación. Las líneas sucesivas no deben indentarse; de lo contrario, se veran extrañas cuando se use C-h v (describe-variable).

defvar y un asterisco

En el pasado, Emacs usaba la forma especial defvar tanto para variables internas que se esperaba que un usuario cambiara como para las que no se esperaban cambios de parte del usuario. Aunque todavía se puede usar defvar para variables personalizadas, por favor, utiliza defcustom en su lugar, ya que esa forma especial proporciona una ruta a los comando de personalización. (Consulta la Seccion Especificar variables usando defcustom.)

Cuando se especifica una variable utilizando la forma especial defvar, se podría distinguir una variable que un usuario puede querer cambiar de las demas escribiendo, ‘*’, en la primera columna de su cadena de documentación. Por ejemplo:

(defvar shell-command-default-error-buffer nil
  "*Nombre de buffer para ‘shell-command’ … salida de error.
… ")

Podrías (y todavía puedes) usar el comando set-variable para cambiar temporalmente el valor de shell-command-default-error-buffer. Sin embargo, las opciones configuradas usando set-variable solo se establecen durante la duración de tu sesión actual. Los nuevos valores no se guardan entre sesiones. Cada vez que Emacs inicia, lee el valor original, a menos que cambie el valor dentro de su fichero .emacs, ya sea configurándolo manualmente o utilizando customize. Consulta la Seccion Tu Fichero .emacs.

Para mí, el mayor uso del comando set-variable es sugerir variables que se podría querer establecer en mi fichero .emacs. Ahora hay más de 700 variables, demasiadas para recordarlas fácilmente. Afortunadamente, se puede presionar TAB después de llamar al comando M-x set-variable para ver la lista de variables. (Consulta la Seccion Examinando y Configurando Variables en El Manual de GNU Emacs.)

Repaso

Aquí hay un breve resumen de algunas funciones introducidas recientemente.

car, cdr

car devuelve el primer elemento de una lista; cdr devuelve el segundo y los siguientes elementos de una lista.

Por ejemplo:

> (car '(1 2 3 4 5 6 7))
1
> (cdr '(1 2 3 4 5 6 7))
(2 3 4 5 6 7)
cons

cons construye una lista enlazando su primer argumento a su segundo argumento.

Por ejemplo:

> (cons 1 '(2 3 4))
(1 2 3 4)
funcall

funcall evalúa su primer argumento como una función. Pasa los argumentos restantes a su primer argumento.

nthcdr

Devuelve el resultado de tomar el cdr ‘n’ veces en una lista. El nᵗʰ (enesimo) cdr. El ‘resto del resto’, por asi decirlo.

Por ejemplo:

> (nthcdr 3 '(1 2 3 4 5 6 7))
(4 5 6 7)
setcar, setcdr

setcar cambia el primer elemento de una lista; setcdr cambia el segundo y los siguientes elementos de una lista.

Por ejemplo:

> (setq triple '(1 2 3))
> (setcar triple '37)
> triple
(37 2 3)
> (setcdr triple '("foo" "bar"))
> triple
(37 "foo" "bar")
progn

Evalúa cada argumento en secuencia y luego devuelve el valor del último.

Por ejemplo:

> (progn 1 2 3 4)
4
save-restriction

Graba cualquier reduccion (narrowing) que esté en efecto en el búfer actual, si la hay, y restablece esa reduccion despues de evaluar los argumentos.

search-forward

Busca una cadena y, si se encuentra, mueve el punto. De manera similar, para una expresión regular utiliza re-search-forward. (Consulta la Sección Búsqueda de Expresiones Regulares, para obtener una explicación de los patrones y busquedas de expresiones regulares.)

search-forward y re-search-forward toman cuatro argumentos:

  1. La cadena o expresión regular a buscar.

  2. Opcionalmente, el límite de la búsqueda.

  3. Opcionalmente, que hacer si la búsqueda falla, devuelve nil o un mensaje de error.

  4. Opcionalmente, cuántas veces repetir la búsqueda; si es negativa, la búsqueda va hacia atrás.

kill-region, delete-and-extract-region, copy-region-as-kill

kill-region corta el texto entre el punto y la marca del búfer y almacena ese texto en el anillo de la muerte, para que puedas recuperarlo "tirando" de el.

copy-region-as-kill copia el texto entre punto y marca dentro del anillo de la muerte. La función no corta ni elimina el texto del búfer.

delete-and-extract-region elimina el texto entre el punto y la marca del búfer. No puede recuperarse. (Este no es un comando interactivo.)

Ejercicios de Busqueda

Cómo se implementan las listas

En Lisp, los átomos se registran de manera directa, si la implementación no es directa en la práctica, es, sin embargo, directa en teoría. El átomo ‘rosa’, por ejemplo, se graba como las cuatro letras contiguas ‘r’, ‘o’, ‘s’, ‘a’. Una lista, por otro lado, se guarda de manera diferente. El mecanismo es igualmente simple, pero toma un momento acostumbrarse a la idea. Una lista se guarda usando una serie de pares de punteros. En las serie, el primer puntero de cada par apunta a un átomo o a otra lista, y el segundo puntero de cada par apunta al siguiente par, o al símbolo nil, que marca el final de la lista.

Un puntero por sí mismo es simplemente la dirección electrónica de lo que se apunta. Por lo tanto, una lista se guarda como una serie de direcciones electrónicas.

Por ejemplo, la lista (rosa violeta tulipan) tiene tres elementos, ‘rosa’, ‘violeta’, y ‘tulipan’. En el ordenador, la dirección electrónica de ‘rosa’ se registra en un segmento de memoria del ordenador junto con la dirección que da la dirección electrónica de donde se encuentra el átomo ‘violeta’; y esta dirección (la que dice donde se localiza ‘violeta’) se guarda junto con una dirección que dice donde se localiza la dirección para el átomo ‘tulipan’.

Esto parece más complicado de lo que es y es más fácil visto en un diagrama:

 ___ ___      ___ ___      ___ ___
|___|___|--> |___|___|--> |___|___|--> nil
  |            |            |
  |            |            |
  ---> rosa    ---> violeta ---> tulipan

En el diagrama, cada caja representa una palabra de la memoria del ordenador que contiene un objeto Lisp, normalmente en forma de una dirección de memoria. Las cajas, es decir, las direcciones, están en pares. Cada flecha apunta a la dirección de un átomo u otro par de direcciones. La primer caja es la dirección electrónica de ‘rosa’ y la flecha apunta a ‘rosa’; la segunda caja es la dirección del siguiente par de cajas, la primera parte de las cuales que es la dirección de ‘violeta’ y la segunda parte es la dirección del siguiente par. La última caja apunta al símbolo nil, que marca el fin de la lista.

Cuando una variable se establece en una lista con una función como setq, almacena la dirección de la primera caja en la variable. Asi pues, la evaluación de la expresión

(setq ramo '(rosa violeta tulipan))

crea una situación como esta:

ramo
  |
  |     ___ ___      ___ ___      ___ ___
   --> |___|___|--> |___|___|--> |___|___|--> nil
         |            |            |
         |            |            |
          --> rosa     --> violeta   --> tulipan

En este ejemplo, el símbolo ramo contiene la dirección del primer par de cajas.

Esta misma lista puede ser ilustrada en un tipo diferente de notación de cajas como este:

ramo
  |
  |    --------------       ----------------       --------------------
  |   | car   | cdr  |     | car     | cdr  |     | car         | cdr  |
   -->| rosa  |   o------->| violeta |   o------->| tulipan     | nil  |
      |       |      |     |         |      |     |             |      |
       --------------       ----------------       --------------------

(Los símbolos consisten en más de un par de direcciones, pero la estructura de un símbolo esta formado por direcciones. En efecto, el símbolo ramo consiste de un grupo de cajas-de-direcciones, una de las cuales es la dirección de la palabra impresa ‘ramo’, la segunda es la dirección de una definición de función adjunta al símbolo, si la hubiera, una tercera parte la cual es la dirección del primer par de cajas-de-direccion para la lista (rosa violeta tulipan), y así sucesivamente. Aquí mostraremos que la tercera caja de direcciónes del símbolo apunta al primer par de cajas-de-direccion de la lista.)

Si un símbolo se asigna al cdr de una lista, la lista en sí no cambia; el símbolo simplemente tiene una dirección mas abajo en la lista. (En la jerga, car y cdr son ‘no destructivos’.) De este modo, la evaluacion de la siguiente expresión

(setq flores (cdr ramo))

produce esto:

ramo        flores
  |              |
  |     ___ ___  |     ___ ___      ___ ___
   --> |   |   |  --> |   |   |    |   |   |
       |___|___|----> |___|___|--> |___|___|--> nil
         |              |            |
         |              |            |
          --> rosa       --> violeta  --> tulipan

El valor de flores es (violeta tulipan), es decir, el símbolo flores contiene la dirección del par de cajas-de-direcciones, la primera de las cuales contiene la dirección de violeta, y la segunda contiene la dirección de tulipan.

Un par de cajas-de-direcciones se denomina una cons cell o par de puntos. Consulta la Seccion Cons Cell y Tipos de Lista en El Manual de Referencia de Emacs Lisp, y Notación de Par Punteado en El Manual de Referencia de GNU Emacs Lisp, para más información acerca las cons cells y los pares punteados.

La función cons añade un nuevo par de direcciones al frente de una serie de direcciones como la que se muestra a continuacion. Por ejemplo, evaluar la expresión

(setq ramo (cons 'lila ramo))

produce:

ramo                       flores
  |                             |
  |     ___ ___        ___ ___  |     ___ ___       ___ ___
   --> |   |   |      |   |   |  --> |   |   |     |   |   |
       |___|___|----> |___|___|----> |___|___|---->|___|___|--> nil
         |              |              |             |
         |              |              |             |
          --> lila      --> rosa       --> violeta    --> tulipan

Sin embargo, esto no cambia el valor del símbolo flores, como puedes ver evaluando lo siguiente,

(eq (cdr (cdr ramo)) flores)

que devuelve t por verdadero.

Hasta que se reinicia, flores todavía tiene el valor (violeta tulipan); es decir, tiene la dirección de la cons cell cuya primer dirección es violeta. Ademas, esto no altera ninguna de las cons cell preexistentes; todas ellas todavía están allí.

De este modo, en Lisp, para obtener el cdr de una lista, solo tienes que obtener la dirección de la siguiente cons cell de la serie; al obtener el car de una lista, se obtiene la dirección del primer elemento de la lista; al agregar (cons) un nuevo elemento en una lista, se añade una nueva cons cell al frente de la lista. ¡Esto es todo lo que hay! ¡La estructura subyacente de Lisp es brillantemente simple!

¿Y a que se refiere la última dirección en una serie de cons cell? Es la direccion de la lista vacía, nil.

En resumen, cuando una variable Lisp se establece a un valor, se proporciona la dirección de la lista a la que se refiere la variable.

Símbolos como una caja con cajones

En una sección anterior, sugeri que se podría imaginar un símbolo como una caja con cajones. La definición de función se coloca en un cajón, el valor en otro, etcetera. Lo que se pone en el cajón que contiene el valor puede cambiarse sin afectar el contenido del cajón que contiene la definicion de función, y viceversa.

En realidad, lo que se pone en la caja es la dirección del valor o definición de función. Es como si encontraras un viejo cofre en el ático, y en uno de sus compartimentos hallaras un mapa que te indica donde yace el tesoro enterrado.

(Además de su nombre, la definición del símbolo, y un valor de variable, un símbolo tiene un ‘cajón’ para una lista de propiedades que puede utilizar para registrar otra información. Las listas de propiedades no se discuten aquí; ver la seccion Listas de Propiedades en El Manual de Referencia de Emacs Lisp.)

Aquí hay una representación imaginaria:

    Caja de Cajones            Contenidos de los Cajones

    __   o0O0o   __
  /                 \
 ---------------------
|    direcciones al   |            [asignar a]
| nombre del simbolo  |            ramo
|                     |
+---------------------+
|  direcciones a la   |
|   definición del    |            [nada]
|    simbolo          |
+---------------------+
|    direcciones al   |            [asignar a]
|   valor de variable |            (rosa violeta tulipan)
|                     |
+---------------------+
|    direcciones a la |
|lista de propiedades |            [no descrito aquí]
|                     |
+---------------------+
|/                   \|

Ejercicio

Asignar flores a violeta y tulipan. Asignar dos flores más en esta lista y asignarla a mas-flores. Asignar el car de flores a un pez. ¿Qué lista contiene ahora mas-flores?

Traer de regreso el texto

Siempre que cortas texto de un búfer con un comando ‘kill’, puede traerse de vuelta con un comando ‘yank’. El texto cortado del búfer es puesto en el anillo de la muerte y los comandos yank insertan el contenido apropiado del anillo de la muerte en un búfer (no necesariamente el búfer original).

Un simple comando C-y (yank) inserta el primer elemento del anillo de la muerte en el búfer actual. Si el comando C-y es seguido inmediatamente por M-y, el primer elemento se reemplaza por el segundo elemento. Los sucesivos comandos M-y reemplazan el segundo elemento con el tercer, cuarto, o quinto elemento, y así sucesivamente. Cuando se alcanza el último elemento del anillo de la muerte, se reemplaza por el primer elemento y el ciclo se repite. (Por la tanto, el anillo de la muerte se llama un ‘anillo’ en lugar de solo una ‘lista’. Sin embargo, la estructura de datos real que contiene el texto es una lista. Consulta la Seccion Apéndice B: Manejando el anillo de la muerte, para los detalles de cómo se maneja la lista como un anillo.)

Resumen del anillo de la muerte

El anillo de la muerte es una lista de cadenas de texto. Asi es como luce:

("algún texto" "un texto diferente" "aún más texto")

Si este fuera el contenido de mi anillo de la muerte y pulsara C-y, la cadena de caracteres que dice ‘algún texto’ seria insertada en este búfer donde se encuentra mi cursor.

El comando yank también se utiliza para duplicar texto copiándolo. El texto copiado no se corta del búfer, sino que se coloca una copia del mismo en el anillo de la muerte y se inserta trayendolo de vuelta.

Se utilizan tres funciones para devolver el texto del anillo de la muerte: yank, que normalmente se asocia a C-y; yank-pop, que normalmente se asocia a M-y; y rotate-yank-pointer, que se utiliza por las otras dos funciones.

Estas funciones se refieren al anillo de la muerte a través de una variable llamada kill-ring-yank-pointer. De hecho, el codigo de inserción para ambas funciones yank y yank-pop es:

(insert (car kill-ring-yank-pointer))

(Bueno, ya no más. En GNU Emacs 22, la función ha sido reemplazada por insert-for-yank que llama repetidamente a insert-for-yank-1 para cada segmento a yank-handler. A su vez, insert-for-yank-1 elimina las propiedades del texto insertado de acuerdo a yank-excluded-properties. De otro modo, seria como insert. Nosotros pegaremos con un insert plano puesto que es mas fácil de comprender.)

Para empezar a comprender cómo funcionan yank y yank-pop, primero hay que mirar la variable kill-ring-yank-pointer.

La variable kill-ring-yank-pointer

kill-ring-yank-pointer es una variable, de la misma forma que kill-ring es una variable. Apunta a alguna cosa al estar ligada al valor de lo que apunta, como cualquier otra variable Lisp.

De este modo, si el valor del anillo de la muerte es:

("algún texto" "un texto diferente" "aún más texto")

y kill-ring-yank-pointer apunta a la segunda oracion, el valor de kill-ring-yank-pointer es:

("un texto diferente" "aún más texto")

Como se explico en el capítulo anterior (Consulta la Seccion Cómo se implementan las listas), el ordenador no guarda dos copias diferentes del texto apuntadas tanto por kill-ring como por kill-ring-yank-pointer. Las palabras “un texto diferente” y “aún más texto” no están duplicadas. En su lugar, las dos variables apuntan a las mismas piezas de texto. Aquí hay un diagrama:

kill-ring     kill-ring-yank-pointer
    |               |
    |      ___ ___  |     ___ ___      ___ ___
     ---> |   |   |  --> |   |   |    |   |   |
          |___|___|----> |___|___|--> |___|___|--> nil
            |              |            |
            |              |            |
            |              |             --> "aún más texto"
            |              |
            |               --> "un texto diferente"
            |
             --> "algún texto"

Tanto la variable kill-ring como kill-ring-yank-pointer son punteros. Pero el anillo de la muerte en sí suele describirse como si fuera realmente de lo que esta compuesto. Se habla de kill-ring como si fuera la lista en lugar de ser un puntero a la lista. Por el contrario, se dice que kill-ring-yank-pointer apunta a una lista.

Estas dos formas de hablar sobre la misma cosa suenan confusas al principio pero tienen sentido tras reflexionar. El anillo de la muerte se piensa generalmente como la estructura completa de datos que contiene la información de lo que se ha cortado reciéntemente de los búfers de Emacs. Por otra parte, kill-ring-yank-pointer, sirve para indicar––es decir, para ‘apuntar a’––esa parte del anillo de la muerte de la cual se inserta el primer elemento (el car).

Ejercicios con yank y nthcdr

Bucles y recursión

Emacs Lisp tiene dos formas principales para hacer que una expresión, o una serie de expresiones, se evaluen repetidamente: una usa un bucle while, y la otra recursión.

La repetición puede ser muy valiosa. Por ejemplo, para avanzar cuatro oraciones, solo necesitas escribir un programa que avance una oracion y luego repetir el proceso cuatro veces. Ya que un ordenador no se aburre ni se cansa, tal acción repetitiva no tiene los efectos nocivos (como un monton de errores) que puede tener en los humanos.

La mayoria de personas escriben sus funciones de Emacs Lisp usando bucles while y sus parientes; pero se puede usar la recursión, que provee un una manera muy poderosa de pensar para resolver problemas11.

while

La forma especial while prueba si el valor devuelto tras evaluar su primer argumento es verdadero o falso. Esto es parecido a lo que el intérprete Lisp hace con un if; sin embargo, lo que el interprete hace despues es diferente.

En una expresión while, si el valor devuelto al evaluar el primer argumento es falso, el intérprete Lisp omite el resto de la expresión (el cuerpo de la expresión) y no la evalúa. Sin embargo, si el valor es verdadero, el intérprete evalúa el cuerpo de la expresión y luego prueba nuevamente si el primer argumento de while es verdadero o falso. Si el valor devuelto al evaluar el primer argumento vuelve a ser verdadero, el intérprete Lisp vuelve a evaluar el cuerpo de la expresión.

La plantilla para una expresión while luce así:

(while prueba-verdadero-o-falso
  cuerpo)

Siempre que la evaluacion de prueba-verdadero-o-falso en la expresion while devuelva un valor verdadero, el cuerpo sera evaluado repetidamente. Este proceso se llama bucle porque el intérprete Lisp repite lo mismo una y otra vez, como un avión haciendo un bucle. Cuando el resultado de evaluar prueba-verdadero-o-falso es falso, el intérprete no evalúa el resto de la expresión while y ‘sale del bucle’.

Claramente, si el valor devuelto evaluando el primer argumento de while es siempre cierto, el cuerpo siguiente será evaluado una y otra vez … y otra vez … por siempre. Por el contrario, si el valor devuelto nunca es verdadero, las expresiones en el cuerpo nunca serán evaluadas. El arte de escribir un bucle while consiste en elegir un mecanismo tal que la prueba-verdadero-o-falso devuelva verdadero solo el número de veces que requiere evaluar las expresiones subsiguientes, y luego hacer que la prueba devuelva falso.

El valor devuelto al evaluar un while es el valor de prueba-verdadero-o-falso. Una consecuencia interesante de esto es que un bucle while que se evalúa sin errores devolverá nil o falso independientemente de si ha hecho el bucle 1 o 100 veces o ninguna. ¿Una expresión while que se evalúa con exito nunca devuelve un valor verdadero! Lo que esto significa es que while siempre se evalua por sus efectos secundarios, es decir, las consecuencias de evaluar las expresiones dentro del cuerpo del bucle. Esto tiene sentido. No es el mero acto del bucle lo que se desea, sino las consecuencias de lo que ocurre cuando las expresiones en el bucle se evaluan repetidamente.

Un bucle while y una lista

Un forma común para controlar un bucle while es probar si una lista tiene algun elemento. Si es asi, el bucle se repite; pero si no, la repetición finaliza. Como esta es una tecnica importante, crearemos un ejemplo breve para ilustrarla.

Un manera sencilla de comprobar si una lista tiene elementos es evaluar la lista: si no tiene elementos, se trata de una lista vacía y devuelve la lista vacía, (), que es un sinónimo de nil o falso. Por otro lado, una lista con elementos devolverá estos elementos cuando se evalúe. Ya que Emacs Lisp considera como verdadero cualquier valor que no sea nil, una lista que devuelve elementos probara verdadero en un bucle while.

Por ejemplo, se puede asignar la variable lista-vacia a nil para evaluar la siguiente expresión setq:

(setq lista-vacia ())

Después de evaluar la expresión setq, se puede evaluar la variable lista-vacia de la manera habitual, colocando el cursor después del símbolo y escribiendo C-x C-e; aparecerá nil en tu área de eco:

lista-vacia

Por otro lado, si se asigna una variable como una lista con elementos, la lista aparecerá cuando se evalúe la variable, como se puede ver al evaluar las dos expresiones siguientes:

(setq animales '(gacela jirafa leon tigre))

animales

De este modo, para crear un bucle while que pruebe si hay algun elemento en la lista animales, la primera parte del bucle se escribira así:

(while animales
       

Cuando while prueba su primer argumento, la evaluacion de la variable animales devuelve una lista. Mientras la lista tenga elementos, el while considera que el resultado de la prueba es verdadero; pero cuando la lista esta vacía, se considera que el resultado de la prueba es falso.

Para prevenir que el bucle while se ejecute para siempre, es necesario proporcionar algún mecanismo para vaciar la lista. Una técnica usada con frecuencia es tener una de las expresiones en el cuerpo de while que asigne el valor del cdr de la lista a la lista. Cada vez que se evalua la función cdr, la lista se va reduciendo, hasta que finalmente solo queda la lista vacía. En este punto, la prueba del bucle while devolverá falso, y al mismo tiempo los argumentos ya no se evaluarán.

Por ejemplo, la lista de animales asociada a la variable animales puede asignarse a el cdr de la lista original con la siguiente expresión:

(setq animales (cdr animales))

Si has evaluado las expresiones anteriores y luego evaluas esta expresión, veras aparecer (jirafa leon tigre) en el área de eco. Si evalúas la expresión de nuevo, aparecera (leon tigre). Si la evalúas de nuevo una y otra vez, veras (tigre) y despues la lista vacía, mostrada como nil.

Una plantilla para un bucle while usa la función cdr repetidamente para hacer que la prueba-verdadero-o-falso eventualmente se evalue a falso como se muesta a continuacion:

(while prueba-si-la-lista-esta-vacia
  cuerpo
  establece-lista-al-cdr-de-la-lista)

Esto prueba y uso de cdr puede colocarse en una funcion a la que se pase una lista e imprima cada elemento de esta en una línea propia.

Un ejemplo: imprimir-elementos-de-la-lista

La función imprimir-elementos-de-la-lista ilustra un bucle while con una lista.

La función requiere varias líneas para su salida. Si estás leyendo esto en una instancia reciente de GNU Emacs, puedes evaluarlo de la forma habitual.

Si estas usando una versión antigua de Emacs, es necesario copiar las expresiones necesarias en el búfer *scratch* y evaluarlas allí. Esto se debe a que el área de eco solo tenía una línea en las versiones antiguas.

Puedes copiar las expresiones marcando el principio de la región con C-SPC (set-mark-command), moviendo el cursor al final de la región y luego copiar la región usando M-w (kill-ring-save, que llama a copy-region-as-kill y proporciona la realimentación visual). En el búfer *scratch*, puedes copiar las expresiones con C-y (yank).

Después de copiar las expresiones al búfer *scratch*, evalúa cada expresión en orden. Asegúrate de evaluar la última expresión, (imprimir-elementos-de-la-lista animales), presionando C-u C-x C-e, es decir, pasando un argumento a eval-last-sexp. Esto hara que el resultado de la evaluación se imprima en el búfer *scratch* en lugar de imprimirse en el área de eco. (De lo contrario, veras algo como esto en tu área de eco: ^Jgacela^J^Jjirafa^J^Jleon^J^Jtigre^Jnil, en la que cada ‘^J’ seria una ‘nueva línea’.)

En una instancia de GNU Emacs reciente, podras evaluar estas expresiones directamente, y el área de eco crecerá para mostrar los resultados.

(setq animales '(gacela jirafa leon tigre))

(defun imprimir-elementos-de-la-lista (list)
  "Imprime cada elemento de LIST en una línea."
  (while list
    (print (car list))
    (setq list (cdr list))))

(imprimir-elementos-de-la-lista animales)

Cuando evalúes las tres expresiones en secuencia, verás esto:

gacela

jirafa

leon

tigre
nil

Cada elemento de la lista se imprime en una línea propia (que es lo que hace la función print) y luego se imprime el valor devuelto por la función. Como la última expresión de la función es el bucle while, y como los bucles while siempre devuelven nil, se imprime un nil después del último elemento de la lista.

Un bucle con un contador incremental

Un bucle no es útil a menos que pare cuando debe. Ademas de controlar un bucle con una lista, una forma comun de detener un bucle es escribir el primer argumento como una prueba que devuelve falso cuando el número correcto de repeticiones esta completo. Esto significa que el bucle debe tener un contador––una expresión que cuenta cuántas veces el bucle se repite a sí mismo.

La prueba para un bucle con un contador incremental puede ser una expresión como (< contador numero-deseado) que devuelve t para verdadero si el valor de contador es menor que el numero-deseado de repeticiones y nil para falso si el valor de contador es igual o mayor al numero-deseado. La expresión que incrementa el contador puede ser un simple setq como (setq contador (1+ contador)), donde 1+ es una función nativa de Emacs Lisp que añade 1 a su argumento. (La expresión (1+ contador) tiene el mismo resultado que (+ contador 1), pero es mas facil de leer para un humano.)

La plantilla para un bucle while controlado por un contador que se incrementa se ve asi:

asignar-un-valor-inicial-al-contador
(while (< contador numero-deseado)         ; prueba-verdadero-o-falso
  cuerpo
  (setq contador (1+ contador)))           ; incremento

Ten en cuenta que se necesita asignar el valor inicial de contador; por lo general se asigna en 1.

Ejemplo con contador incremental

Supón que estás jugando en la playa y decides crear un triángulo de guijarros, poniendo uno en la primera fila, dos en la segunda fila, tres en la tercera fila y así sucesivamente:

   •
  • •
 • • •
• • • •

(Hace 2500 años, Pitágoras y otros desarrollaron los principios de la teoría de números al considerar preguntas como esta.)

Supón que quieres saber cuántos guijarros necesitarás para hacer un triángulo con 7 filas

Claramente, lo que necesitas hacer es sumar los números de 1 a 7. Hay dos maneras de hacer esto; se puede comenzar con el numero más pequeño, uno, y sumar la lista en secuencia, 1, 2, 3, 4 y así sucesivamente; o empezar con el número más grande y sumar la lista hacia abajo: 7, 6, 5, 4 y así sucesivamente. Debido a que ambos mecanismos ilustran formas comunes de escribir el bucle while, crearemos dos ejemplos, uno contando hacia arriba y el otro contando hacia abajo. En este primer ejemplo, empezaremos con 1 y sumaremos 2, 3, 4, etc.

Si quieres sumar toda una lista de números, el camino más fácil de hacerlo es sumar todos los números a la vez. Sin embargo, si no se sabe de antemano cuántos números tendrá la lista, o si se requiere estar preparado para una lista muy larga, entonces se necesita diseñar la suma de manera que lo que haga sea repetir un proceso simple muchas veces en vez de hacer un proceso más complejo una solo vez.

Por ejemplo, en lugar de sumar todos los guijaros a la vez, lo que se puede hacer es sumar el número de guijarros en la primera fila, 1, al número de la segunda fila, 2, y entonces añadir el total de estas dos filas a la tercera fila, 3. Luego se puede sumar el número en la cuarta fila, 4, al total de las primeras tres filas; y así sucesivamente.

La característica crítica del proceso es que cada acción repetitiva sea simple. En este caso, en cada paso se suman solo dos números, el número de guijarros en la fila y el total ya encontrado. Este proceso de sumar dos números se repite una y otra vez hasta que la última fila ha sido añadida al total de todas las filas precedentes. En un bucle más complejo la acción repetitiva podría no ser tan sencilla, pero será mas simple que hacerlo todo de una sola vez.

Las partes de la definición de función

El análisis anterior nos da los huesos de nuestra definición de función: primero, necesitaremos una variable que podemos llamar total que será el número total de guijarros. Este será el valor devuelto por la función.

En segundo lugar, sabemos que la función requerirá un argumento: este argumento será el número total de filas del triángulo. Puede llamarse numero-de-filas.

Finalmente, necesitamos una variable para usar como contador. Podriamos llamar a esta variable contador, pero un mejor nombre es numero-de-fila. Esto se debe a que lo que hace el contador en esta función es contar filas, y un programa debería escribirse para ser lo mas comprensible posible.

Cuando el intérprete Lisp comienza a evaluar las expresiones de la función, el valor de total debe ponerse a cero, ya que no hemos sumado nada al mismo. Luego la función debe sumar el número de guijarros en la primera fila al total, y luego sumar el número de guijarros en la segunda al total, y luego sumar el número de guijarros en la tercera fila al total, y así sucesivamente, hasta que no queden más filas por sumar.

Tanto total como numero-de-fila se usan solo dentro de la función, por lo que pueden ser declarados como variables locales con let dando sus valores iniciales. Evidentemente, el valor inicial para total sera 0. El valor inicial de numero-de-fila sera 1, ya que comenzaremos con la primer fila. Esto significa que la declaracion let se vera asi:

(let ((total 0)
      (numero-de-fila 1))
  cuerpo)

Después de declarar y asignar las variables internas, podemos comenzar el bucle while. La expresión que sirve como prueba deberia devolver un valor de t para verdadero siempre que el numero-de-fila sea menor o igual al numero-de-filas. (Si la prueba devuelve verdadero solo si el número de fila es menor que el número de filas en el triángulo, la última fila nunca se sumara al total; por lo tanto, el número de fila tiene que ser menor o igual al número de filas.)

Lisp proporciona la función <= que devuelve verdadero si el valor de su primer argumento es menor o igual al valor de su segundo argumento y falso de lo contrario. Así que la expresión que el while evaluará como su prueba deveria verse asi:

(<= numero-de-fila numero-de-filas)

El número total de guijarros se puede encontrar sumando repetidamente el número de guijarros en una fila al total ya encontrado. Puesto que el número de guijarros en la fila es igual al número de la fila, el total puede encontrarse añadiendo el número de filas al total. (Claramente, en una situación más compleja, el número de guijarros en la fila podría estar relacionado con el número de la fila de una manera más complicada; si este fuera el caso, el número de la fila sería reemplazado por la expresión apropiada.)

(setq total (+ total numero-de-fila))

Lo que esto hace es asignar el nuevo valor de total para que sea igual a la suma del número de guijarros en la fila al total anterior.

Después de establecer el valor de total, se deben establecer las condiciones para la siguiente repetición del bucle, si hay alguna. Esto se hace incrementando el valor de la variable numero-de-fila, que sirve como contador. Después de incrementar la variable numero-de-fila, la prueba-verdodero-o-falso al principio del bucle while prueba si su valor aun es menor o igual al valor del numero-de-filas y, si lo es, suma el nuevo valor de la variable numero-de-fila al total de la repetición previa.

La función nativa 1+ en Emacs Lisp añade 1 a un número, por lo que la variable numero-de-fila puede incrementarse con esta expresión:

(setq numero-de-fila (1+ numero-de-fila))
Juntando la definición de función

Hemos creado las partes para la definición de la función; ahora necesitamos juntarlas.

Primero, el contenido de la expresión while:

(while (<= numero-de-fila numero-de-filas)    ; prueva-verdadero-o-falso
  (setq total (+ total numero-de-fila))
  (setq numero-de-fila (1+ numero-de-fila)))  ; incremento

Junto con la expresión let de la varlist, casi completan el cuerpo de la definición de función. Sin embargo, se requiere un ultimo elemento, cuya necesidad es algo sutil.

El toque final es colocar la variable total en una línea por sí misma después de la expresión while. De lo contrario, el valor devuelto por la función completa sera el valor de la última expresión en evaluarse dentro del cuerpo del let, y este es el valor devuelto por el while que es siempre nil.

Esto puede no ser evidente a primera vista. Casi parece como si la expresión de incremento fuera la última expresión de la función completa. Pero esa expresión es parte del cuerpo del while; es el último elemento de la lista que empieza con el símbolo while. Ademas, todo el bucle while es una lista dentro del cuerpo del let.

En el esquema, la función tendra este aspecto:

(defun nombre-de-la-funcion (lista-de-argumentos)
  "documentacion…"
  (let (varlist)
    (while (prueba-verdadero-o-falso)
      cuerpo-de-while )
     ))                    ; aqui necesita la expresión final.

El resultado de evaluar el let es lo que va a ser devuelto por defun ya que let no está embebido dentro de ninguna lista que la contenga, excepto por el defun como un todo. Sin embargo, si el while es el último elemento de la expresión let, la función siempre devolverá nil. ¡Esto no es lo que queremos! En vez de eso, queremos el valor de la variable total. Esto se devuelve simplemente colocando el símbolo como el último elemento de la lista que comienza con let. Se evalúa después de evaluar los elementos precedentes, lo que significa que se evaluó después de que se le ha asignado el valor correcto para el total.

Puede ser mas fácil ver esto imprimiendo la lista iniciada por let en una línea. Este formato hace evidente que las expresiones varlist y while son el segundo y tercer elementos de la lista iniciado por let, y total es el último elemento:

(let (varlist) (while (prueba-verdadero-o-falso) cuerpo-de-while ) total)

Poniendo todo junto, la definición de la función triangulo se ve asi:

(defun triangulo (numero-de-filas)  ; Versión con
                                    ;   contador de incremento.
  "Suma el número de guijarros en un triángulo.
La primera fila tiene un guijarro, la segunda fila dos guijarros,
la tercera fila tres guijarros, y así sucesivamente.
El argumento es NUMERO-DE-FILAS."
  (let ((total 0)
        (numero-de-fila 1))
    (while (<= numero-de-fila numero-de-filas)
      (setq total (+ total numero-de-fila))
      (setq numero-de-fila (1+ numero-de-fila)))
    total))

Después de haber instalado triangulo al evaluar la función, puedes probarla. Aquí hay dos ejemplos:

(triangulo 4)

(triangulo 7)

La suma de los primeros cuatro números es 10 y la suma de los primeros siete números es 28.

Bucle con un contador decreciente

Otra manera común de escribir un bucle while es con una prueba que determine si un contador es mayor que cero. Mientras el contador sea mayor que cero, el bucle se repite. Pero cuando el contador es igual o menor que cero, el bucle se detiene. Para que esto funcione, el contador tiene que empezar por encima de cero y luego hacerse mas y mas pequeño mediante una forma que se evalue repetidamente.

La prueba será una expresión como (> contador 0) que devuelve t para verdadero si el valor del contador es mayor que cero, y nil para falso si el valor del contador es igual a o menor que cero. La expresión que hace que el número menor sea cada vez mas pequeño puede ser un simple setq como (setq contador (1- contador), donde 1- es una función nativa en Emacs Lisp que resta 1 de su argumento.

La plantilla para un bucle while decreciente se ve así:

(while (> contador 0)                   ; prueba-verdadero-o-falso
  cuerpo
  (setq contador (1- contador)))        ; decremento
Ejemplo con contador decreciente

Para ilustrar un bucle con un contador decreciente, reescribiremos la función triangulo para que el contador disminuya a cero.

Esto es lo inverso de la versión anterior de la función. En este caso, para saber cuantos guijarros se necesitan para crear un triángulo con 3 filas, se suma el número de guijarros en la tercera fila, 3, al numero en la fila anterior, 2, y luego se suman el total de esas dos filas a la fila que lo precede, que es 1.

Del mismo modo, para encontrar el número de guijarros en un triángulo con 7 filas, se suma el número de guijarros en la septima fila, 7, al número en la fila anterior, que es 6, y luego se suma el total de estas dos filas a la fila que las precede, que es 5, y así sucesivamente. Como en el ejemplo anterior, cada suma solo implica sumar dos números, el total de las filas ya sumadas y el número de guijarros en la fila que se suma al total. Este proceso de sumar dos números se repite una y otra vez hasta que no haya más guijarros que agregar.

Sabemos con cuántos guijarros empezar: el número de guijarros en la última fila es igual al número de filas. Si el triángulo tiene siete filas, el número de guijarros en la última fila es 7. Del mismo modo, sabemos cuántos guijarros hay en la fila anterior: uno menos que el número en la fila actual.

Las partes de la definición de función

Empezamos con tres variables: el número total de filas en el triángulo; el número de guijarros en una fila; y el número total de guijarros, que es lo que queremos calcular. Estas variables pueden llamarse numero-de-filas, numero-de-guijarros-en-fila, y total, respectivamente.

Tanto total como numero-de-guijarros-en-fila se usan solo dentro de la función y se declaran con let. El valor inicial de total deberia ser cero. Sin embargo, el valor inicial de numero-de-guijarros-en-fila deberia ser igual al número de filas en el triángulo, ya que la suma comenzara con la fila más larga.

Esto significa que el principio de la expresión let se verá así:

(let ((total 0)
      (numero-de-guijarros-en-fila numero-de-filas))
  cuerpo)

El número total de guijarros se puede encontrar sumando repetidamente el número de guijarros en una fila al total ya encontrado, es decir, evaluando repetidamente la siguiente expresión:

(setq total (+ total numero-de-guijarros-en-fila))

Después de sumar numero-de-guijarros-en-fila al total, el numero-de-guijarros-en-fila debe decrecer por uno, ya que la siguiente vez que el bucle se repita, la fila anterior se sumara al total.

El número de guijarros en una fila anterior es uno menos que el número de guijarros en la fila actual, por lo que se utilizara la función nativa 1- de Emacs para calcular el número de guijarros de la fila anterior. Esto se puede hacer con la siguiente expresión:

(setq numero-de-guijarros-en-fila
      (1- numero-de-guijarros-en-fila))

Finalmente, sabemos que el bucle while deberia detenerse cuando no halla guijarros en una fila. Así que la prueba en el bucle while es simple:

(while (> numero-de-guijarros-en-fila 0)
Juntando la definición de función

Podemos juntar estas expresiones para crear una definición de función que funcione. Sin embargo, al examinarlas, encontraremos que una de las variables locales ¡es innecesaria!

La definición de función tiene este aspeto:

;;; Primer version decreciente.
(defun triangulo (numero-de-filas)
  "Suma el número de guijarros en un triángulo."
  (let ((total 0)
        (numero-de-guijarros-en-fila numero-de-filas))
    (while (> numero-de-guijarros-en-fila 0)
      (setq total (+ total numero-de-guijarros-en-fila))
      (setq numero-de-guijarros-en-fila
            (1- numero-de-guijarros-en-fila)))
    total))

Como esta escrita, esta función funciona.

Sin embargo, no se necesita numero-de-guijarros-en-fila.

Cuando se evalua la función triangulo, el símbolo numero-de-filas se vinculara a un número, dandole un valor inicial. Ese número puede ser modificado en el cuerpo de la función como si fuera una variable local, sin temor a que tal cambio afecte el valor de la variable fuera de la función. Esta es una característica muy útil de Lisp; significa que la variable numero-de-filas se puede utilizar en cualquier lugar de la función donde se utilice numero-de-guijarros-en-fila.

Aquí hay una segunda versión de la función escrita un poco más limpiamente:

(defun triangulo (numero)                ; Segunda versión.
  "Devuelve la suma de números desde el 1 hasta e incluyendo a NUMERO."
  (let ((total 0))
    (while (> numero 0)
      (setq total (+ total numero))
      (setq numero (1- numero)))
    total))

En resumen, un bucle while escrito apropiadamente constará de tres partes:

  1. Una prueba que devuelva falso después de que el bucle se ha repetido el número de veces correcto.

  2. Una expresión cuya evaluación repetida devuelva el valor deseado.

  3. Una expresión para cambiar el valor pasado a la prueba-verdadero-o-falso para que esta devuelva falso después de que el bucle se haya repetido el numero de veces correcto.

Ahorra tu tiempo: dolist y dotimes

Además de while, tanto dolist como dotimes permiten hacer bucles. Algunas veces estos son mas rápidos de escribir que el bucle while equivalente. Ambas son macros Lisp. (Consulta la Seccion Macros en El Manual de Referencia GNU Emacs Lisp.)

dolist funciona como un bucle while con ‘cdrs que acortan la lista’: dolist acorta automáticamente la lista con cada bucle––toma el cdr de la lista––y liga el car de cada versión mas corta al primero de sus argumentos.

dotimes repite el bucle un número específico de veces: tu especificas el número.

La macro dolist

Supón, por ejemplo, que quieres invertir una lista, para que “primero”, “segundo”, “tercero” se convierta en “tercero”, “segundo”, “primero”.

En la práctica, usarías la función reverse, como aqui:

(setq animales '(gacela jirafa leon tigre))

(reverse animales)

A continuacion se muestra como se podría invertir la lista usando un bucle while:

(setq animales '(gacela jirafa leon tigre))

(defun invertir-lista-con-while (lista)
  "Usando while, invierte el orden de LISTA."
  (let (valor)  ; asegura que la lista comienza vacía
    (while lista
      (setq valor (cons (car lista) valor))
      (setq lista (cdr lista)))
    valor))

(invertir-lista-con-while animales)

Y aquí se ve cómo puedes usar la macro dolist:

(setq animales '(gacela jirafa leon tigre))

(defun invertir-lista-con-dolist (lista)
  "Usando dolist, invierte el orden de LISTA."
  (let (valor)  ; asegura que la lista empieza vacía
    (dolist (elemento lista valor)
      (setq valor (cons elemento valor)))))

(invertir-lista-con-dolist animales)

Puedes colocar el cursor después del parentesis de cierre de cada expresión y pulsar C-x C-e; en cualquier caso, deberias ver

(tigre leon jirafa gacela)

en el área de eco.

Para este ejemplo, la función reverse obviamente es la mejor opcion. El bucle while es como nuestro primer ejemplo (Consulta la Seccion Un bucle while y una lista). while comprueba si la lista tiene elementos; si es así, construye una nueva lista añadiendo el primer elemento de la lista a la lista existente (que en la primer iteración del bucle es nil). Puesto que el segundo elemento se asigna delante del primero, y el tercero delante del segundo, la lista se invierte.

En la expresión que usa un bucle while, la expresión (setq lista (cdr lista)) acorta la lista, por lo que el bucle while termina deteniendose. Además, se proporciona la expresión cons con un nuevo primer elemento creando una nueva lista en cada repetición del bucle.

La expresión dolist hace lo mismo que la expresión while, excepto que la macro dolist hace algo del trabajo que tienes que hacer cuando se escribe una expresión while.

Como un bucle while, dolist hace un bucle. Lo que es diferente es que (dolist) acorta automáticamente la lista con cada repeticion––con ‘cdrs que reducen la lista’ por su cuenta––y asocia automáticamente el car de cada versión acortada de la lista al primero de sus argumentos.

En el ejemplo, el car de cada versión reducida de la lista se vincula al símbolo ‘elemento’, la lista en sí se llama ‘lista’, y el valor devuelto se llama ‘valor’. El resto de la expresión dolist es el cuerpo.

La expresión dolist asocia el car de cada versión reducida de la lista a elemento y luego evalúa el cuerpo de la expresión y repite el bucle. El resultado es devuelto en valor.

La macro dotimes

La macro dotimes es similar a dolist, excepto que el bucle se repite un número específico de veces.

Al primer argumento de dotimes se le asignan los números 0, 1, 2 y así sucesivamente cada iteracion, y se devuelve el valor del tercer argumento. Se necesita proveer el valor del segundo argumento, que es el numero de veces que la macro se repite.

Por ejemplo, lo siguiente asocia al primer argumento numero los números de 0 en adelante, pero no incluye, el número 3, y luego construye una lista de los tres números. (El primer número es 0, el segundo es 1, y el tercero es 2; esto hace un total de tres, comenzando con cero como el primer número.)

> (let (valor)      ; de otro modo, "valor" es una variable vacia
  (dotimes (numero 3 valor)
    (setq valor (cons numero valor))))
(2 1 0)

dotimes devuelve valor, así que la forma de usar dotimes es operar sobre alguna expresión numero un numero de veces y luego devolver el resultado, como una lista o un átomo.

Aquí hay un ejemplo de un defun que usa dotimes para sumar el número de guijarros en un triángulo.

(defun triangulo-utilizando-dotimes (numero-de-filas)
  "Usando dotimes, suma el número de guijarros en un triángulo."
(let ((total 0))  ; de otro modo total es una variable vacía
  (dotimes (numero numero-de-filas total)
    (setq total (+ total (1+ numero))))))

(triangulo-utilizando-dotimes 4)

Recursividad

Una función recursiva contiene código que indica al intérprete Lisp que llame a un programa que se ejecuta exactamente como el, pero con argumentos ligeramente diferentes. El código funciona exactamente igual porque tiene el mismo nombre. Sin embargo, aunque el programa tenga el mismo nombre, no es la misma entidad. Eso diferente. En la jerga, se dice es una ‘instancia’ diferente.

Eventualmente, si el programa se escribe correctamente, los ‘argumentos ligeramente diferentes’ llegan a ser suficientemente diferentes de los primeros argumentos para que la instancia final se detenga.

Construyendo robots: Extendiendo la metáfora

Algunas veces es útil pensar en un programa en ejecución como un robot que hace un trabajo. Al hacer su trabajo, una función recursiva llama a un segundo robot para que le ayude. El segundo robot es idéntico al primero en todos los sentidos, excepto que el segundo robot ayuda al primero y ha recibido argumentos diferentes al primero.

En una función recursiva, el segundo robot puede llamar a un tercero; y el tercero puede llamar a un cuarto, y así sucesivamente. Cada uno de ellos es una entidad diferente; pero todos son clones.

Dado que cada robot tiene instrucciones ligeramente diferentes––los argumentos difieren de un robot a otro––el último robot deberia saber cuando detenerse.

Expandamos la metáfora en la que un programa de ordenador es un robot.

Una definición de función proporciona los planos de un robot. Cuando se instala una definición de función, que es, cuando se evalúa una forma especial defun, se instala el equipo necesario para construir robots. Es como si estuvieras en una fábrica, construyendo una línea de montaje. Los robots con el mismo nombre se construyen segun los mismos planos. Así que tienen, por asi decirlo, el mismo ‘número de modelo’, pero un ‘número de serie’ diferente.

A menudo decimos que una función recursiva ‘se llama así misma’. Lo que queremos decir es que las instrucciones en una función recursiva hacen que el intérprete Lisp ejecute una función diferente que tiene el mismo nombre y hace el mismo trabajo que la primera, pero con argumentos diferentes.

Es importante que los argumentos difieran de una instancia a la siguiente; de otro modo, el proceso nunca se detendra.

Las partes de una definición recursiva

Una función recursiva típicamente contiene una expresión condicional que tiene tres partes:

  1. Una prueva-verdadero-o-falso que determina si la función se vuelve a llamar, aquí se llamara prueba-hazlo-de-nuevo.

  2. El nombre de la función. Cuando este nombre se llama, se crea una nueva instancia de la función––un nuevo robot, por asi decirlo––y se le dice qué hacer.

  3. Una expresión que devuelve un valor diferente cada vez que se llama a la función, aquí se llamara expresion-del-siguiente-paso. Consecuentemente, el argumento (o argumentos) pasados a la nueva instancia de la función será diferente del argumento (o argumentos) pasados a la instancia previa. Esto hace que la expresión condicional, la prueba-hazlo-de-nuevo, devuelva falso después del número correcto de repeticiones.

Las funciones recursivas pueden ser mucho más simples que cualquier otro tipo de funcion. De hecho, cuando las personas comienzan a usarlas, con frecuencia parecen tan misteriosamente simples que resultan incomprensibles. Al igual que montar en bicicleta, leer la definicion de una función recursiva requiere una cierta habilidad, es dificil al principio, pero después parece facil.

Hay varios patrones recursivos diferentes. Un patrón muy simple se ve asi:

(defun nombre-de-funcion-recursiva (lista-de-argumentos)
  "documentation…"
  (if prueba-hazlo-de-nuevo
    cuerpo
    (nombre-de-funcion-recursiva expresion-del-siguiente-paso)))

Cada vez que se evalua una función recursiva, se crea una nueva instancia y se le dice qué hacer. Los argumentos indican a la instancia qué hacer.

Un argumento es ligado al valor de la expresion-del-siguiente-paso. Cada instancia se ejecuta con un valor de la expresion-del-siguiente-paso diferente.

El valor en la expresion-del-siguiente-paso se utiliza en la prueba-hazlo-de-nuevo.

El valor devuelto por la expresion-del-siguiente-paso se pasa a la nueva instancia de la función, que lo evalúa para determinar si continua o se detiene. La expresion-del-siguiente-paso está diseñada para que la prueba-hazlo-de-nuevo devuelva falso cuando la función ya no deba repetirse.

La prueba-hazlo-de-nuevo a veces se denomina condición de parada, ya que detiene las repeticiones cuando la prueba falla.

Recursividad con una lista

El ejemplo de un bucle while que imprimia los elementos de una lista de números puede escribirse recursivamente. Aquí está el código, incluyendo una expresión para asignar el valor de la variable animales a una lista.

Si estás leyendo esto dentro de Emacs, puedes evaluar la expresión directamente. De lo contrario, debes copiar el ejemplo al búfer *scratch* y evalúar cada expresión alli. Utiliza C-u C-x C-e para evaluar la expresión (imprimir-elementos-recursivamente animales) de manera que se imprima el resultado en el búfer; de otro modo el intérprete Lisp intentara comprimir los resultados en la unica línea del área de eco.

También, coloca el cursor inmediatamente después del último paréntesis de cierre de la función imprimir-elementos-recursivamente, antes del comentario. De lo contrario, el intérprete Lisp intentará evaluar el comentario.

(setq animales '(gacela jirafa leon tigre))

(defun imprimir-elementos-recursivamente (lista)
  "Imprime cada elemento de LISTA en una línea propia.
Usa recursión."
  (when lista                               ; prueba-hazlo-de-nuevo
        (print (car lista))                 ; cuerpo
        (imprimir-elementos-recursivamente  ; llamada recursiva
         (cdr lista))))                     ; expresion-del-siguiente-paso

(imprimir-elementos-recursivamente animales)

La función imprimir-elementos-recursivamente primero prueba si hay algun contenido en la lista; si lo hay, la función imprime el primer elemento de la lista, el car de la lista. Entonces la función se ‘invoca a sí misma’, pero se da como argumento, no la lista completa, sino el segundo y subsiguientes elementos de esta, es decir, el cdr de la lista.

Dicho de otro modo, si la lista no está vacía, la función invoca otra instancia de código que es similar al código inicial, pero es un hilo de ejecución diferente, con argumentos diferentes o los de la primera instancia.

Dicho de otra manera mas, si la lista no está vacía, el primer robot ensambla un segundo robot y le dice qué hacer; el segundo robot es un individuo diferente del primero, pero es del mismo modelo.

Cuando se produce la segunda evaluación, se evalua la expresión when y si es verdadera, imprime el primer elemento de la lista que recibe como argumento (que es el segundo elemento de la lista original). A continuacion la función ‘se llama a sí misma’ (la segunda vez) con el cdr del cdr de la lista original.

Ten en cuenta que aunque decimos que la función ‘se llama a sí misma’, lo que queremos decir es que el intérprete Lisp ensambla e instruye una nueva instancia del programa. La nueva instancia es un clon del primero, pero es un individuo separado.

Cada vez que la función ‘se invoca a sí misma’, se invoca con una versión mas corta de la lista original. Crea una nueva instancia que funciona en una lista mas corta.

Eventualmente, la función se invoca a sí misma con una lista vacía. Crea una nueva instancia cuyo argumento es nil. La expresión condicional prueba el valor de lista. Ya que el valor de la lista es nil, la expresión when prueba falso por lo que la parte-then no se evalua. La función como un todo entonces devuelve nil.

Cuando se evalúa la expresión (imprimir-elementos-recursivamente animales) en el búfer *scratch*, se ve este resultado:

gacela

jirafa

leon

tigre
nil

Recursión en lugar de un contador

La función triangulo descrita en una sección anterior tambien se puede escribir recursivamente. Se vera así:

(defun triangulo-recursivo (numero)
  "Devuelve la suma de números desde el 1 hasta e incluyendo a NUMERO.
Usa recursion."
  (if (= numero 1)                    ; prueba-hazlo-de-nuevo
      1                               ; parte-then
    (+ numero                         ; parte-else
       (triangulo-recursivo           ; llamada recursiva
        (1- numero)))))               ; expresion-del-siguiente-paso

(triangulo-recursivo 7)

Puedes instalar esta función evaluandola y luego probarla evaluando (triangulo-recursivo 7). (Recuerda colocar el cursor inmediatamente después del último paréntesis de la definición de la función, antes del comentario.) La función se evalúa a 28.

Para comprender cómo funciona esta función, hay que considerar qué ocurre en varios casos cuando la función pasa 1, 2, 3, o 4 como el valor de su argumento.

Primero, ¿que sucede si el valor del argumento es 1?

La función tiene una expresión if después de la cadena de documentación. Prueba si el valor de numero es igual a 1; si es así, Emacs evalúa la parte-then de la expresión if, que devuelve el número 1 como el valor de la función. (Un triángulo con una fila tiene un guijarro dentro.)

Supón, sin embargo, que el valor del argumento es 2. En este caso, Emacs evalúa la parte-else de la expresión if.

La parte-else consiste en una suma, la llamada recursiva a triangulo-recursivo y una acción de decremento; y se ve así:

(+ numero (triangulo-recursivo (1- numero)))

Cuando Emacs evalúa esta expresión, la expresión interna se evalua primero; luego las otras partes en secuencia. Aquí están los pasos en detalle:

Un argumento de 3 o 4

Supón que triangulo-recursivo se llama con un argumento de 3.

El valor devuelto por la función en su conjunto será 6.

Ahora que sabemos qué ocurrirá cuando se llame a triangulo-recursivo con un argumento de 3, es evidente lo que ocurrirá si se llama con un argumento de 4:

En la llamada recursiva, la evaluación de

(triangulo-recursivo (1- 4))

devolvera el valor de evaluar

(triangulo-recursivo 3)

que es 6 y este valor se sumara a 4 mediante la suma en la tercera línea.

El valor devuelto por la función en su conjunto será 10.

Cada vez que se evalua triangulo-recursivo, se evalua una versión de sí misma––una instancia diferente en sí––con un argumento mas pequeño, hasta que el argumento es lo suficientemente pequeño para que no se evalue a si misma.

Ten en cuenta que este particular diseño de una función recursiva requiere que las operaciones se pospongan.

Antes de que (triangulo-recursivo 7) pueda calcular su respuesta, debe llamar a (triangulo-recursivo 6); y antes de que (triangulo-recursivo 6) pueda calcular su respuesta debe llamar a (triangulo-recursivo 5); y así sucesivamente. Es decir, el cálculo que hace (triangulo-recursivo 7) debe ser pospuesto hasta que (triangulo-recursivo 6) haga su cálculo; y (triangulo-recursivo 6) debe posponerse hasta que (triangulo-recursivo 5) se complete; y así sucesivamente.

Si se piensa que cada una de estas instancias de triangulo-recursivo son robots diferentes, el primer robot debe esperar al segundo para completar su trabajo, que debe esperar hasta que el tercero se complete, y así sucesivamente.

Hay una forma de evitar este tipo de espera, que se discutirá en la Seccion Recursividad sin aplazamiento.

Ejemplo de recursión usando cond

La versión de triangulo-recursivo descrita anteriormente se escribió con la forma especial if. También se puede escribir usando otra forma especial llamada cond. El nombre de la forma especial cond es una abreviación de la palabra ‘conditional’ (condicional).

Aunque la forma especial cond no se usa tan a menudo en las fuentes de Emacs como if, se usa con bastante frecuencia para justificar su explicacion.

La plantilla de una expresión cond se ve asi:

(cond
 cuerpo)

donde el cuerpo es una serie de listas.

Escrito de forma más completa, la plantilla se ve asi:

(cond
 (primera-prueba-verdadero-o-falso primera-consequencia)
 (segunda-prueba-verdadero-o-falso segunda-consequencia)
 (tercera-prueba-verdadero-o-falso tercera-consequencia)
  )

Cuando el intérprete Lisp evalúa la expresión cond, evalúa el primer elemento (el car o prueba-verdadero-o-falso) de la primer expresión en una serie de expresiones dentro del cuerpo del cond.

Si la prueba-verdadero-o-falso devuelve nil el resto de esa expresión, la consecuencia; se descarta y se evalua la prueba-verdadero-o-falso de la siguiente expresión. Cuando se encuentra una expresión cuya prueba-verdadero-o-falso devuelve un valor que no es nil, se evalua la consecuencia de esa expresión. La consecuencia puede ser una o más expresiones. Si la consecuencia consiste de más de una expresión, las expresiones se evaluan en secuencia y se devuelve el valor de la última. Si la expresión no tiene consecuencia, se devuelve el valor de la prueba-verdadero-o-falso.

Si ninguna de las pruebas prueba-verdadero-o-falso es verdadera, la expresión cond devuelve nil.

Asi se ve la función triangle, usando cond:

(defun triangulo-usando-cond (numero)
  (cond ((<= numero 0) 0)
        ((= numero 1) 1)
        ((> numero 1)
         (+ numero (triangulo-usando-cond (1- numero))))))

En este ejemplo, cond devuelve 0 si el número es menor o igual a 0, devuelve 1 si el número es 1 y evalúa (+ numero (triangulo-usando-cond (1- numero))) si el número es mayor a 1.

Patrones recursivos

Aquí hay tres patrones recursivos comunes. Cada uno implica una lista. La recursión no necesita involucrar listas, pero Lisp esta diseñado para listas y esto proporciona una idea de sus capacidades primarias.

Patrón recursivo: every

En el patrón recursivo every, se desarrolla una acción en cada elemento de una lista.

El patrón básico es:

Aquí está el ejemplo:

(defun cuadrar-cada-uno (lista-de-numeros)
  "El cuadrado de cada elemento en LISTA DE NUMEROS, recursivamente."
  (if (not lista-de-numeros)          ; prueba-hazlo-de-nuevo
      nil
    (cons
     (* (car lista-de-numeros) (car lista-de-numeros))
     (cuadrar-cada-uno (cdr lista-de-numeros))))) ; expresion-del-siguiente-paso
> (cuadrar-cada-uno '(1 2 3))
(1 4 9)

Si lista-de-numeros está vacía, no hay que hacer nada. Pero si tiene contenido, se construye una lista combinando el cuadrado del primer número en la lista con el resultado de la llamada recursiva.

(El ejemplo sigue el patrón exactamente: se devuelve nil si la lista de números esta vacía. En la práctica, se escribiría el condicional para que lleve a cabo la acción cuando la lista de números no este vacía.)

La función imprimir-elementos-recursivamente (Ver Sección Recursividad con una Lista) es otro ejemplo de un patrón every, excepto que en este caso, en vez de juntar los resultados usando cons, se imprime cada elemento de salida.

La función imprimir-elementos-recursivamente se ve asi:

(setq animales '(gacela jirafa leon tigre))

(defun imprimir-elementos-recursivamente (lista)
  "Imprime cada elemento de LISTA en una línea propia.
Usa recursión."
  (when lista                               ; prueba-hazlo-de-nuevo
        (print (car lista))                 ; cuerpo
        (imprimir-elementos-recursivamente  ; llamada recursiva
         (cdr lista))))                     ; expresion-del-siguiente-paso

(imprimir-elementos-recursivamente animales)

El patrón para imprimir-elementos-recursivamente es:

Patrón recursivo: accumulate

Otro patrón recursivo se llama patrón accumulate. En el patrón recursivo accumulate, se realiza una acción en cada elemento de una lista y el resultado de esta acción se acumula con los resultados de realizar la acción en los otros elementos.

Esto es muy parecido al patron ‘every’ usando cons, excepto que no se utiliza cons, sino algun otro combinador.

El patrón es:

Aquí hay un ejemplo:

(defun sumar-elementos (lista-de-numeros)
  "Suma los elementos de LISTA-DE-NUMEROS."
  (if (not lista-de-numeros)
      0
    (+ (car lista-de-numeros) (sumar-elementos (cdr lista-de-numeros)))))
> (sumar-elementos '(1 2 3 4))
10

Consulta la Seccion Creando una lista de ficheros, para un ejemplo del patrón accumulate.

Patrón recursivo: keep

Un tercer patrón se llama el patrón keep. En el patrón recursivo keep, se prueba cada elemento de una lista; se actúa sobre el elemento y los resultados se conservan solo si el elemento cumple un criterio.

De nuevo, esto es muy parecido al patrón ‘every’, excepto que el elemento se descarta a menos que cumpla un criterio.

El patrón tiene tres partes:

Aquí hay un ejemplo que usa cond:

(defun guardar-palabras-de-tres-letras (lista-palabras)
  "De la LISTA-PALABRAS, guarda las palabras de 3 letras."
  (cond
   ;; Primera prueba-hazlo-de-nuevo: condicion-de-parada
   ((not lista-palabras) nil)

   ;; Segunda prueba-hazlo-de-nuevo: cuando actuar
   ((eq 3 (length (symbol-name (car lista-palabras))))
    ;; combina el elemento actual con la llamada recursiva en
    ;; una lista mas corta
    (cons (car lista-palabras) (guardar-palabras-de-tres-letras (cdr lista-palabras))))

   ;; Tercera prueba-hazlo-de-nuevo: cuando saltar el elemento;
   ;; llamada recursiva con una lista mas corta con la
   ;; expresion-del-siguiente-paso
   (t (guardar-palabras-de-tres-letras (cdr lista-palabras)))))
> (guardar-palabras-de-tres-letras '(uno dos tres cuatro cinco seis))
(uno dos)

No hace falta decir que no es necesario utilizar nil como prueba para saber cuando parar; y, por su puesto, se pueden combinar estos patrones.

Recursividad sin aplazamiento

Consideremos de nuevo lo que sucede con la función triangulo-recursivo. Encontraremos que los cálculos intermedios se postergan hasta que se pueda hacer todo.

Aquí está la definición de función:

(defun triangulo-recursivo (numero)
  "Devuelve la suma de números desde el 1 hasta e incluyendo a NUMERO.
Usa recursion."
  (if (= numero 1)                    ; prueba-hazlo-de-nuevo
      1                               ; parte-then
    (+ numero                         ; parte-else
       (triangulo-recursivo           ; llamada recursiva
        (1- numero)))))               ; expresion-del-siguiente-paso

¿Qué ocurre cuando llamamos a esta función con un argumento de 7?

La primera instancia de la función triangulo-recursivo suma el número 7 al valor devuelto por una segunda instancia de triangulo-recursivo, una instancia a la que se ha pasado un argumento de 6. Es decir, el primer cálculo es:

(+ 7 (triangulo-recursivo 6))

La primera instancia de triangulo-recursivo––tal vez quieres pensar en ella como un pequeño robot––no puede completar su trabajo. Debe pasar el cálculo a (triangulo-recursivo 6) una segunda instancia del programa, a un segundo robot. Este segundo individuo es completamente diferente del primero; en la jerga, una ‘instancia diferente’. O, dicho de otro modo, es un robot diferente. Es del mismo modelo que el primero; calcula números de triángulo recursivamente; pero tiene un número de serie diferente.

¿Y qué devuelve (triangulo-recursivo 6)? Devuelve el número 6 sumado al valor devuelto de evaluar triangulo-recursivo con un argumento de 5. Usando la metáfora del robot, le pide a otro robot que lo ayude.

Ahora el total es:

(+ 7 6 (triangulo-recursivo 5))

¿Y qué sucede después?

(+ 7 6 5 (triangulo-recursivo 4))

Cada vez que se llama a triangulo-recursivo, excepto la última vez, se crea otra instancia del programa––otro robot––y le pide que haga un cálculo.

Eventualmente, se establece y realiza la suma completa:

(+ 7 6 5 4 3 2 1)

Este diseño de la función aplaza el cálculo del primer paso hasta que se pueda hacer el segundo, y aplaza este hasta que se puede hacer el tercero, y así sucesivamente. Cada aplazamiento significa que el ordenador debe recordar lo que se está esperado. Esto no es un problema cuando solo hay unos pocos pasos, como en este ejemplo. Pero puede ser un problema cuando hay más pasos.

Solucion sin aplazamiento

La solución al problema de deferir operaciones es escribir de una manera que no se pospongan las operaciones12. Esto requiere escribir en un patrón diferente, con frecuencia uno que implica escribir dos definiciones de función, una función de ‘inicialización’ y una función ‘auxiliar’.

La función de ‘inicializacion’ configura el trabajo; la función ‘auxiliar’ hace el trabajo.

Aquí estan las dos definiciones de funcion para sumar números. Son tan simples, que me cuesta entenderlas.

(defun triangulo-inicializacion (numero)
  "Devuelve la suma de números desde el 1 hasta e incluyendo a NUMERO.
Este es el componente de ‘inicialización’ de una función doble
que utiliza recursión"
  (triangulo-recursivo-auxiliar 0 0 numero))
(defun triangulo-recursivo-auxiliar (suma contador numero)
  "Devuelve la SUMA, usando CONTADOR, hasta e incluyendo NUMERO.
Este es el componente ‘auxiliar’ de una funcion doble que
utiliza recursión."
  (if (> contador numero)
      suma
    (triangulo-recursivo-auxiliar (+ suma contador)  ; suma
                                  (1+ contador)      ; contador
                                  numero)))          ; número

Instala ambas definiciones de función evaluandolas, luego llama a triangulo-inicializacion con 2 filas:

> (triangulo-inicializacion 2)
3

La función de ‘inicialización’ llama a la primera instancia de la función ‘auxiliar’ con tres argumentos: cero, cero, y un número que es el número de filas en el triángulo.

Los dos primeros argumentos que se pasan a la función ‘auxiliar’ son valores de inicialización. Estos valores cambian cuando triangulo-recursivo-auxiliar invoca nuevas instancias.13

Veamos que pasa cuando tenemos un triángulo que tiene una fila. (¡Este triángulo tendrá un guijarro en el!)

triangulo-inicializacion llamará a su auxiliar con los argumentos 0 0 1. Esta función ejecutará la prueba condicional (> contador numero):

(> 0 1)

y encontrara que el resultado es falso, por lo que invocara la parte-else de la clausula if:

    (triangulo-recursivo-auxiliar
     (+ suma contador)  ; suma más contador   ⇒ suma
     (1+ contador)      ; incrementa contador ⇒ contador
     numero)            ; numero se mentiene igual

que al inicio calcula:

(triangulo-recursivo-auxiliar (+ 0 0)  ; suma
                              (1+ 0)   ; contador
                              1)       ; numero

que es:

(triangulo-recursivo-auxiliar 0 1 1)

Una vez mas, (> contador numero) será falso, por lo que de nuevo, el intérprete Lisp evaluará triangulo-recursivo-auxiliar, creando una nueva instancia con nuevos argumentos.

Esta nueva instancia será;

(triangulo-recursivo-auxiliar
 (+ suma contador)  ; suma más contador ⇒ suma
 (1+ contador)      ; incrementa contador ⇒ contador
numero)             ; numero se mentiene igual

que es:

(triangulo-recursivo-auxiliar 1 2 1)

En este caso, la prueba (> contador numero) ¡será verdadera! Así que la instancia devolverá el valor de la suma, que es 1, como se esperaba.

Ahora, vamos a pasar a triangulo-inicializacion un argumento de 2, para encontrar cuántos guijarros hay en un triángulo con dos filas.

Esta función haria la llamada (triangulo-recursivo-auxiliar 0 0 2).

En etapas, las instancias llamadas serán:

                            suma contador número
(triangulo-recursivo-auxiliar 0    1       2)

(triangulo-recursivo-auxiliar 1    2       2)

(triangulo-recursivo-auxiliar 3    3       2)

Cuando se llama a la última instancia, la prueba (> contador numero) será verdadera, por lo que la instancia devolverá el valor de suma, que será 3.

Este tipo de patrón ayuda cuando estás escribiendo funciones que pueden usar muchos recursos en un ordenador.

Ejercicio de bucles

Para obtener más información, consulta la Seccion Indicando Definiciones, Comandos, etc. en el Manual de Texinfo dentro de Emacs. O, tambien puedes consultarlo en internet en http://www.gnu.org/software/texinfo/manual/texinfo/

Búsqueda de expresiones regulares

Las búsquedas de expresiones regulares se utilizan extensivamente en GNU Emacs. Las dos funciones forward-sentence y forward-paragraph, ilustran bien estas búsquedas. Usan expresiones regulares para encontrar donde mover el punto. La frase ‘expresión regular’ se escribe a menudo como ‘regexp’.

Las búsquedas de expresiones regulares se describen en la Seccion Búsqueda de Expresiónes Regulares en El Manual de GNU Emacs, asi como en la Seccion Expresiones Regulares en El Manual de Referencia de GNU Emacs Lisp. Al escribir este capítulo, estoy suponiendo que tienes al menos un leve conocimiento de esto. El punto principal a recordar es que las expresiones regulares te permiten buscar patrones, asi como cadenas literales de caracteres. Por ejemplo, el código en forward-sentence busca el patrón de posibles caracteres que podrían marcar el final de una oracion, y mueve el punto a ese lugar.

Antes de mirar el código de la función forward-sentence, vale la pena considerar cual debe ser el patrón que marca el final de una oracion. El patrón se discute en la siguiente sección; a continuacion se describe la funcion de busqueda de expresiónes regulares, re-search-forward. La función forward-sentence se describe en la sección siguiente. Finalmente, la función forward-paragraph se describe en la última sección de este capítulo. forward-paragraph es una función compleja que introduce varias caracteristicas nuevas.

La expresión regular de sentence-end

El símbolo sentence-end esta ligado al patrón que marca el fin de una oracion. ¿Cuál deberia ser esta expresión regular?

Claramente, una oracion puede terminarse por un punto, un signo de interrogación, o un signo de exclamación. De hecho, en inglés, solo las oraciones que terminan con uno de estos tres caracteres deberían considerse como el final de una oracion. Esto significa que el patrón debe incluir el conjunto de caracteres:

[.?!]

Sin embargo, no queremos que forward-sentence salte simplemente a un punto, a un signo de interrogacion o a un signo de exclamación, porque tal carácter podría utilizarse en medio de una oracion. Un punto, por ejemplo, se usa después de las abreviaturas. Por lo tanto, se necesita mas información.

Segun la convención, escribes dos espacios despues de cada oracion, pero solo un espacio después de un punto, un signo de interrogacion o un signo de exclamación en el cuerpo de una oracion. Asi que un punto, un signo de interrogacion o de exclamacion seguido por dos espacios es un buen indicador de un final de oracion. Sin embargo, en un archivo, los dos espacios pueden ser un tabulador o el fin de una línea. Esto significa que la expresión regular incluiría estos tres elementos como alternativas.

Este grupo de alternativas se vera asi:

\\($\\| \\|  \\)
       ^   ^^
      TAB  SPC

Aquí, ‘$’ indica el fin de la línea, ademas he señalado en la expresión donde se inserta el tabulador y los dos espacios. Ambos se insertan poniendo los caracteres reales en la expresión.

Antes de los parentesis y las barras verticales, se requiere dos barras invertidas ‘\\’: la primera barra invertida cita la siguiente barra invertida en Emacs; y la segunda indica que el siguiente caracter, el paréntesis o la barra vertical, es especial.

También, una oracion puede ir seguida por uno o más retornos de carro, como aqui:

[
]*

Al igual que los tabuladores y espacios, un retorno de carro se inserta en una expresión regular insertándolo literalmente. El asterisco indica que el RET se repite cero o más veces.

Pero una oracion no consiste solo en un punto, o un signo de interrogacion o de exclamación seguido de un espacio apropiado: una comilla de cierre o una llave de cierre de algún tipo puede preceder al espacio. De hecho, más de una marca o paréntesis pueden preceder al espacio. Estas requieren una expresión parecida a esta:

[]\"')}]*

En esta expresión, el primer ‘]’ es el primer caracter en la expresión; el segundo caracter es ‘"’, que está precedido por un ‘\’ para decirle a Emacs que el ‘"no es especial. Los últimos tres caracteres son ‘'’, ‘)’, y ‘}’.

Todo esto sugiere cual deberia ser el patrón de la expresión regular para que coincida con el final de una oracion; y, de hecho, si evaluamos sentence-end encontraremos que devuelve el siguiente valor:

> sentence-end
"[.?!][]\"')}]*\\($\\|     \\|  \\)[
]*"

(Bueno, no en GNU Emacs 22; eso se debe a un esfuerzo por hacer el proceso simple y manejar más símbolos y lenguajes. Cuando el valor de sentence-end es nil, entonces utiliza el valor definido por la función sentence-end. (Aquí se utiliza la diferencia entre un valor y una función en Emacs Lisp.) La función devuelve un valor construido a partir de las variables sentence-end-base, sentence-end-double-space, sentence-end-without-period, y sentence-end-without-space. La variable crítica es sentence-end-base; su valor global es similar al descrito anteriormente, pero también contiene dos marcas de cita adicionales. Estas tienen diferentes grados de curvatura. La variable sentence-end-without-period, cuando es verdadera, le dice a Emacs que una oracion puede finalizar sin un punto, como en texto en Tailandés.)

La Función re-search-forward

La función re-search-forward es similar a la función search-forward. (Consulta la Seccion La Función search-forward.)

re-search-forward busca una expresión regular. Si la búsqueda es exitosa, deja el punto inmediatamente después del último caracter en el objetivo. Si la búsqueda es hacia atrás, deja el punto antes del primer caracter en el objetivo. Puedes decirle a re-search-forward que regrese t por verdadero. (El movimiento del punto es por la tanto un ‘efecto secundario’.)

Al igual que search-forward, la función re-search-forward toma cuatro argumentos:

  1. El primer argumento es la expresión regular que busca la función. La expresión regular será una cadena entre comillas.

  2. El segundo argumento opcional limita el grado de busqueda de la función; es un valor ligado con una posición especifica en el búfer.

  3. El tercer argumento opcional especifica cómo la función responde al fallo: nil como tercer argumento hace que la función señale un error (e imprima un mensaje) cuando la búsqueda falla; cualquier otro valor hace que devuelva nil si la búsqueda falla y t si la búsqueda tiene éxito.

  4. El cuarto argumento opcional es el contador de repeticiones. Un contador de repeticion negativo hace que re-search-forward busque hacia atrás.

La plantilla para re-search-forward se ve asi:

(re-search-forward "expresion-regular"
                   limite-de-busqueda
                   que-hacer-si-falla-la-busqueda
                   contador-de-repeticiones)

El segundo, tercer, y cuarto argumentos son opcionales. Sin embargo, si se quiere pasar un valor a uno o ambos de los últimos dos argumentos, se debe también pasar un valor a todos los argumentos anteriores. De otro modo, el intérprete Lisp confundira el argumento al que estás pasando el valor.

En la función forward-sentence, la expresión regular será el valor de la variable sentence-end. En forma simple, esto es:

"[.?!][]\"')}]*\\($\\|  \\|  \\)[
]*"

El límite de la búsqueda será el fin del párrafo (ya que una oracion no puede ir mas alla de un párrafo). Si la búsqueda falla, la función devuelve nil, y el argumento del contador de repeticion será pasado a la función forward-sentence.

La Función forward-sentence

El comando para mover el cursor hacia adelante una oracion es una ilustración directa de cómo usar búsqueda de expresiones regulares en Emacs Lisp. De hecho, la función parece más larga y más complicada de lo que es; esto se debe a que la función está diseñada para ir hacia atrás y hacia adelante; y, opcionalmente, mas de una oracion. La función normalmente está vinculada al comando M-e.

Aquí está la código de forward-sentence:

(defun forward-sentence (&optional arg)
  "Avansa al siguiente ‘sentence-end’. Con un argumento, se repite.
Con un argumento negativo, avanza hacia atras repetidamente al ‘sentence-beginning’.

La variable ‘sentence-end’ es una expresión regular que corresponde al
fin de la oracion. Ademas, cada limite de párrafo tambien termina una oracion."
  (interactive "p")
  (or arg (setq arg 1))
  (let ((opoint (point))
        (sentence-end (sentence-end)))
    (while (< arg 0)
      (let ((pos (point))
            (par-beg (save-excursion (start-of-paragraph-text) (point))))
       (if (and (re-search-backward sentence-end par-beg t)
                (or (< (match-end 0) pos)
                    (re-search-backward sentence-end par-beg t)))
           (goto-char (match-end 0))
         (goto-char par-beg)))
      (setq arg (1+ arg)))
    (while (> arg 0)
      (let ((par-end (save-excursion (end-of-paragraph-text) (point))))
       (if (re-search-forward sentence-end par-end t)
           (skip-chars-backward " \t\n")
         (goto-char par-end)))
      (setq arg (1- arg)))
    (constrain-to-field nil opoint t)))

La función se ve larga a primera vista y lo mejor es mirar primero su esqueleto, y luego su músculo. La forma de ver el esqueleto es mirar las expresiones que comienzan en las columnas de la izquierda:

(defun forward-sentence (&optional arg)
  "documentacion…"
  (interactive "p")
  (or arg (setq arg 1))
  (let ((opoint (point)) (sentence-end (sentence-end)))
    (while (< arg 0)
      (let ((pos (point))
            (par-beg (save-excursion (start-of-paragraph-text) (point))))
       resto-del-cuerpo-del-bucle-while-cuando-va-hacia-atras
    (while (> arg 0)
      (let ((par-end (save-excursion (end-of-paragraph-text) (point))))
       resto-del-cuerpo-del-bucle-while-cuando-va-hacia-adelante
    manejar-formularios-y-equivalentes

¡Esto parece mucho mas simple! La definición de la función consiste en la documentación, una expresión interactive, una expresión or, una expresión let, y bucles while.

Veamos cada una de estas partes por separado.

Observamos que la documentación es completa y comprensible.

La función tiene una declaración interactive "p". Esto signifca que el argumento prefijo procesado, si lo hay, pasa a la función como su argumento. (Este será un número.) Si no se pasa un argumento a la función (es opcional) entonces el argumento arg será vinculara a 1.

Cuando forward-sentence se llama no interactivamente sin un argumento, arg está vinculado a nil. La expresión or maneja esto. Lo que hace es dejar el valor de arg como esta, pero solo si arg está ligado a un valor; de otro modo asigna el valor de arg a 1, en el caso de que arg este ligado a nil.

Lo siguiente es un let. Especifica los valores de dos variables locales point y sentence-end. El valor local del punto, desde antes de la búsqueda, se utiliza en la función constrain-to-field que maneja formularios y equivalentes. La variable sentence-end se establece por la función sentence-end.

Los bucles while

Siguen dos bucles while. El primer while tiene una prueba-verdadero-o-falso que es verdadero si el argumento prefijo de forward-sentence es un número negativo. Esto es para ir hacia atras. El cuerpo de este bucle es similar al cuerpo del segundo while, pero no es exactamente el mismo. Pasaremos de este bucle while y nos centraremos en el segundo while.

El segundo bucle while es para mover el punto hacia adelante. Su esqueleto luce asi:

(while (> arg 0)            ; prueba-verdadero-o-falso
  (let varlist
    (if (prueba-verdadero-o-falso)
        parte-then
      parte-else
  (setq arg (1- arg))))     ; decremento del bucle while

El bucle while es de tipo decreciente. (Consulta la Seccion Bucle con un contador decreciente.) Tiene una prueba-verdadero-o-falso que regresa verdadero siempre y cuando el contador (en este caso, la variable arg) sea mayor que cero; y tiene un decremento que resta 1 del valor del contador cada vez que el bucle se repite.

Si no se da un argumento prefijo a forward-sentece, que es lo mas habitual, este bucle while se ejecutará una vez, ya que el valor de arg será 1.

El cuerpo del bucle while consite en una expresión let, que crea y vincula una variable local, y tiene, como su cuerpo, una expresión if.

El cuerpo del bucle while se ve asi:

(let ((par-end
       (save-excursion (end-of-paragraph-text) (point))))
  (if (re-search-forward sentence-end par-end t)
      (skip-chars-backward " \t\n")
    (goto-char par-end)))

La expresión let crea y enlaza la variable local par-end. Como veremos, esta variable local está diseñada para proporcionar un limite a la búsqueda de expresiónes regulares. Si la búsqueda no encuentra una oracion apropiada que termine en el párrafo, se detendra al llegar al final del párrafo.

Pero primero, examinaremos cómo par-end se liga al valor del fin del párrafo. Lo que ocurre es que el let asigna el valor de par-end al valor devuelto cuando el intérprete evalúa la expresión.

(save-excursion (end-of-paragraph-text) (point))

En esta expresión, (end-of-paragraph-text) mueve el punto al fin del párrafo, (point) devuelve el valor del punto, y luego save-excursion restaura el punto a su posición original. De este modo, el let liga par-end al valor devuelto por la expresión save-excursion, que es la posición del fin del párrafo. (La función end-of-paragraph-text utiliza forward-paragraph, que discutiremos en breve.)

A continuacion Emacs evalúa el cuerpo del let, que es una expresión if:

(if (re-search-forward sentence-end par-end t) ; parte-if
    (skip-chars-backward " \t\n")              ; parte-then
  (goto-char par-end)))                        ; parte-else

El if comprueba si su primer argumento es verdadero y es así, evalúa su parte-then; de lo contrario, el intérprete Emacs Lisp evalúa la parte-else. La prueba-verdadero-o-falso de la expresión if es la búsqueda de la expresión regular.

Puede parecer extraño tener lo que parece ser el ‘trabajo real’ de la función forward-sentence enterrado aqui, pero esta es una forma común de realizar este tipo de operación en Lisp.

La búsqueda de expresiones regulares

La función re-search-forward busca el fin de la oracion, es decir, el patrón definido por la expresión regular sentence-end. Si se encuentra el patrón––si se encuentra el fin de la oracion––entonces la función re-search-forward hace dos cosas:

  1. La función re-search-forward realiza un efecto secundario, que es mover el punto al final de la ocurrencia encontrada.

  2. La función re-search-forward devuelve un valor verdadero. Este es el valor recibido por el if, y significa que la búsqueda fué exitosa.

El efecto secundario, el movimiento del punto se completa antes de que la función if reciva el valor devuelto por la conclusión exitosa de la búsqueda.

Cuando la función if recibe el valor verdadero desde una llamada exitosa a re-search-forward, el if evalúa la parte-then que es la expresión (skip-chars-backward "\t\n"). Esta expresión se mueve hacia atrás a través de cualquier espacio en blanco, tabulador o retorno de carro hasta encontrar un caracter imprimible y deja el punto después del caracter. Como el punto ya se ha movido al final del patron que marca el final de la oracion, esta accion deja el punto justo despues del caracter imprimible de cierre de la oracion, que suele ser un punto.

Por otro lado, si la función re-search-forward no encuentra un patrón que marque el fin de la oracion, la función devuelve falso. El falso hace que if evalue su tercer argumento, que es (goto-char par-end): esto mueve el punto al final del párrafo.

(Y si el texto está en una forma o equivalente, y el punto no se mueve completamente, entonces entra en juego la función constrain-to-field.)

La búsqueda de expresiones regulares son excepcionalmente útiles y el patrón ilustrado por re-search-forward, en el que la búsqueda es la prueba de una expresión if, es facil de manejar. Veras o escribirás código incorporando este patrón con frecuencia.

forward-paragraph: una mina de oro de funciones

La función forward-paragraph mueve el punto al fin del párrafo. Por lo general esta asociado a M-} y hace uso de un número de funciones que son importantes en sí mismas, incluyendo let*, match-beginning, y looking-at.

La definición de función de forward-paragraph es considerablemente mas extensa que la definición de función de forward-sentence porque trabaja con un párrafo, cada línea de la cual puede empezar con un prefijo de relleno.

Un prefijo de relleno consiste en una cadena de caracteres que se repite al principio de cada línea. Por ejemplo, en el código Lisp, es una convención empezar cada línea de un comentario de un párrafo extenso con ‘;;; ’. En el modo Texto, cuatro espacios en blanco forman otro prefijo común de relleno, creando un párrafo indentado. (Consulta la Sección Prefijo de relleno en El Manual de GNU Emacs para más información acerca de los prefijos de relleno.)

La existencia de un prefijo de relleno significa que, además de poder encontrar el fin de un párrafo cuyas líneas empiezan en la columna más a la izquierda, la función forward-paragraph debe ser capaz de encontrar el final de un párrafo cuando todas o muchas de las líneas en el búfer empiezan con el prefijo de relleno.

Ademas, a veces es práctico ignorar un prefijo de relleno que existe, especialmente cuando las líneas en blanco separan los párrafos. Esto es una complicación añadida.

En vez de imprimir toda la función forward-paragraph, solo imprimiremos partes de la misma. ¡Leer la función sin preparación, puede ser desalentador!

En general, la función tiene este aspecto:

(defun forward-paragraph (&optional arg)
  "documentacion…"
  (interactive "p")
  (or arg (setq arg 1))
  (let*
      varlist
    (while (and (< arg 0) (not (bobp)))     ; codigo-de-movimiento-hacia-atras
      
    (while (and (> arg 0) (not (eobp)))     ; codigo-de-movimiento-hacia-adelante
      

Las primeras partes de la función son rutinarias: la lista de argumentos de la función consiste en un argumento opcional. Luego se presenta la documentación.

La letra minúscula ‘p’ en la declaración interactive significa que el argumento prefijo se procesa, si lo hay, pasa a la función. Este será un número, y es el contador de repeticion de cuántos párrafos se moverá el punto. La expresión or en la siguiente línea maneja el caso común cuando no se pasa ningun argumento a la función, esto ocurre si la función se llama desde otro código en lugar de interactivamente. Este caso se describio anteriormente. (Consulta la Seccion La Función forward-sentence.) Ahora llegamos al final de la parte familiar de esta función.

La expresión let*

La siguiente línea en la función forward-paragraph empieza con una expresión let*. Esto es diferente a let. El símbolo es let* no let.

La forma especial let* es como let excepto que Emacs establece cada variable en secuencia, una después de otra, y las variables en la última parte de la varlist pueden hacen uso de los valores a los que Emacs asignó las variables al principio la varlist.

(Seccion save-excursion en append-to-buffer.)

En la expresión let* en esta función, Emacs asigna un total de siete variables: opoint, fill-prefix-regexp, parstart, parsep, sp-parstart, start, y found-start.

La variable parsep aparece dos veces, primero, para eliminar instancias de ‘^’, y segundo, para manejar prefijos de relleno.

La variable opoint es solo el valor de point. Como se puede adivinar, se usa en una expresión constrain-to-field, igual que en forward-sentence.

La variable fill-prefix-regexp se establece con el valor devuelto de evaluar la siguiente lista:

(and fill-prefix
     (not (equal fill-prefix ""))
     (not paragraph-ignore-fill-prefix)
     (regexp-quote fill-prefix))

Esta es una expresión cuyo primer elemento es la forma especial and.

Como aprendimos anteriormente (Sección La función kill-new), la forma especial and evalúa cada uno de sus argumentos hasta uno de los argumentos devuelve un valor nil, en cuyo caso la expresión and devuelve nil; sin embargo, si ninguno de los argumentos devuelve un valor nil, se devuelve el valor resultante de evaluar el último argumento. (Puesto que tal valor no es nil, se considera verdadero en Lisp.) En otras palabras, una expresión and devuelve un valor verdadero solo si todos sus argumentos son verdaderos.

En este caso, la variable fill-prefix-regexp se vincula a un valor no nil solo si las cuatro expresiones siguientes producen un valor verdadero (es decir, un valor no nil) cuando se evalúan; de lo contrario, la variable fill-prefix-regexp se vincula a nil.

fill-prefix

Cuando se evalua esta variable, se devuelve el valor del prefijo de relleno, si lo hay. Si no hay ningun prefijo relleno, esta variable devuelve nil.

(not (equal fill-prefix "")

Esta expresión comprueba si un prefijo de relleno existente es una cadena vacía, es decir, una cadena sin caracteres. Una cadena vacía no es un prefijo de relleno útil.

(not paragraph-ignore-fill-prefix)

Esta expresión devuelve nil si la variable paragraph-ignore-fill-prefix ha sido activada al asignarle un valor verdadero como t.

(regexp-quote fill-prefix)

Este es el último argumento pasado a la forma especial and. Si todos los argumentos de and son verdaderos, el valor resultante de evaluar esta expresión será devuelto por la expresión and y asociado a la variable fill-prefix-regexp,

El resultado de evaluar esta expresión and con éxito es que fill-prefix-regexp sera ligada al valor de fill-prefix como fué modificado por la función regexp-quote. Lo que regexp-quote hace es leer una cadena y devolver la expresión regular que coincida exactamente con la cadena y nada más. Esto significa que el valor de fill-prefix-regexp será asignado a un valor que coincida exactamente con el prefijo si existe. De lo contrario, la variable sera ligada a nil.

Las siguientes dos variables locales en la expresión let* están diseñadas para eliminar instancias de ‘^’ de parstart y parsep, las variables locales que indican el inicio del párrafo y el separador de párrafos. La siguiente expresión define parsep de nuevo. Esto es para manejar prefijos de relleno.

Esta es la configuracion que requiere la llamada de la definición let* en lugar de let. La prueba-verdadero-o-falso del if depende de si la variable fill-prefix-regexp se evalúa a nil o algún otro valor.

Si fill-prefix-regexp no tiene un valor, Emacs evalúa la parte-else de la expresión if y vinculara parsep a su valor local. (parsep es una expresión regular que coincide con la separacion de párrafos.)

Pero si fill-prefix-regexp tiene un valor, Emacs evalúa la parte-then de la expresión if y enlaza parsep a una expresión regular que incluye fill-prefix-regexp como parte del patrón.

Específicamente, parsep se asigna al valor original del separador del párrafo, la expresión regular concatenada con una expresión alternativa que consiste en fill-prefix-regexp seguido por espacios en blanco opcionales halta el fin de la línea. (El espacio en blanco se define con [ \t]*$.) El ‘\\|’ define esta porción de la regexp como una alternativa a parsep.

De acuerdo a un comentario en el código, la siguiente variable local, sp-parstart, se utiliza para la busqueda, y luego los dos finales, start y found-start, se asignan a nil.

Ahora entramos en el cuerpo del let*. La primera parte del cuerpo del let* trata el caso cuando la función recibe un argumento negativo y por lo tanto se mueve hacia atrás. Omitiremos esta sección.

El bucle while hacia adelante

La segunda parte del cuerpo del let* se ocupa del movimiento hacia adelante. Es un bucle while que se repite mientras el valor de arg sea mayor a cero. En el uso más común de la función el valor del argumento es 1, por lo que el cuerpo del bucle while se evalúa exactamente una vez, y el cursor avanza hacia adelante un párrafo.

Esta parte maneja tres situaciones: cuando el punto está entre párrafos, cuando hay un prefijo de relleno y cuando no hay un prefijo de relleno.

El bucle while se ve así:

;; avanza hacia adelante y no al final del búfer
(while (and (> arg 0) (not (eobp)))

  ;; entre párrafos
  ;; Avanzar sobre lineas de separacion...
  (while (and (not (eobp))
              (progn (move-to-left-margin) (not (eobp)))
              (looking-at parsep))
    (forward-line 1))
  ;;  Esto decrementa el bucle
  (unless (eobp) (setq arg (1- arg)))
  ;; ... y una línea más
  (forward-line 1)

  (if fill-prefix-regexp
      ;; Hay un prefijo de relleno; sobreescribe parstart;
      ;; avanzamos línea a línea
      (while (and (not (eobp))
                  (progn (move-to-left-margin) (not (eobp)))
                  (not (looking-at parsep))
                  (looking-at fill-prefix-regexp))
        (forward-line 1))

    ;; No hay prefijo de relleno;
    ;; avanzamos caracter por caracter
    (while (and (re-search-forward sp-parstart nil 1)
                (progn (setq start (match-beginning 0))
                       (goto-char start)
                       (not (eobp)))
                (progn (move-to-left-margin)
                       (not (looking-at parsep)))
                (or (not (looking-at parstart))
                    (and use-hard-newlines
                         (not (get-text-property (1- start) 'hard)))))
      (forward-char 1))

    ;; y si no hay prefijo de relleno y si no estamos al final
    ;; ir a lo que se encontro en la búsqueda de expresiones regulares
    ;; para sp-parstart
    (if (< (point) (point-max))
        (goto-char start))))

Podemos ver que se trata de un contador de decremento while, usando la expresión (setq arg (1- arg)) como decrementador. Esta expresión no está lejos del while, pero está oculta en otra macro Lisp, una macro unless. A menos que estemos al final del búfer––eso es lo que determina la función eobp; es una abreviación de ‘End of Buffer P’––disminuimos el valor de arg por uno.

(Si estamos al fin del búfer, ya no podemos avanzar mas y el siguiente bucle de la expresión while sera falso ya que la prueba es un and con (not (eobp)). La función not significa exactamente lo que se espera; es otro nombre para null, una función que devuelve verdadero cuando su argumento es falso.)

Curiosamento, el contador del bucle no disminuye hasta que dejamos el espacio entre párrafos, a menos que lleguemos al fin del búfer o dejemos de ver el valor local del separador del párrafo.

El segundo while también tiene una expresión (move-to-left-margin). La función es autoexplicativa. Está dentro de una expresión progn y no es el último elemento de su cuerpo, por lo que solo se invoca por su efecto secundario, que es mover el punto al margen izquierdo de la línea actual.

La función looking-at también es auto-explicativa; devuelve verdadero si el texto después del punto coincide con la expresión regular dada como su argumento.

El resto del cuerpo del bucle parece complejo al principio, pero tiene sentido a medida que se llega a entender.

Primero considera lo que sucede si hay un prefijo de relleno:

(if fill-prefix-regexp
    ;; Hay un prefijo de relleno; sobreescribe parstart;
    ;; avanzamos línea a línea
    (while (and (not (eobp))
                (progn (move-to-left-margin) (not (eobp)))
                (not (looking-at parsep))
                (looking-at fill-prefix-regexp))
      (forward-line 1))

Esta expresión mueve el punto hacia adelante línea por línea siempre y cuando se cumplan cuatro condiciones:

  1. El punto no está al final del búfer.

  2. Podemos movernos al margen izquierdo del texto y no estamos al fin del búfer.

  3. El texto siguiente al punto no separa los párrafos.

  4. El patrón que sigue al punto es la expresión regular del prefijo de relleno.

La última condición puede ser un puzzle, hasta que se recuerde qué el punto fue movido al principio de la línea anteriormente en la función forward-paragraph. Esto significa que si el texto tiene un prefijo de relleno, la función looking-at lo verá.

Considera qué ocurre cuando no hay un prefijo lleno.

(while (and (re-search-forward sp-parstart nil 1)
            (progn (setq start (match-beginning 0))
                   (goto-char start)
                   (not (eobp)))
            (progn (move-to-left-margin)
                   (not (looking-at parsep)))
            (or (not (looking-at parstart))
                (and use-hard-newlines
                     (not (get-text-property (1- start) 'hard)))))
  (forward-char 1))

El bucle while nos lleva a la busqueda de sp-parstart, que es la combinación de posibles espacios en blanco con el valor local del inicio de un párrafo o de un separador de párrafos. (Los dos últimos se encuentran dentro de una expresión que comienza con \(?: así que no están referenciados por la función match-beginning.)

Las dos expresiones,

(setq start (match-beginning 0))
(goto-char start)

significan ir al comienzo del texto localizado con la expresión regular.

La expresión (match-beginning 0) es nueva. Devuelve un número que especifica la ubicacion del inicio del texto que coincidio con la última búsqueda.

La función match-beginning se utiliza aquí debido a una característica de la búsqueda hacia adelante: una búsqueda exitosa hacia adelante, independientemente de si se trata de una búsqueda simple o de una con expresiónes regulares, mueve el punto al fin del texto que encuentra. En este caso, una búsqueda exitosa mueve el punto al fin del patrón de sp-parstart.

Sin embargo, queremos poner el punto al fin del actual párrafo, no en algún otro lugar. De hecho, dado que la búsqueda posiblemente incluye el separador de párrafos, el punto puede finalizar al principio del siguiente a menos que utilicemos una expresión que incluya match-beginning.

Cuando se le da un argumento de 0, match-beginning devuelve la posición de inicio del texto coincidente con la búsqueda más reciente. En este caso, la búsqueda más reciente de sp-parstart. La expresión (match-beginning 0) devuelve la posición inicial de ese patrón, en lugar de la posición final.

(Incidentalmente, cuando se pasa un número positivo como argumento, la función match-beginning devuelve la ubicacion del punto de la expresión con paréntesis en la última búsqueda a menos que la expresión con paréntesis empiece con \(?:. No sé porque \(?: aparece aquí, ya que el argumento es 0.)

La última expresión cuando no hay prefijo de relleno es

(if (< (point) (point-max))
    (goto-char start))))

Esto dice que si no hay un prefijo de relleno y no estamos al final, el punto deberia moverse al principio de lo que sea que sea haya encontrado al buscar la expresión regular de sp-parstart.

La definición completa de la función forward-paragraph no solo incluye código para avanzar, sino también código para retroceder.

Si estás leyendo esto dentro de GNU Emacs y quieres ver la función completa, puedes escribir C-h f (describe-function) y el nombre de la función. Esto proporciona la documentación de función y el nombre de la librería que contiene el codigo fuente de la función. Coloca el punto sobre el nombre de la librería y presionar la tecla RET; serás llevado directamente al codigo fuente. (¡Asegúrate de instalar el codigo fuente! ¡Sin el, eres como una persona que intenta conducir un coche con los ojos cerrados!)

Crea tu propio fichero TAGS

Ademas de C-h f (describe-function), otra forma de ver el codigo de una función es escribir M-. (find-tag) y el nombre de la función cuando se solicite. Es un buen hábito para obtenerlo. El comando M-. (find-tag) te lleva directamente al codigo de una función, variable, o nodo. La función depende de tablas de etiquetas para saber donde ir.

Si la función find-tag pregunta primero por el nombre de una tabla TAGS, dale el nombre de un fichero TAGS como /usr/local/src/emacs/src/TAGS. (La ruta exacta a tu fichero TAGS depende de cómo instalaste tu copia de Emacs. Yo proporcione la localización tanto de mi codigo fuente de C, como de Emacs Lisp.)

Tambien puedes crear tu propio fichero TAGS para directorios que carecen de uno.

Con frecuencia se necesita construir e instalar tablas de etiquetas por cuenta propia. No se construiyen automáticamente. Una tabla de etiquetas es un fichero que lleva por nombre TAGS; el nombre esta en letras mayúsculas.

Puedes crear un fichero TAGS llamando al programa etags que viene como parte de la distribución de Emacs. Por lo general, etags se compila e instala cuando se construye Emacs. (etags no es una función Lisp ni parte de Emacs; es un programa C.)

Para crear el fichero TAGS, primero cambia al directorio en el que deseas crear el fichero. En Emacs se puede hacer esto con el comando M-x cd, o visitando un fichero en el directorio, o listando el directorio con C-x d (dired). A continuacion, ejecuta el comando compile, con etags *.el como el comando a ejecutar

M-x compile RET etags *.el RET

para crear un fichero TAGS para Emacs Lisp.

Por ejemplo, si se tiene un gran número de ficheros en el directorio ~/emacs, como en mi caso––yo tengo 137 ficheros .el, de los cuales cargo 12––se puede crear un fichero TAGS para los ficheros Emacs Lisp en ese directorio.

El programa etags recive todos los ‘comodines’ usuales del shell. Por ejemplo, si tienes dos directorios para los que deseas un unico fichero TAGS, ingresa etags *.el ../elisp/*.el, donde ../elisp/ es el segundo directorio:

M-x compile RET etags *.el ../elisp/*.el RET

Ingresa

M-x compile RET etags --help RET

para ver una lista de las opciones aceptadas por etags asi como una lista de lenguajes soportados.

El programa etags maneja más de 20 lenguajes, incluyendo Emacs Lisp, Common Lisp, Scheme, C, C++, Ada, Fortran, HTML, Java, LaTeX, Pascal, Perl, Postscript, Python, TeX, Texinfo, makefiles, y la mayoría de ensambladores. El programa no tiene interruptores para especificar el lenguaje; reconoce el lenguaje en un fichero de entrada segun su nombre y contenido.

etags es muy útil cuando estas escribiendo código y quieres referirte a funciones que ya has escrito. Simplemente ejecuta de nuevo etags a medida que escribas nuevas funciones, para que formen parte del fichero TAGS.

Si crees que ya existe un fichero TAGS apropiado para lo que buscas, pero no conoces donde está, puedes usar el programa locate para intentar encontrarlo.

Escribe M-x locate RET TAGS RET y Emacs listará los nombres de ruta completos de todos tus ficheros TAGS. En mi sistema, este comando muestra 34 fichero TAGS. Por otra parte, un sistema ‘vainilla’ instalado recientemente no contenía ningun fichero TAGS.

Si la tabla de etiquetas que buscas ya ha sido creada, puedes utilizar el comando M-x visit-tags-table para especificarla. De lo contrario, tendras que crear la tabla de etiquetas por tí mismo y luego utilizar M-x visit-tags-table.

Construyendo Etiquetas en el codigo de Emacs

El codigo fuente de GNU Emacs vienen con un Makefile que contiene un comando etags sofisticado que crea, recoge, y une tablas de etiquetas con todo el codigo de Emacs y coloca la información dentro de un fichero TAGS en el directorio src/. (El directorio src/ está debajo del nivel superior de tu directorio Emacs.)

Para construir este fichero TAGS, debes ir al nivel superior del directorio con el codigo fuente de Emacs y ejecutar el comando de compilacion make tags:

M-x compile RET make tags RET

(El comando make tags funciona bien con las fuentes de GNU Emacs, asi como con algunos otros paquetes de codigo fuente.)

Para más información, mira Tablas de Etiquetas en El Manual de GNU Emacs.

Repaso

Aquí hay un breve resumen de algunas de las funciones introducidas recientemente.

while

Evalúa repetidamente el cuerpo de la expresión simpre y cuando el primer elemento del cuerpo sea verdadero. Despues devuelve nil. (La expresión se evalua solo por sus efectos secundarios.)

Por ejemplo:

> (let ((foo 2))
  (while (> foo 0)
    (insert (format "foo es %d.\n" foo))
    (setq foo (1- foo))))
foo es 2.
foo es 1.
nil

(La función insert inserta sus argumentos en el punto; la función format devuelve una cadena formateada a partir de sus argumentos de la misma manera en que message formatea sus argumentos; \n produce una nueva línea.)

re-search-forward

Busca un patrón, y si se encuentra, mueve el punto al final de la coincidencia.

Igual que search-forward, toma cuatro argumentos:

  1. Una expresión regular que especifica el patrón a buscar. (¡Recuerda poner comillas alrededor de este argumento!)

  2. Opcionalmente, el límite de la búsqueda.

  3. Opcionalmente, que hacer si la búsqueda falla, devuelve nil o un mensaje de error.

  4. Opcionalmente, cuántas veces se puede repetir la búsqueda; si es negativa, la búsqueda va hacia atrás.

let*

Asocia algunas variables localmente a valores particulares, y luego evalúa los argumentos restantes, devolviendo el valor del último. Al asociar las variables locales, se pueden usan los valores locales de variables declaradas anteriormente.

Por ejemplo:

> (let* ((foo 7)
       (bar (* 3 foo)))
  (message "`bar' es %d." bar))
‘bar’ es 21.
match-beginning

Devuelve la posición del inicio del texto encontrado por la última búsqueda de una expresión regular.

looking-at

Devuelve t por verdadero si el texto después del punto coincide con el argumento, que debería ser una expresión regular.

eobp

Devuelve t por verdadero si el punto está al final de la parte accesible de un búfer. El final de la parte accesible es el final del búfer sin reduccion; es el final de la parte reducida en un búfer reducido.

Ejercicios con re-search-forward

Conteo: repetición y regexps

La repetición y la búsqueda de expresiones regulares son poderosas herramientas que con frecuencia se usan al escribir código en Emacs Lisp. Este capítulo ilustra el uso de búsqueda de expresiones regulares a través de la construcción de comandos de conteo de palabras usando bucles while y recursión.

La distribución de Emacs estándar contiene una función para contar el número de líneas dentro de una región.

Ciertos tipos de escritura piden el conteo de palabras. Por lo tanto, si escribes un ensayo, puedes limitarte a 800 palabras; si escribes una novela, puedes disciplinarte a escribir 1000 palabras al día. Parece extraño, pero durante mucho tiempo, a Emacs le faltó un comando para contar palabras. Quizás la gente usaba Emacs principalmente para codificar o documentar cosas que no requerian contar las palabras, o quizás se limitaban al comando de conteo de palabras del sistema operativo, wc. Alternativamente, las personas pueden haber seguido la convención de las editoriales y calcular un conteo de palabras dividiendo el número de caracteres de un documento por cinco.

Hay muchas maneras de implementar un comando para contar palabras. Aquí hay algunos ejemplos, que se puede comparar con el comando estándar de Emacs, count-words-region.

La función count-words-example

Un comando de conteo de palabras podría contar palabras en una línea, párrafo, región, o búfer. ¿Qué debe cubrir el comando? Se podría diseñar el comando para contar el número de palabras en un búfer completo. Sin embargo, la tradición en Emacs fomenta la flexibilidad––se puede querer contar solo las palabras en una sección, en lugar de en todo un búfer. Así que tiene más sentido diseñar el comando para contar el número de palabras en una región. Una vez tienes un comando count-words-region, puedes, si lo deseas, contar palabras en un búfer completo marcándolo con C-x h (mark-whole-buffer).

Es evidente que contar palabras es un acto repetitivo: a partir del principio de la región, se cuenta la primer palabra, luego la segunda, despues la tercera, y así sucesivamente, hasta llegar al fin de la región. Esto significa que el conteo de palabras se ajusta idealmente a la recursión o a un bucle while.

Primero, implementaremos el comando para contar palabras con un bucle while, luego con recursión. El comando sera, por supuesto, interactivo.

La plantilla para una definición de función interactiva es, como siempre:

(defun nombre-de-funcion (lista-de-argumentos)
  "documentacion…"
  (expresion-interactiva)
  cuerpo)

Lo que tenemos que hacer es llenar los espacios.

El nombre de la función debe ser auto-explicativo y similar al nombre ya existente count-lines-region. Esto hace que el nombre sea fácil de recordar. count-words-region es una buena elección. Ya que ese nombre ahora se usa para el comando estándar de Emacs para contar palabras, nombraremos a nuestra implementación count-words-example.

La función cuenta palabras dentro de una región. Esto significa que la lista de argumentos debe contener símbolos que esten ligados a las dos posiciones, el principio y fin de la región. Estas dos posiciones se pueden llamar ‘inicio’ y ‘fin’ respectivamente. La primer línea de la documentación deberia ser una solo frase, ya que esto es todo lo que imprimen comandos como apropos. La expresión interactiva tendra la forma ‘(interactive "r")’, puesto que esto provoca que Emacs pase el principio y fin de la región a la lista de argumentos de la función. Todo esto es rutina.

El cuerpo de la función necesita ser escrito para hacer tres tareas: primero, configurar condiciones bajo las cuales el bucle while pueda contar palabras, segundo, ejecutar el bucle while, y tercero, enviar un mensaje al usuario.

Cuando un usuario llama a count-words-example, el punto puede estar al principio o fin de la región. Sin embargo, el proceso de conteo debe empezar al principio de la región. Esto significa que querremos poner el punto hay si no lo esta. Ejecutando (goto-char beginning) aseguramos esto. Por supuesto, querremos devolver el punto a su posición esperada cuando la función termine su trabajo. Por esta razón, el cuerpo debe estar encerrado en una expresión save-excursion.

La parte central del cuerpo de la función consiste en un bucle while en el que una expresión salta el punto hacia adelante palabra por palabra, y otra expresión cuenta estos saltos. Si la prueba-verdadero-o-falso del bucle while es verdadera, el punto salta hacia adelante, y si es falsa el punto estaría al final de la región.

Podríamos usar (forward-word 1) como la expresión para mover el punto hacia adelante palabra por palabra, pero es mas fácil ver lo que Emacs identifica como una ‘palabra’ si usamos una búsqueda de expresión regular.

Una busqueda de expresión regular que encuentra el patron que esta buscando, deja el punto justo después del último carácter de la coincidencia. Esto significa que una sucesión de busquedas exitosas movera el punto hacia adelante palabra por palabra.

Como cuestion práctica, queremos que la busqueda de expresiónes regulares salta a través de los espacios en blanco y tambien sobre la puntuacion entre palabras. Una regexp que no pueda saltar a través de espacios en blanco entre palabras ¡nunca saltara más de una palabra!. Esto significa que la regexp debe incluir el espacio en blanco y los signos de puntuación que siguen a una palabra, si la hay, asi como la palabra misma. (Una palabra puede terminar un búfer y no tener ninguna puntuacion o espacio en blanco, por lo que esa parte de la regexp debe ser opcional.)

Por lo tanto, queremos que el patron de la regexp defina uno o más caracteres constituyentes de la palabra, seguidos opcionalmente, por uno o más caracteres que no sean constituyentes de la palabra. La expresión regular para esto es:

\w+\W*

La tabla sintactica del búfer determina qué caracteres son o no constituyentes de palabras. Para más información sobre la sintaxis, Consulta la Sección Tablas de Sintaxis en El Manual de Referencia de GNU Emacs Lisp.

La expresión se parece a esto:

(re-search-forward "\\w+\\W*")

(Observa las barras invertidas que preceden a ‘w’ y ‘W’. Una barra invertida tiene un significado especial para el intérprete Emacs Lisp. Indica que el siguiente caracter se interpreta de forma diferente a lo habitual. Por ejemplo, los dos caracteres, ‘\n’, representan una ‘nueva línea’, en lugar de una barra invertida seguida por ‘n’. Dos barras invertidas consecutivas representan una ‘barra invertida ordinaria’, así que el interprete de Emacs Lisp termina viendo una sola barra invertida seguida de una letra. Así descubre que la letra es especial.)

Necesitamon un contador para contar cuántas palabras hay; esta variable debe ponerse primero a 0 y luego incrementarse cada vez que Emacs recorra el bucle while. La expresión de incremento es simple:

(setq cuenta (1+ cuenta))

Finalmente, necesitamos informar al usuario cuántas palabras hay en la región. La función message sirve para presentar este tipo de información al usuario. El mensaje tiene que estar redactado de manera que se lea correctamente independientemente de cuantas palabras haya en la región: no queremos decir que “hay 1 palabras en la región”. El conflicto entre singular y plural no es gramatical. Se puede resolver este problema usando una expresión condicional que evalue diferentes mensajes dependiendo del número de palabras en la región. Hay tres posibilidades: niguna palabra en la región, una palabra en la región, y más de una palabra. Esto significa que la forma especial cond es apropiada.

Todo esto lleva a la siguiente definición de función:

;;; ¡Primer versión; tiene errores!
(defun count-words-example (inicio fin)
  "Imprime el número de palabras en la región.
Las palabras se definen como al menos un caracter
constituyente de palabras seguido por al menos un
caracter no constituyente de palabras. La tabla de
sintaxis del búfer determina qué caracteres son."
  (interactive "r")
  (message "Contando palaras en la región ... ")

;;; 1. Establecer las condiciones apropiadas.
  (save-excursion
    (goto-char inicio)
    (let ((cuenta 0))

;;; 2. Ejecutar el bucle while.
      (while (< (point) fin)
        (re-search-forward "\\w+\\W*")
        (setq cuenta (1+ cuenta)))

;;; 3. Enviar un mensaje al usuario.
      (cond ((zerop cuenta)
             (message
              "La región NO tiene palabras."))
            ((= 1 cuenta)
             (message
              "La región tiene 1 palabra."))
            (t
             (message
              "La región tiene %d palabras." cuenta))))))

Como esta escrito, la función funciona, pero no en todas las circunstancias.

El error de espacio en blanco en count-words-example

El comando count-words-example descrito en la sección anterior tiene dos errores, o mas bien, un error con dos manifestaciones. Primero, si se marca una región que contiene solo espacios en blanco en el medio de algún texto el comando count-words-example dira que la región contiene una palabra. Segundo, si se marca una región conteniendo solo espacios en blanco al final del búfer o la porción accesible de un búfer reducido, el comando muestra un mensaje de error que tiene el siguiente aspecto:

Search failed: "\\w+\\W*"

Si estás leyendo esto en GNU Emacs, puedes probar estos errores por ti mismo.

Primero, evalúa la función de la manera habitual para instalarla.

Si quieres, también puedes vincular la funcion al atajo C-c = evaluando lo siguiente:

(global-set-key "\C-c=" 'count-words-example)

Para realizar la primer prueba, establece la marca y punto al principio y fin de la siguiente línea y luego presiona C-c = (o M-x count-words-example si no se vinculo a C-c =):

    uno   dos  tres

Emacs te dira, correctamente, que la región tiene tres palabras.

Repite la prueba, pero coloca la marca al principio de la línea y el punto justo antes de la palabra ‘uno’. De nuevo presiona C-c = (o M-x count-words-example). Emacs deberia decir que la región no tiene palabras, ya que está compuesta solo por espacios en blanco al inicio de la línea. ¡Pero en vez de eso, Emacs informa que la región tiene una palabra!

Para la tercer prueba, copia la línea de ejemplo al final del búfer *scratch* y luego escribe varios espacios al fin de la línea. Coloca la marca justo después de la palabra ‘tres’ y el punto al fin de la línea. (El fin de la línea será el fin del búfer.) Como anteriormente, pulsa C-c = (o M-x count-words-example). De nuevo, Emacs deberia decir que la región no tiene palabras, ya que está compuesta solo por espacios en blanco al final de la línea. En su lugar, Emacs muestra un mensaje de error diciendo ‘Search failed’.

Los dos errores provienen del mismo problema.

Considera la primer manifestación del error, en la que el comando informa que el espacio en blanco al principio de la línea contiene una palabra. Lo que ocurre es lo siguiente: El comando M-x count-words-example mueve el punto al principio de la región. La prueba de while que verifica que el valor del punto sea más pequeño que el valor de fin, es verdadera. En consecuencia, la busqueda regexp busca y encuentra la primera palabra. Eso deja el punto después de la palabra. count se establece a uno. El bucle while se repite; pero esta vez el valor del punto es mayor al valor de fin, el bucle termina; y la función muestra un mensaje diciendo que el número de palabras en la región es uno. En resumen, la expresión regular busca y encuentra la palabra aunque esté fuera de la región marcada.

En la segunda manifestación del error, la región es un espacio en blanco al final del búfer. Emacs dice ‘Search failed’. Lo que ocurre es que la prueba-verdadero-o-falso en el bucle while es verdadera, por lo que se ejecuta la expresión de búsqueda. Pero como no hay más palabras en el buffer, la búsqueda falla.

En ambas manifestaciones del error, la búsqueda se extiende o intenta extenderse fuera de la región.

La solución es limitar la búsqueda a la región––esta es una acción bastante simple, pero como se puede esperar, no es tan simple como se podría pensar.

Como hemos visto, la función re-search-forward toma un patrón de búsqueda como su primer argumento. Pero además de este primer, argumento obligatorio, acepta tres argumentos opcionales. El segundo argumento opcional limita la búsqueda. El tercer argumento opcional, si es t, hace que la función devuelva nil en lugar de la señal de error si la búsqueda falla. El cuarto argumento opcional es un contador de repeticiones. (En Emacs, se puede ver la documentación de una la función pulsando C-h f, seguido por el nombre de la función, y finalmente presionando RET.)

En la definición de count-words-example, el valor del fin de la región se almacena en la variable fin que se pasa como un argumento a la función. De este modo, podemos añadir fin como argumento en la búsqueda de la expresión regular:

(re-search-forward "\\w+\\W*" fin)

Sin embargo, con solo este cambio en la definición de count-words-example y luego de probar la nueva versión en una region de espacios en blanco, se recibirá un mensaje de error ‘Search failed’.

Lo que ocurre es esto: la búsqueda se limita a la región, y falla como se espera porque no hay caracteres constitutivos de palabras en la región. Puesto que falla, se recibe un mensaje de error. Pero no queremos recibir un mensaje de error en este caso; queremos recibir el mensaje de que "La región no tiene palabras".

La solución a este problema es proveer un tercer argumento t a re-search-forward, haciendo que la función regrese nil en lugar de señalar un error si la búsqueda falla.

Sin embargo, aun con este cambio, veremos el mensaje “Contando palaras en la región ... ” y … lo seguiremos viendo …, hasta presionar C-g (keyboard-quit).

Esto es lo que ocurre: la búsqueda se limitada a la región, como antes, y falla porque no hay caracteres constituyentes de palabras en la región, como se esperaba. Por consiguiente, la expresión re-search-forward devuelve nil. No hace nada más. En particular, no mueve el punto, cosa que hace como un efecto secundario si encuentra el objetivo de la búsqueda. Después de que la expresión re-search-forward devuelve nil, se evalua la siguiente expresión en el bucle while. Esta expresión incrementa el contador. Entonces el bucle se repite. La prueba-verdadero-o-falso es verdadera porque el valor del punto aun es menor al valor del final, ya que la expresión re-search-forward no movio el punto. … y el ciclo repite …

La definición de count-words-example requiere otra modificación para hacer que la prueba-verdadero-o-falso del bucle while sea falsa si la búsqueda falla. Dicho de otra manera, hay dos condiciones que deben cumplirse en la prueba-verdadero-o-falso antes de incrementar la variable de conteo: el punto debe estar dentro de la región y la expresión de búsqueda debe haber encontrado una palabra para contar.

Dado que ambas condiciones deben ser ciertas a la vez, la prueba de la región y la expresión de búsqueda, pueden unirse con una forma especial and e incrustarse en el bucle while como la prueba-verdadero-o-falso, asi:

(and (< (point) fin) (re-search-forward "\\w+\\W*" fin t))

(Para obtener mas información sobre and, consulta la Seccion La función kill-new.)

La expresión re-search-forward devuelve t si la búsqueda tiene exito y, como efecto secundario, mueve el punto. En consecuencia, a medida que se encuentran las palabras, el punto se mueve a través de la región. Cuando la expresion de búsqueda no encuentra otra palabra, o cuando el punto llega al final de la región, la prueba-verdadero-o-falso da falso, termina el bucle while, y la función count-words-example muestra uno u otro de sus mensajes.

Después de incorporar estos cambios finales, count-words-example funciona sin errores (¡o al menos, sin los errores que he encontrado!

;;; Versión final: while
(defun count-words-example (inicio fin)
  "Imprime número de palabras en la región."
  (interactive "r")
  (message "Contando palabras en la región ... ")

;;; 1. Establecer las condiciones apropiadas.
  (save-excursion
    (let ((cuenta 0))
      (goto-char inicio)

;;; 2. Ejecutar el bucle while
      (while (and (< (point) fin)
                  (re-search-forward "\\w+\\W*" fin t))
        (setq cuenta (1+ cuenta)))

;;; 3. Enviar un mensaje al usuario.
      (cond ((zerop cuenta)
             (message
              "La región NO tiene palabras."))
            ((= 1 cuenta)
             (message
              "La región tiene 1 palabra."))
            (t
             (message
              "La región tiene %d palabras." cuenta))))))

Contar Palabras Recursivamente

Puedes escribir la función para contar palabras tanto de manera recursiva como con un bucle while. Veamos cómo se hace esto.

En primer lugar, debemos reconocer que la función count-words-example tiene tres trabajos: establecer las condiciones apropiadas para que ocurra el conteo; contar las palabras en la región; y enviar un mensaje al usuario diciendo cuántas palabras hay.

Si escribimos una sola función recursiva para hacer todo, recibiremos un mensaje por cada llamada recursiva. Si la región contiene 13 palabras, recibiremos trece mensajes, uno tras otro. ¡No queremos esto!. En su lugar, debemos escribir dos funciones para hacer el trabajo, una de las cuales (la función recursiva) se utilizara dentro de la otra. Una función configurará las condiciones y mostrara el mensaje; la otra devolverá el conteo de palabras.

Comencemos con la función que hace que se muestre el mensaje. Podemos continuar llamandola count-words-example.

Esta es la función que el usuario llamara. Será interactiva. De hecho, será similar a nuestras versiones previas de esta función, excepto que llamará a contar-palabras-recursivamente para determinar cuántas palabras hay en la región.

Podemos construir facilmente una plantilla para esta función, basada en versiones anteriores:

;; Versión Recursiva; usa búsqueda de expresiónes regulares
(defun count-words-example (inicio fin)
  "documentacion…"
  (expresion-interactiva)

;;; 1. Establecer las condiciones apropiadas.
  (mensaje explicativo)
  (funciones de configuracion

;;; 2. Contar las palabras.
    llamada recursiva

;;; 3. Envía un mensaje al usuario.
    mensaje que proporciona el conteo de palabras))

La definición parece sencilla, excepto que de alguna manera el conteo devuelto por la llamada recursiva debe ser pasado al mensaje que muestra el conteo de palabras. Un poco de reflexion sugiere que esto se puede hacer mediante una expresión let: podemos enlazar una variable en la lista de variables de la expresion let con el número de palabras de la región, tal y como lo devuelve la llamada recursiva; y entonces la expresión cond, podra mostrar el valor al usuario.

Con frecuencia, uno piensa que el enlace dentro de una expresión let es algo secundario al trabajo ‘primario’ de una función. Pero en este caso, se podría considerar que el trabajo ‘primario’ de la función (contar palabras), ocurre dentro de la expresión let.

Usando let, la definición de función se veria asi:

(defun count-words-example (inicio fin)
  "Imprime número de palabras en la región."
  (interactive "r")

;;; 1. Establecer las condiciones apropiadas.
  (message "Contando palabras en la región ... ")
  (save-excursion
      (goto-char inicio)

;;; 2. Contar las palabras.
    (let ((cuenta (contar-palabras-recursivamente fin)))

;;; 3. Enviar un mensaje al usuario.
      (cond ((zerop cuenta)
             (message
              "La región NO tiene palabras."))
            ((= 1 cuenta)
             (message
              "La región tiene 1 palabra."))
            (t
             (message
              "La región tiene %d palabras." cuenta))))))

A continuacion, tenemos que escribir la función de conteo recursivo.

Una función recursiva tiene al menos tres partes: la ‘prueba-hazlo-de-nuevo’, la ‘expresion-del-siguiente-paso’, y la llamada recursiva.

La prueba-hazlo-de-nuevo determina si la función será o no llamada de nuevo. Ya que estamos contando palabras en una región y podemos usar una función que mueve el punto hacia delante para cada palabra, la prueba-hazlo-de-nuevo puede evaluar si el punto todavía está dentro de la región. La prueba-hazlo-de-nuevo debe encontrar el valor del punto y determina si el punto está antes, en, o después del valor del final de la región. Podemos usar la función point para localizar el punto. Claramente, se debe pasar el valor del final de la región como un argumento a la función de conteo recursivo.

Además, la prueba-hazlo-de-nuevo también tiene que probar si la búsqueda encuentra una palabra. Si no lo hace, la función no deberia llamarse de nuevo.

La expresion-del-siguiente-paso cambia un valor de modo que cuando se supone que la función recursiva deja de llamarse así misma, se detiene. Más precisamente, la expresion-del-siguiente-paso cambia un valor en el momento adecuado, la prueba-hazlo-de-nuevo detiene la función recursiva de llamarse a si misma otra vez. En este caso, la expresion-del-siguiente-paso puede ser la expresión que mueve el punto hacia adelante, palabra por palabra.

La tercera parte de una función recursiva es la llamada recursiva.

En algún lugar, también, necesitamos una parte que haga el ‘trabajo’ de la función, una parte que haga el conteo. ¡Una parte vital!

Pero ya tenemos un esquema de la función de conteo recursivo:

(defun contar-palabras-recursivamente (fin-de-region)
  "documentacion…"
  prueba-hazlo-de-nuevo
  expresion-del-siguiente-paso
  llamada recursiva)

Ahora tenemos que rellenar los huecos. Comencemos con los casos más simples: si el punto esta en o mas alla del fin de la región, no puede haber ninguna palabra en la región, así que la función deberia regresar cero. Del mismo modo, si la búsqueda falla no hay mas palabras para contar, por lo que la función tambien deberia regresar cero.

Por otro lado, si el punto esta dentro de la región y la búsqueda tiene éxito, la función deberia volver a llamarse a si misma.

Por lo tanto, la prueba-hazlo-de-nuevo se vería así:

(and (< (point) fin-de-region)
     (re-search-forward "\\w+\\W*" fin-de-region t))

Ten en cuenta que la expresión de búsqueda es parte de la prueba-hazlo-de-nuevo––la función devuelve t si su búsqueda tiene éxito y nil si falla. (Consulta la Seccion El error de espacio en blanco en count-words-example, para una explicación de cómo funciona re-search-forward.)

La prueba-hazlo-de-nuevo es la prueba verdadero-o-falso de una cláusula if. Claramente si la prueba-hazlo-de-nuevo tiene éxito, la parte-then de la cláusula if llamaría a la función de nuevo; pero si falla, la parte-else deveria regresar cero ya que está fuera de la región o la búsqueda falló porque no había palabras que encontrar.

Pero antes de considerar la llamada recursiva, se necesita considerar la expresion-del-siguiente-paso. ¿Qué es esto? Curiosamente, es la parte de la búsqueda de la prueba-hazlo-de-nuevo.

Además de regresar t o nil en la prueba-hazlo-de-nuevo, re-search-forward mueve el punto hacia adelante como un efecto secundario de una búsqueda exitosa. Esta es la acción que cambia el valor de punto para que la función recursiva deje de llamarse a sí misma cuando el punto complete su movimiento a través de la región. Por consiguiente, la expresión re-search-forward es la expresion-del-siguiente-paso.

Entonces, en la plantilla, el cuerpo de la función contar-palabras-recursivamente se ve asi:

(if prueba-hazlo-de-nuevo-y-del-siguiente-paso-combinadas
    ;; then
    llamada-recursiva-regresando-la-cuenta
  ;; else
  regresar-cero)

¿Cómo incorporar el mecanismo que cuenta?

Si no estás acostumbrado a escribir funciones recursivas, una pregunta como esta puede ser un problema. Pero puede y debe abordarse sistemáticamente.

Sabemos que el mecanismo de conteo debe estar asociado de algúna manera con la llamada recursiva. De hecho, dado que la expresion-del-siguiente-paso mueve el punto hacia adelante en una palabra, y dado que se hace una llamada recursiva para cada palabra, el mecanismo de conteo debe ser una expresión que agregue uno al valor devuelto en una llamada a contar-palabras-recursivamente

Considera varios casos:

En el esquema podemos ver que la parte-else del if devuelve cero para el caso en el que no hay palabras. Esto significa que la parte-then del if debe devolver un valor resultante de sumar uno al valor devuelto por el conteo las palabras restantes.

La expresión se vera asi, donde 1+ es una función que añade uno a su argumento.

(1+ (contar-palabras-recursivamente fin-de-region))

La función contar-palabras-recursivamente completa se vera asi:

(defun contar-palabras-recursivamente (fin-de-region)
  "documentacion…"

;;; 1. prueba-hazlo-de-nuevo
  (if (and (< (point) fin-de-region)
           (re-search-forward "\\w+\\W*" fin-de-region t))

;;; 2. parte-then: la llamada recursiva
      (1+ (contar-palabras-recursivamente fin-de-region))

;;; 3. parte-else
    0))

Examinemos como funciona esto:

Si no hay palabras en la región, la parte-else de la expresión if es evaluada y, por tanto, la función devuelve cero.

Si hay una palabra en la región, el valor del punto es menor que el valor de fin-de-region y la búsqueda tiene éxito. En este caso, la prueba-verdadero-o-falso de la expresión if es verdadera, y se evalua la parte-then de la expresión if. Se evalua la expresión de conteo. Esta expresión devuelve un valor (que será el valor devuelto por toda la función) que es la suma de uno añadida al valor devuelto por una llamada recursiva.

Mientras tanto, la expresion-del-siguiente-paso ha hecho que punto salte sobre la primera (y en este caso única) palabra en la región. Esto significa que cuando (contar-palabras-recursivamente fin-de-region) se evalua una segunda vez, como resultado de la llamada recursiva, el valor del punto será igual o mayor que el valor del fin de región. Así que esta vez, contar-palabras-recursivamente devolverá cero. El cero se sumara a uno, y la evaluación original de contar-palabras-recursivamente devolverá uno más cero, que es uno y es la cantidad correcta.

Claramente, si hay dos palabras en la región, la primer llamada a contar-palabras-recursivamente devuelve uno mas el valor devuelto por contar-palabras-recursivamente en una región que contiene la palabra restante––es decir, suma uno a uno, produciendo dos, que es la cantidad correcta.

De manera similar, si hay tres palabras en la región, la primer llamada contar-palabras-recursivamente devuelve uno mas el valor devuelto de contar-palabras-recursivamente en una región que contiene las dos palabras restantes––y así sucesivamente.

Con la documentación completa las dos funciones serian asi:

La función recursiva:

(defun contar-palabras-recursivamente (fin-de-region)
  "Número de palabras entre punto y FIN-DE-REGION."

;;; 1. prueba-hazlo-de-nuevo
  (if (and (< (point) fin-de-region)
           (re-search-forward "\\w+\\W*" fin-de-region t))

;;; 2. parte-then: la llamada recursiva
      (1+ (contar-palabras-recursivamente fin-de-region))

;;; 3. parte-else
    0))

El envoltorio:

;;; Versión Recursiva
(defun count-words-example (inicio fin)
  "Imprime número de palabras en la región.

Las palabras se definen como al menos un caracter
constituyente de palabras seguido por al menos un
caracter no constituyente de palabras. La tabla de
sintaxis del búfer determina qué caracteres son."
  (interactive "r")
  (message "Contando palabras en la región ... ")
  (save-excursion
      (goto-char inicio)
    (let ((cuenta (contar-palabras-recursivamente fin)))
      (cond ((zerop cuenta)
             (message
              "La región NO tiene palabras."))
            ((= 1 cuenta)
             (message "La región tiene 1 palabra."))
            (t
             (message
              "La región tiene %d palabras." cuenta))))))

Ejercicio: Conteo de signos de puntuación

Usando un bucle while, escribe una función para contar el número de signos de puntuación en una región––punto, coma, punto y coma, dos puntos, signos de interrogacion y exclamación. Has lo mismo usando recursión.

Contando palabras en una defun

Nuestro siguiente proyecto es contar el número de palabras en una definición de función. Claramente, esto se puede hacer usando alguna variante de count-words-example. Consulta la Seccion Conteo: repetición y regexps. Si solo vamos a contar las palabras en una definición, es bastante fácil marcar la definición con el comando C-M-h (mark-defun), y luego llamar a count-words-example.

Sin embargo, soy más ambicioso: quiero contar las palabras y símbolos en todas las definiciónes del codigo de Emacs y despues imprimir un grafico que muestre cuántas funciones hay de cada tamaño: cuántas contienen de 40 a 49 palabras o símbolos, cuántas contienen de 50 a 59 palabras o símbolos, y así sucesivamente. A menudo he tenido curiosidad por saber cuanto abarca una función típica, y esto lo dira.

Descrito en una frase, el proyecto del histograma es desalentador; pero dividido en muchos pequeños pasos, dando uno a uno a la vez, el proyecto se vuelve menos atemorizante. Consideremos cuales deberian ser los pasos a seguir:

¡Este es un gran proyecto! Pero si tomamos cada paso lentamente, no será difícil.

¿Qué contar?

Cuando empezamos a pensar en como contar las palabras en una definición de función, la primera pregunta es (o deberia ser) ¿qué vamos a contar? Cuando hablamos de ‘palabras’ con repecto a una definición de función Lisp, en realidad estamos hablando, en gran parte, de ‘símbolos’. Por ejemplo, la siguiente función multiplicar-por-siete contiene los cinco símbolos defun, multiplicar-por-siete, numero, *, y 7. Además, en la cadena de documentación, contiene cuatro palabras ‘Multiplicar’, ‘NUMERO’, ‘por’, y ‘siete’. El símbolo ‘número’ se repite, por lo que la definición contiene un total de diez palabras y símbolos.

(defun multiplicar-por-siete (numero)
  "Multiplica NUMERO por siete."
  (* 7 numero))

Sin embargo, si marcamos la definición multiplicar-por-siete con C-M-h (mark-defun), y luego llamamos a count-words-example en ella, encontraremos que count-words-example afirma que la definición tiene once palabras, no diez ¡Alguna cosa está mal!

El problema es doble: count-words-example no cuenta el ‘*’ como una palabra, y cuenta el símbolo unico, multiplicar-por-siete, conteniendo tres palabras. Los guines se tratan como si fueran espacios entre palabras en lugar de conectores entre palabras ‘multiplicar-por-siete’ se cuenta como si fuese escrito ‘multiplicar por siete’.

La causa de esta confusión es la expresión regular que busca la definición count-words-example que mueve el punto hacia adelante palabra por palabra. En la versión canónica de count-words-example, la regexp es:

"\\w+\\W*"

Esta expresión regular es un patrón que define uno o más caracteres constituyentes de palabras, posiblemente seguidos por uno o más caracteres que no son constituyentes de palabras. Lo que se entiende por ‘caracteres constituyentes de palabras’ nos lleva a la cuestión de la sintaxis, que merece una sección en por sí misma.

¿Qué constituye una palabra o símbolo?

Emacs trata diferentes caracteres como pertenecientes a diferentes categorías sintacticas. Por ejemplo, la expresión regular, ‘\\w+’, es un patrón que especifica uno o más caracteres constituyentes de palabras. Los caracteres constituyentes de palabras son miembros de una categoría sintactica. Otras categoría sintactica incluye la clase de caracteres de puntuación, como el punto y la coma, y la clase de caracteres de espacio en blanco, como el espacio en blanco o el tabulador. (Para más información, consulta la Seccion Tablas de Sintaxis en El Manual de Referencia GNU Emacs Lisp.)

Las tablas sintacticas especifican qué caracteres pertenecen a qué categorías. Normalmente un guión no está especificado como un ‘caracter constiyente de una palabra’. En su lugar, se especifica como perteneciente a la ‘clase de caracteres que son parte de los nombres de símbolos, pero no de las palabras’. Esto significa que la función count-words-example la trata del mismo modo que trata un espacio en blanco entre palabras, por lo qué la funcion count-words-example cuenta a ‘multiplicar-por-siete’ como tres palabras.

Hay dos maneras de hacer que Emacs cuente ‘multiplicar-por-siete’ como un símbolo: modificar la tabla sintactica o modificar la expresión regular.

Se podría redefinir un guión como un caracter constituyente de una palabra modificando la tabla de sintaxis que Emacs guarda para cada modo. Esta acción serviría a nuestro propósito, excepto que un guion es simplemente el caracter más común dentro de los símbolos que no es son tipicamente un caracter constituyente de una palabra; hay otros, también.

Alternativamente, podemos redefinir la regexp usada en la definición de count-words-example para incluir símbolos. Este procedimiento tiene el mérito de la claridad, pero la tarea es un poco dificil.

La primera parte es bastante simple: el patrón debe coincidir con “al menos un carácter que sea una palabra o un símbolo constituyente”. Asi:

"\\(\\w\\|\\s_\\)+"

El ‘\\(’ es la primera parte del constructor de agrupacion que incluye el ‘\\w’ y el ‘\\s_’ como alternativas, separadas por ‘\\|’. El ‘\\w’ coincide con cualquier caracter constituyente de una palabras y ‘\\s_’ con cualquier caracter que forme parte de un símbolo no constituyente de palabras. El signo ‘+’ a continuacion de la agrupacion indica que los caracteres que componen la palabra o el símbolo deben coincidir al menos una vez.

Sin embargo, la segunda parte de la regexp es más difícil de diseñar. Lo que queremos es seguir la primera parte con “opcionalmente uno o más caracteres que no constituyen una palabra o símbolo”. Al principio, pense que esto se podría definir con lo siguiente:

"\\(\\W\\|\\S_\\)*"

Las mayúsculas ‘W’ y ‘S’ coinciden con caracteres que no son parte de palabras o símbolos. Desafortunadamente, esta expresión coincide con cualquier caracter que no sea parte de una palabra o simbolo. ¡Esto coincide con cualquier caracter!

Entonces note que cada palabra o símbolo en mi región de prueba iba seguido de un espacio en blanco (espacio en blanco, tabulador, o línea nueva). Así que intente colocar un patrón para que coincidiese con uno o más espacios en blanco después del patrón para una o más palabras o símbolos constituyentes. Esto tambien falló. Las palabras y los símbolos suelen estar separados por espacios en blanco, pero en el código real los paréntesis pueden ir despues de los símbolos y la puntuación puede seguir a las palabras. Así que finalmente, diseñe un patrón en el que los componentes de la palabra o simbolo van seguidos opcionalmente por caracteres que no son espacios en blanco, seguidos a su vez por espacios en blanco opcionales.

Aquí está la expresión regular completa:

"\\(\\w\\|\\s_\\)+[^ \t\n]*[ \t\n]*"

La función contar-palabras-en-definicion

Hemos visto que hay varias maneras de escribir la función count-word-region. Para escribir contar-palabras-en-definicion, basta con adaptar una de estas versiones.

La versión que utiliza un bucle while es fácil de comprender, así que voy a adaptarla. Debido a que contar-palabras-en-definicion formara parte de un programa más complejo, no necesita ser interactivo y ni mostrar un mensaje, solamente devolver el conteo. Estas consideraciones simplifican un poco la definición.

Por otro lado, contar-palabras-en-definicion se utilizara dentro de un buffer que contiene definiciones de función. Por consiguiente, es razonable pedir que la función determine si se llama cuando el punto está dentro de una definición de función, y si lo esta, que devuelva el conteo para esa definición. Esto añade complejidad a la definición, pero nos ahorra la necesidad de pasar argumentos a la función.

Estas consideraciones nos llevan a preparar la siguiente plantilla:

(defun contar-palabras-en-definicion ()
  "documentacion…"
  (configuracion
     (bucle while)
   regresar conteo)

Como de costumbre, nuestro trabajo es rellenar los huecos.

Primero, la configuración.

Suponemos que esta función se llamara dentro de un búfer que contiene definiciones de función. El punto estara o no dentro de una definición de función. Para que contar-palabras-en-definicion funcione, el punto debe moverse al principio de la definición, un contador debe empezar en cero, y el bucle de conteo debe parar cuando el punto alcance el final de la definición.

La función beginning-of-defun busca hacia atrás un delimitador de apertura como ‘(’ al principio de una línea, y mueve el punto a esa posición, o sino al límite de la búsqueda. En la práctica, esto significa que beginning-of-defun mueve el punto al principio de la funcion que lo rodea o a la anterior función, o bien al principio del buffer.

El bucle while requiere un contador para registrar las palabras o símbolos que se estan contando. Una expresión let puede ser usada para crear una variable local para este propósito, y vincularse a un valor inicial de cero.

La función end-of-defun opera como beginning-of-defun excepto que mueve el punto al fin de la definición. end-of-defun puede usarse como parte de una expresión que determina la posición del fin de la definición.

La configuración para contar-palabras-en-definicion toma forma rápidamente: primero movemos el punto al principio de la definición, luego creamos una variable local para almacenar el conteo, y finalmente, registramos la posición del final de la definición para que el bucle while sepa cuando terminar.

El código es asi:

(beginning-of-defun)
(let ((cuenta 0)
      (fin (save-excursion (end-of-defun) (point))))

El código es simple. Es probable que la única pequeña complicación este en fin, que se vincula a la posición del fin de la definición con una expresión save-excursion que devuelve el valor de end-of-defun que a su vez mueve temporalmente el punto al final de la definición.

La segunda parte de contar-palabras-en-definicion, después de la configuración, es el bucle while.

El bucle debe contener una expresión que mueva el punto hacia adelante palabra por palabra y símbolo por símbolo, y otra expresión que cuente los saltos. La prueba-verdadero-o-falso del bucle while debe ser verdadero siempre y cuando el punto salte hacia adelante, y falso si el punto esta al final de la definición. Ya hemos redefinido la expresión regular para esto, así que el bucle es sencillo:

(while (and (< (point) fin)
            (re-search-forward
             "\\(\\w\\|\\s_\\)+[^ \t\n]*[ \t\n]*" fin t)
  (setq cuenta (1+ cuenta)))

La tercera parte de la definición devuelve el numero de palabras y símbolos. Esta parte es la última expresión dentro del cuerpo de la expresión let, y puede ser, simplemente la variable local cuenta, que al evaluarse devuelve el conteo.

En conjunto, la definición contar-palabras-en-definicion luce así:

(defun contar-palabras-en-definicion ()
  "Devuelve el número de palabras y símbolos en una defun."
  (beginning-of-defun)
  (let ((cuenta 0)
        (fin (save-excursion (end-of-defun) (point))))
    (while
        (and (< (point) fin)
             (re-search-forward
              "\\(\\w\\|\\s_\\)+[^ \t\n]*[ \t\n]*"
              fin t))
      (setq cuenta (1+ cuenta)))
    cuenta))

¿Cómo probar esto? La función no es interactiva, pero es fácil poner un envoltorio alrededor de la función para hacerla interactiva; podemos usar casi el mismo código que el de la versión recursiva de count-words-example:

;;; Versión Interactiva.
(defun contar-palabras-en-defun ()
  "Número de palabras y símbolos en una definición de función."
  (interactive)
  (message
   "Contando palabras y símbolos en la definición de función ... ")
  (let ((cuenta (contar-palabras-en-definicion)))
    (cond
     ((zerop cuenta)
      (message
       "La definición NO tiene palabras o símbolos."))
     ((= 1 cuenta)
      (message
       "La definición tiene 1 palabra o símbolo."))
     (t
      (message
       "La definición tiene %d palabras o símbolos." cuenta)))))

Reutilicemos C-c = como un conveniente atajo:

(global-set-key "\C-c=" 'contar-palabras-en-defun)

Ahora podemos probar contar-palabras-en-defun: instala ambas funciones contar-palabras-en-definicion y contar-palabras-en-defun, y asigna el atajo, luego coloca el cursor dentro de la siguiente definición:

(defun multiplicar-por-siete (numero)
  "Multiplicar NUMERO por siete."
  (* 7 numero))

¡Éxito! La definición tiene 10 palabras y símbolos.

El siguiente problema es contar el numero de palabras y símbolos en varias definiciones en un mismo fichero.

Contar varias defuns dentro de un fichero

Un fichero como simple.el puede tener cientos o más definiciones dentro de el. Nuestro objetivo a largo plazo es recopilar estadísticas de muchos ficheros, pero como primer paso, nuestro objetivo inmediato es recoger estadísticas de un solo fichero.

La información será una serie de números, siendo cada número la longitud de una definición de función. Podemos almacenar los números en una lista.

Sabemos que querremos incorporar la información relativa a un fichero con información sobre muchos otros ficheros; esto significa que la función para contar la longitudes de las definiciones solo necesita devolver la lista de longitudes. No necesita ni debe mostrar ningun mensaje.

Los comandos de conteo de palabras contienen una expresión para mover el punto palabra a palabra y otra expresión para contar los saltos. La función para devolver la longitud de las definiciones puede ser diseñada para trabajar del mismo modo, con una expresión para mover el punto hacia adelante definición por definición y otra expresión para construir la lista de longitudes.

Esta afirmacion del problema hace que sea elemental escribir la definición de función. Claramente, empezaremos el conteo al principio del fichero, por lo que el primer comando será (goto-char (point-min)). Lo siguiente, es iniciar el bucle while; y la puerba verdadero-o-falso del bucle puede ser una regexp para la siguiente definición de función––siempre y cuando la búsqueda tenga éxito, el punto se movera hacia adelante y luego se evaluara el cuerpo del bucle. El cuerpo necesita una expresión que construya la lista de longitudes. cons, el comando de construccion de listas, puede utilizarce para crear la lista. Esto es casi todo lo que hay.

Este fragmento de código tendria el siguiente aspecto:

(goto-char (point-min))
(while (re-search-forward "^(defun" nil t)
  (setq lista-de-longitudes
        (cons (contar-palabras-en-definicion) lista-de-longitudes)))

Dejamos fuera el mecanismo para encontrar el fichero que contiene las definiciones de función.

En ejemplos anteriores, usabamos este fichero, o el fichero Info, o cambiamos de un búfer a otro, como el búfer *scratch*.

Encontrar un fichero es un nuevo proceso que aun no hemos discutido.

Encontrar un fichero

Para encontrar un fichero en Emacs, se usa el comando C-x C-f (find-file). Este comando es casi, pero no del todo adecuado para el problema de las longitudes.

Veamos el codigo fuente de find-file:

(defun find-file (filename)
  "Edita el fichero FILENAME.
Cambia a un búfer visitando el fichero FILENAME,
creando uno si no existe ya."
  (interactive "FFind file: ")
  (switch-to-buffer (find-file-noselect filename)))

(La definición de la versión más reciente de find-file permite especificar comodines especiales para visitar múltiples ficheros; que hacen la definición más compleja y no la discutiremos aquí, ya que no es relevante. Se puede ver el codigo usando M-. (find-tag) o C-h f (describe-function).)

La definición que estoy mostrando posee una documentación corta, pero completa y una expresion interactiva que pide un nombre de fichero cuando se usa el comando interactivamente. El cuerpo de la definición contiene dos funciones, find-file-noselect y switch-to-buffer.

De acuerdo con su documentación, tal y como se muestra con C-h f (el comando describe-function), la función find-file-noselect lee el fichero nombrado en un búfer y devuelve el búfer. (Su versión más reciente incluye un argumento comodín opcional, así como otro para leer un fichero literalmente y otro para suprimir mensajes de advertencia. Estos argumentos opcionales son irrelevantes.)

Sin embargo, la función find-file-noselect no selecciona el búfer en el que se pone el fichero. Emacs no cambia su atención (o la tuya si estás usando find-file-noselect) al búfer seleccionado. Esto es lo que hace switch-to-buffer: cambia el búfer al que se dirige la atención de Emacs; y cambia el búfer mostrado en la ventana al nuevo búfer. Hemos discutido el cambiando de búfer en otra parte. (Consulta la Seccion Cambiando búfers.)

En este proyecto de histograma, no necesitamos mostrar cada fichero en la pantalla ya que el programa determina el tamaño de cada definición dentro de el. En lugar de emplear switch-to-buffer, podemos trabajar con set-buffer, que redirige la atención del programa a un búfer diferente, pero no lo muestra en pantalla. Así en vez llamar a find-file para hacer el trabajo, debemos escribir nuestra propia expresión.

La tarea es fácil: usar find-file-noselect y set-buffer.

lista-de-longitudes-en-fichero en detalle

El núcleo de la función lista-de-longitudes-en-fichero es un bucle while que contiene una función para mover el punto hacia delante ‘defun a defun’ y una función para contar el número de palabras y símbolos en cada definicion de funcion. Este núcleo debe ser rodeado por funciones que realizan otras tareas varias, incluyendo encontrar el fichero, y asegurarse que el punto empieza al principio del fichero. La definición de la función se ve asi:

(defun lista-de-longitudes-en-fichero (nombre-de-fichero)
  "Devuelve la lista de longitudes de las definiciones dentro de NOMBRE-DE-FICHERO.
La lista devuelta es una lista de números.
Cada número es el número de palabras o
símbolos en una definición."
  (message "Trabajando en `%s' ... " nombre-de-fichero)
  (save-excursion
    (let ((buffer (find-file-noselect nombre-de-fichero))
          (lista-de-longitudes))
      (set-buffer buffer)
      (setq buffer-read-only t)
      (widen)
      (goto-char (point-min))
      (while (re-search-forward "^(defun" nil t)
        (setq lista-de-longitudes
              (cons (contar-palabras-en-definicion) lista-de-longitudes)))
      (kill-buffer buffer)
      lista-de-longitudes)))

A la función se le pasa un argumento, el nombre del fichero en el que trabajará. Tiene cuatro líneas de documentación, pero ninguna especificacion interactiva. Ya que a la gente le preocupa que un ordenador se estropee si no ve nada, la primera línea del cuerpo es un mensaje de aviso.

La siguiente línea contiene un save-excursion que devuelve la atencion de Emacs al búfer actual cuando la función se completa. Esto es útil en caso de incorporar esta función dentro de otra función que suponga que el punto se restaura al búfer original.

En la varlist de la expresión let, Emacs busca el fichero y enlaza la variable local buffer al búfer que contiene el fichero. Al mismo tiempo, Emacs crea lista-de-longitudes como una variable local.

A continuacion, Emacs cambia su atención al búfer.

En la siguiente línea, Emacs hace que el búfer sea de solo lectura. Idealmente, esta línea no es necesaria. Ninguna de las funciones para contar palabras y símbolos en una definición de función debe cambiar el búfer. Ademas, el búfer no va a guardarse, incluso si se ha modificado. Esta línea es enteramente la consecuencia de una gran cautela, quizás excesiva. La razón de la precaución es que esta función y aquellas a las que llama trabajaran en el codigo fuente de Emacs y es un inconveniente si se modifican de forma inadvertida. No hace falta decir que no me di cuenta de la necesidad de esta línea hasta que un experimento salio mal y empezó a modificar mis ficheros…

Luego viene una llamada para extender el búfer si esta reducido. Esta función normalmente es innecesaria––Emacs crea un búfer nuevo si no existe ninguno; pero si hay un búfer visitando el fichero, Emacs devuelve ese búfer. En este caso, el búfer puede estar reducido y debe extenderse. Si quisieramos ser completamente ‘amigables con el usuario’, nos encargariamos de guardar la restricción y la ubicacion del punto, pero no lo haremos.

La expresión (goto-char (point-min)) mueve el punto al principio del búfer.

Luego viene un bucle while en el que se realiza el ‘trabajo’ de la función. En el bucle, Emacs determina la longitud de cada definición y construye una lista de longitudes que contiene la información.

Emacs mata el búfer después de trabajar a través de el. Esto es para ahorrar espacio dentro de Emacs. Mi versión de GNU Emacs 19 contenía 300 ficheros de codigo fuente de interés; GNU Emacs 22 contiene mas de mil ficheros de codigo fuente. Otra función aplicará lista-de-longitudes-en-fichero a cada uno de los ficheros.

Finalmente, la última expresión dentro de la expresión let es la variable lista-de-longitudes; su valor se devuelve como el valor de toda la función.

Se puede probar esta función instalándola de la forma habitual. A continuacion coloca tu cursor después de la siguiente expresión y presiona C-x C-e (eval-last-sexp).

(lista-de-longitudes-en-fichero
 "/usr/local/share/emacs/22.1.1/lisp/emacs-lisp/debug.el")

Puede que necesites cambiar la ruta del fichero; la de aqui es para GNU Emacs versión 22.1.1. Para cambiar la expresión, cópiala al búfer *scratch* y edítala.

Ademas, para ver el la longitud completa de la lista, en lugar de una versión truncada es posible tener que evaluar lo siguiente:

(custom-set-variables '(eval-expression-print-length nil))

(Consulta la Seccion Especificar variables usando defcustom. A contituacion evalúa la expresión lista-de-longitudes-en-fichero.)

La lista de longitudes para debug.el tarda menos de un segundo en producirse y se ve asi en GNU Emacs 22:

(83 113 105 144 289 22 30 97 48 89 25 52 52 88 28 29 77 49 43 290 232 587)

Usando mi vieja máquina, con la versión 19 tambien con debug.el demora siete segundos en producir esto:

(75 41 80 62 20 45 44 68 45 12 34 235)

La versión nueva de debug.el contiene más defuns que la anterior; y mi nueva máquina es mucho más rápida que la vieja.

Ten en cuenta que el tamaño de la última definición en el fichero es la primera de la lista.

Contar palabras en defuns en diferentes ficheros

En la sección anterior, creamos una función que devuelve una lista de las longitudes de cada definición en un fichero. Ahora, queremos definir una función para devolver una lista maestra de las longitudes de las definiciones en una lista de ficheros.

Trabajar en cada una de las listas de ficheros es un acto repetitivo, por lo que podemos usar un bucle while o recursión.

El diseño utilizando un bucle while es rutinario. El argumento que se pasa a la función es una lista de ficheros. Como vimos anteriormente (Ver Sección Un bucle while y una lista), se puede escribir un bucle while de un modo que el cuerpo del bucle se evalue si tal lista contiene elementos, pero que deba salir del bucle si la lista está vacía. Para que este diseño funcione, el cuerpo del bucle debe contener una expresión que acorte la lista con cada evaluacion del cuerpo, de modo que finalmente la lista esté vacía. La técnica usual es asignar el valor de la lista al valor del cdr de la lista cada vez que se evalua el cuerpo.

La plantilla se ve así:

(while comprobar-si-la-lista-esta-vacia
  cuerpo
  asignar-lista-al-cdr-de-la-lista)

Ademas, recordemos que un bucle while devuelve nil (el resultado de evaluar la prueba-verdadero-o-falso), no el resultado de ninguna evaluación dentro de su cuerpo. (Las evaluaciones dentro del cuerpo del bucle se hacen por sus efectos secundarios.) Sin embargo, la expresión que establece la lista de longitudes es parte del cuerpo––y ese es valor que queremos que devuelva la función como un todo. Para hacer esto, rodeamos el bucle while con una expresión let, y disponemos que el último elemento de la expresión let contiene el valor de lista de longitudes. (Consulta la Seccion Ejemplo con contador incremental.)

Estas consideraciones nos llevan directamente a la función en sí:

;;; Usar bucle while.
(defun lista-de-longitudes-de-muchos-ficheros (lista-de-ficheros)
  "Devuelve la lista de longitudes de defuns en LISTA-DE-FICHEROS."
  (let (lista-de-longitudes)

;;; prueba-verdadero-o-falso
    (while lista-de-ficheros
      (setq lista-de-longitudes
            (append
             lista-de-longitudes

;;; Genera una lista de longitudes.
             (lista-de-longitudes-en-fichero
              (expand-file-name (car lista-de-ficheros)))))

;;; Reducir la lista de ficheros.
      (setq lista-de-ficheros (cdr lista-de-ficheros)))

;;; Devuelve el valor final de la lista de longitudes.
    lista-de-longitudes))

expand-file-name es una función nativa que convierte el nombre de un fichero a su nombre de ruta absoluta. La función emplea el nombre del directorio en el que se llama la función.

De este modo, si se llama a expand-file-name dentro de debug.el cuando Emacs está visitando el directorio /usr/local/share/emacs/22.1.1/lisp/emacs-lisp/

debug.el

se convierte en

/usr/local/share/emacs/22.1.1/lisp/emacs-lisp/debug.el

El único otro nuevo elemento de esta definición de función es la todavía no estudiada función append, que merece una breve sección.

La función append

La función append une una lista a otra. De este modo,

(append '(1 2 3 4) '(5 6 7 8))

produce la lista

(1 2 3 4 5 6 7 8)

Asi es exactamente cómo queremos unir dos listas de longitudes producidas por lista-de-longitudes-en-fichero entre si. Los resultados contrastan con cons,

(cons '(1 2 3 4) '(5 6 7 8))

que construye una nueva lista en la que el primer argumento de cons se convierte en el primer elemento de la nueva lista:

((1 2 3 4) 5 6 7 8)

Contar palabras recursivamente en diferentes ficheros

Ademas de un bucle while, podemos trabajar en cada lista de ficheros con recursión. Una versión recursiva de lista-de-longitudes-de-muchos-ficheros es corta y simple.

La función recursiva tiene las partes usuales: la ‘prueba-hazlo-de-nuevo’, la ‘expresion-del-siguiente-paso’, y la llamada recursiva. La ‘prueba-hazlo-de-nuevo’ determina si la función debe volver a llamarse a si misma, lo que hará si la lista-de-ficheros contiene algun elemento restante; la ‘expresion-del-siguiente-paso’ reasigna la lista-de-ficheros con su mismo cdr, por lo que eventualmente la lista estara vacía; y la llamada recursiva se llamara a si misma en la lista mas corta. ¡La función completa es mas corta que esta descripción!

(defun lista-de-longitudes-de-muchos-ficheros-recursiva (lista-de-ficheros)
  "Devuelve la lista de longitudes de cada defun en LISTA-DE-FICHEROS."
  (if lista-de-ficheros                    ; prueba-hazlo-de-nuevo
      (append
       (lista-de-longitudes-en-fichero
        (expand-file-name (car lista-de-ficheros)))
       (lista-de-longitudes-de-muchos-ficheros-recursiva
        (cdr lista-de-ficheros)))))

En una frase, la función devuelve la lista de longitudes para la primer lista-de-ficheros adjunta al resultado de llamarse así misma al resto de la lista-de-ficheros.

Aquí hay una prueba de lista-de-longitudes-de-muchos-ficheros-recursiva, junto con los resultados de ejecutar lista-de-longitudes-en-fichero en cada uno de los ficheros individualmente.

Instala lista-de-longitudes-de-muchos-ficheros-recursiva y lista-de-longitudes-en-fichero, y luego evalúa las siguientes expresiones. Es posible que necesites cambiar las rutas a los ficheros; las que aquí se incluyen funcionan cuando las fuentes de Emacs se encuentran en sus lugares habituales. Para cambiar las expresiones, cópialas al búfer *scratch*, edítalas y evalualas.

(Estos resultados son para ficheros de la versión 22.1.1; los ficheros de otras versiones puede producir resultados diferentes.)

> (cd "/usr/local/share/emacs/22.1.1/")
> (lista-de-longitudes-en-fichero "./lisp/macros.el")
(283 263 480 90)
> (lista-de-longitudes-en-fichero "./lisp/mail/mailalias.el")
(38 32 29 95 178 180 321 218 324)
> (lista-de-longitudes-en-fichero "./lisp/makesum.el")
(85 181)
> (lista-de-longitudes-de-muchos-ficheros-recursiva
 '("./lisp/macros.el"
   "./lisp/mail/mailalias.el"
   "./lisp/makesum.el"))
(283 263 480 90 38 32 29 95 178 180 321 218 324 85 181)

La función lista-de-longitudes-de-muchos-ficheros-recursiva produce la salida que queremos.

El siguiente paso es preparar los datos de la lista para visualizarlos en un grafico.

Preparar los datos para visualizarlos en un grafico

La función lista-de-longitudes-de-muchos-ficheros-recursiva devuelve una lista de números. Cada número registra la longitud de una definición de función. Lo que tenemos que hacer ahora es transformar estos datos en una lista de números adecuados para generar un grafico. La nueva lista dira cuántas definiciones de funcion contienen menos de 10 palabras y símbolos, cuantas entre 10 y 19, cuántas entre 20 y 29, y así sucesivamente.

En resumen, necesitamos revisar la lista de longitudes producida por la función lista-de-longitudes-de-muchos-ficheros-recursiva y contar el número de definiciones dentro de cada rango, y producir una lista de esos números.

Basado en lo que hemos hecho antes, podemos preveer que no sera demaciado difícil escribir una función que redusca la lista de longitudes con ‘cdrs’, mire cada elemento, determine en que rango de longitud esta, e incremente un contador para ese rango.

Sin embargo, antes de empezar a escribir tal función, debemos considerar las ventajas de primero ordenar la lista de longitudes, de modo que los números se ordenen del más pequeño al más grande. En primer lugar, la ordenacion facilitara el conteo de los números en el mismo rango, ya que dos números adyacentes estaran en el mismo rango de longitud o en rangos adyacentes. Segundo, inspeccionando una lista ordenada, podemos descubrir el número mas alto y el mas bajo, y asi determinar el rango de longitud mas grande y mas pequeño que necesitaremos.

Ordenando listas

Emacs contiene una función para ordenar listas, llamada (como se podría adivinar) sort. La función sort toma dos argumentos, la lista a ordenar, y un predicado que determina si el primero de dos elementos de la lista es “menor” que el segundo.

Como vimos anteriormente (Ver Sección Usando el tipo incorrecto de objeto como argumento), un predicado es una función que determina si alguna propiedad es verdadera o falsa. La función sort reordenará una lista de acuerdo a cualquier propiedad que use el predicado; esto significa que sort puede usarse para ordenar listas no numéricas por un criterio no numérico––puede, por ejemplo, ordenar la lista alfabeticamente.

La función < se utiliza para ordena una lista numérica. Por ejemplo,

(sort '(4 8 21 17 33 7 21 7) '<)

produce esto:

(4 7 7 8 17 21 21 33)

(Ten en cuenta que en este ejemplo, ambos argumentos se citan para que no se evaluen los símbolos antes de pasarlos a sort como argumentos.)

Ordenar la lista devuelta por la función lista-de-longitudes-de-muchos-ficheros-recursiva es sencillo; utilizando la función <:

(sort
 (lista-de-longitudes-de-muchos-ficheros-recursiva
  '("./lisp/macros.el"
    "./lisp/mailalias.el"
    "./lisp/makesum.el"))
 '<)

que produce:

(29 32 38 85 90 95 178 180 181 218 263 283 321 324 480)

(Nota que en este ejemplo, el primer argumento para sort no se cita, ya que la expresión debe ser evaluada para producir la lista que se pasada a sort.)

Creando una lista de ficheros

La función lista-de-longitudes-de-muchos-ficheros-recursiva requiere una lista de ficheros como argumento. Para nuestros ejemplos de prueba, hemos construido una lista de este tipo a mano; pero el directorio fuente de Emacs Lisp es demasiado grande para que podamos hacerlo. En su lugar, escribiremos una función para hacer el trabajo por nosotros. En esta función, usaremos tanto un bucle while como una llamada recursiva.

En las viejas versiones de GNU Emacs no hacia falta escribir esta función, ya que todos los ficheros ‘.el’ estaban colocados en un directorio. En su lugar, pudimos usar la función directory-files, que lista los nombres de los ficheros que coinciden con un patron especifico dentro de un solo directorio.

Sin embargo, las versiones recientes de Emacs colocan los ficheros de Emacs Lisp en subdirectorios del directorio lisp de nivel superior. Esta reorganizacion facilita la navegación. Por ejemplo, todos los ficheros relacionados con el correo están en el subdirectorio mail. Pero al mismo tiempo, esta estructura nos obliga a crear una funcion de listado de ficheros que descienda dentro de los subdirectorios.

Podemos crear esta función, llamada ficheros-en-el-siguiente-directorio, usando funciones familiares como car, nthcdr, y substring en conjunción con una función existente llamada directory-files-and-attributes. Esta última función no solo listas todos los ficheros en un directorio, incluyendo los nombres de los subdirectorios, también sus atributos.

Repitamos nuestro objetivo: crear una función que nos permita alimentar a lista-de-longitudes-de-muchos-ficheros-recursiva con nombres de fichero en una lista parecida a esta (pero con más elementos):

("./lisp/macros.el"
 "./lisp/mail/rmail.el"
 "./lisp/makesum.el")

La función directory-files-and-attributes devuelve una lista de listas. Cada una de las listas de la lista principal consiste de 13 elementos. El primer elemento es una cadena que contiene el nombre del fichero––que, en GNU/Linux, puede ser un ‘fichero de directorio’, es decir, un fichero con los atributos especiales de un directorio. El segundo elemento de la lista es t para un directorio, una cadena para el enlace simbólico (la cadena es el nombre al que enlaza), o nil.

Por ejemplo, el primer fichero ‘.el’ en el directorio abbrev.el es abbrev.el. Su nombre es /usr/local/share/emacs/22.1.1/lisp/abbrev.el y no es un directorio o un enlace simbólico.

Asi es cómo directory-files-and-attributes lista este fichero y sus atributos:

("abbrev.el"
 nil
 1
 1000
 100
 (20615 27034 579989 697000)
 (17905 55681 0 0)
 (20615 26327 734791 805000)
 13188
 "-rw-r--r--"
 nil
 2971624
 773)

Por otro lado, mail/ es un directorio dentro del directorio lisp/. El inicio del listado se ve asi:

("mail"
 t
 
 )

(Para conocer los diferentes atributos, mira en la documentación de file-attributes. Ten en mente que la función file-attributes no lista el nombre del fichero, por lo que el primer elemento es el segundo elemento de directory-files-and-attributes.)

Quisieramos que nuestra nueva funcion, ficheros-bajo-el-dirirectorio, listara los ficheros ‘.el’ en el directorio que le pedimos inspeccionar, y en cualquier directorio debajo de ese directorio.

Esto nos da una pista de como construir ficheros-bajo-el-dirirectorio: dentro de un directorio, la función deberia añadir los ficheros ‘.el’ a una lista; y si, dentro de un directorio, la función se encuentra con un subdirectorio, ir dentro de este subdirectorio y repetir sus acciones.

Sin embargo, debemos tener en cuenta que cada directorio contiene un nombre que hace referencia a sí mismo, llamado ., (“punto”) y un nombre que hace referencia a su directorio padre, llamado .. (“doble punto”). (En /, el directorio raíz, .. se refiere así mismo, ya que / no tiene padre.) Claramente, no queremos que nuestra función ficheros-bajo-el-dirirectorio ingrese a estos directorios, puesto que nos llevaran directamente o indirectamente, al directorio actual.

Por consiguiente, nuestra función ficheros-bajo-el-dirirectorio debe realizar varias tareas:

Escribamos una definición de función para realizar estas tareas. Usaremos un bucle while para movernos de un nombre de fichero a otro con un directorio comprobando lo que hay que hacer; y usaremos una llamada recursiva para repetir las acciones en cada subdirectorio. El patrón recursivo es ‘accumulate’ (Consulta la Sección Patrón recursivo: accumulate) usando append como combinador.

Aquí está la función:

(defun ficheros-bajo-el-dirirectorio (directorio)
  "Lista los ficheros .el en DIRECTORIO y en sus subdirectorios."
  ;; Aunque la función se utilizara no interactivamente,
  ;; será mas fácil de probar si la hacemos interactiva.
  ;; El directorio tendrá un nombre como
  ;;  "/usr/local/share/emacs/22.1.1/lisp/"
  (interactive "DNombre del Directorio: ")
  (let (lista-de-ficheros-el
        (lista-del-directorio-actual
         (directory-files-and-attributes directorio t)))
    ;; mientras estamos en el directorio actual
    (while lista-del-directorio-actual
      (cond
       ;; realiza una prueba para ver si el nombre del fichero termina
       ;; en ‘.el’ y si es así, añade su nombre a una lista.
       ((equal ".el" (substring (car (car lista-del-directorio-actual)) -3))
        (setq lista-de-ficheros-el
              (cons (car (car lista-del-directorio-actual)) lista-de-ficheros-el)))
       ;; prueba si el nombre del fichero es un directorio
       ((eq t (car (cdr (car lista-del-directorio-actual))))
        ;; decide si ignorarlo o hacer recursión
        (if
            (equal "."
                   (substring (car (car lista-del-directorio-actual)) -1))
            ;; entonces no hacer nada puesto que el nombre del fichero es
            ;; el directorio actual o el padre, "." o ".."
            ()
          ;; de otra forma, desciende dentro del directorio y repite el proceso
          (setq lista-de-ficheros-el
                (append
                 (ficheros-bajo-el-dirirectorio
                  (car (car lista-del-directorio-actual)))
                 lista-de-ficheros-el)))))
      ;; moverse al siguiente fichero en la lista; esto también acorta
      ;; la lista para que el bucle while eventualmente llegue a su fin
      (setq lista-del-directorio-actual (cdr lista-del-directorio-actual)))
    ;; devuelve los ficheros
    lista-de-ficheros-el))

La funcion ficheros-bajo-el-dirirectorio toma un argumento, el nombre de un directorio.

Por eso, en mi sistema,

(length
 (ficheros-bajo-el-dirirectorio "/usr/local/share/emacs/22.1.1/lisp/"))

me dice que en y debajo de mi directorio de codigo fuente Lisp hay 1031 ficheros ‘.el

ficheros-bajo-el-dirirectorio devuelve una lista en orden alfabético inverso. Una expresión para ordenar la lista en orden alfabetico tiene el siguiente aspecto:

(sort
 (ficheros-bajo-el-dirirectorio "/usr/local/share/emacs/22.1.1/lisp/")
 'string-lessp)

Contando definiciones de función

Nuestro objetivo inmediato es generar una lista que nos diga cuantas definiciones de funcion contienen menos de 10 palabras y símbolos, cuantas contienen entre 10 y 19 palabras y símbolos, cuantas entre 20 y 29, y así sucesivamente.

Con una lista ordenada de números, esto es fácil: se cuentan cuantos elementos de la lista son más pequeños de 10, luego, despues de haber pasado los numeros que se acaban de contar, se cuenta cuantos son más pequeños de 20, despues de haber pasado los numeros que se acaban de contar, los que son más pequeños de 30, y así sucesivamente. Cada uno de los números, 10, 20, 30, 40, y similares, es uno más grande que la parte superior de ese rango. Podemos llamar a esta lista de estos numeros, cima-de-rangos.

Si quisieramos, podriamos generar esta lista automáticamente, pero es más sencillo escribir una lista manualmente. Aquí está:

(defvar cima-de-rangos
 '(10  20  30  40  50
   60  70  80  90 100
  110 120 130 140 150
  160 170 180 190 200
  210 220 230 240 250
  260 270 280 290 300)
 "Listar especificando rangos para ‘definiciones-por-rango’.")

Para cambiar los rangos, editamos esta lista.

A continuacion, necesitamos escribir la función que crea la lista del número de definiciones dentro de cada rango. Evidentemente, esta función debe tomar las listas longitudes-ordenadas y cima-de-rangos como argumentos.

La función definiciones-por-rango debe hacer dos cosas una y otra vez: debe contar el número de definiciones con un rango específicado por el valor superior actual del rango; y debe pasar al siguiente valor superior en la lista cima-de-rangos después de contar el número de definiciones en el rango actual. Dado que cada una de estas acciones es repetitiva, se pueden utilizar bucles while para el trabajo. Un bucle cuenta el número de definiciones en el rango definido por el valor superior actual, y el otro bucle a su vez selecciona cada uno de los valores superiores del rango.

Se cuentan varias entradas de la lista de longitudes-ordenadas para cada rango; esto significa que el bucle para la lista de longitudes-ordenadas estara dentro del bucle para la lista de cima-de-rangos, como un pequeño engrane dentro de un gran mecanismo.

El bucle interno cuenta el número de definiciones dentro del rango. Es un simple bucle de conteo del tipo que hemos visto antes. (Ver la Seccion Un bucle con un contador incremental). La prueba verdadero-o-falso del bucle comprueba si el valor de la lista longitudes-ordenadas es menor que el valor actual de la cima del rango. Si es así, la función incrementa el contador y prueba el siguiente valor de la lista longitudes-ordenadas.

El bucle interno se ve asi:

(while elemento-de-longitud-menor-que-el-de-la-cima-de-rango
  (setq numero-dentro-del-rango (1+ numero-dentro-del-rango))
  (setq longitudes-ordenadas (cdr longitudes-ordenadas)))

El bucle exterior debe empezar con el valor más bajo de la lista cima-de-rangos y, a continuacion, ajustarse a cada uno de los valores superiores sucesivos. Esto puede ser hecho con un bucle como este:

(while cima-de-rangos
  cuerpo-del-bucle
  (setq cima-de-rangos (cdr cima-de-rangos)))

Puestos juntos, los dos bucles se ven asi:

(while cima-de-rangos

  ;; Cuenta el número de elementos dentro del rango actual.
  (while elemento-de-longitud-menor-que-el-de-la-cima-de-rango
    (setq numero-dentro-del-rango (1+ numero-dentro-del-rango))
    (setq longitudes-ordenadas (cdr longitudes-ordenadas)))

  ;; Mover al siguiente rango.
  (setq cima-de-rangos (cdr cima-de-rangos)))

Además, en cada iteracion del bucle exterior, Emacs debe registrar el número de definiciones dentro de ese rango (el valor de numero-dentro-del-rango) en una lista. Podemos usar cons para este propósito. (Consulta la Seccion cons.)

La función cons trabaja bien, excepto que la lista que construye contendrá el número de definiciones para el rango mas alto y el número de definiciones para el rango más bajo al final. Esto se debe a que cons adjunta nuevos elementos de la lista al principio de la lista, y ya que los dos bucles hacen cálculos a través de la lista de longitudes iniciando desde el extremo inferior, la lista-de-definiciones-por-rango terminara con el numero mayor al pricipio. Pero querremos imprimir nuestro grafo con los valores pequeños primero y los más grandes después. La solución es invertir el orden de la lista-de-definiciones-por-rango. Podemos hacer esto usando la función nreverse, que invierte el orden de una lista.

Por ejemplo,

(nreverse '(1 2 3 4))

produce:

(4 3 2 1)

Advertir que la función nreverse es “destructiva”––es decir, cambia la lista a la que se aplica; esto contrasta con las funciones car y cdr, que no son destructivas. En este caso, no queremos la lista-de-definiciones-por-rango original, asi que no importa que sea destruida. (La función reverse proporciona un copia inversa de una lista, dejando la lista original tal cual.)

En conjunto, la funcion definiciones-por-rango luce asi:

(defun definiciones-por-rango (longitudes-ordenadas cima-de-rangos)
  "LONGITUDES-ORDENADAS de defuns en cada CIMA-DE-RANGOS."
  (let ((cima-de-rango (car cima-de-rangos))
        (numero-dentro-del-rango 0)
        lista-de-definiciones-por-rango)

    ;; Bucle Externo.
    (while cima-de-rangos

      ;; Bucle Interno.
      (while (and
              ;; Necesita el número para la prueba numérica.
              (car longitudes-ordenadas)
              (< (car longitudes-ordenadas) cima-de-rango))

        ;; Cuenta el número de definiciones dentro del rango actual.
        (setq numero-dentro-del-rango (1+ numero-dentro-del-rango))
        (setq longitudes-ordenadas (cdr longitudes-ordenadas)))

      ;; Sale del bucle interno pero permanece dentro del bucle externo.

      (setq lista-de-definiciones-por-rango
            (cons numero-dentro-del-rango lista-de-definiciones-por-rango))
      (setq numero-dentro-del-rango 0)      ; Restablece el conteo a cero.

      ;; Mover al siguiente rango.
      (setq cima-de-rangos (cdr cima-de-rangos))
      ;; Designa el siguiente valor superior del rango.
      (setq cima-de-rango (car cima-de-rangos)))
    ;; Sale del bucle externo y cuenta el número de defuns mayor que
    ;; el valor mas alto de cima-de-rango.
    (setq lista-de-definiciones-por-rango
          (cons
           (length longitudes-ordenadas)
           lista-de-definiciones-por-rango))

    ;; Devuelve una lista del número de definiciones dentro de cada rango,
    ;;   del menor al mayor.
    (nreverse lista-de-definiciones-por-rango)))

La función es sencilla, excepto por una caracteristica sutil. La prueba verdadero-o-falso del bucle interno es asi:

(and (car longitudes-ordenadas)
     (< (car longitudes-ordenadas) cima-de-rango))

en lugar de asi:

(< (car longitudes-ordenadas) cima-de-rango)

El propósito de la prueba es determinar si el primer elemento de la lista de longitudes-ordenadas es inferior al valor de la parte superior del rango.

La versión simple de la prueba funciona bien a menos que la lista longitudes-ordenadas tenga un valor nil. En este caso, la expresión (car longitudes-ordenadas) devuelve nil. La función < no puede compara un número con nil, que es una lista vacía, por lo que Emacs indica un error e impide que la función siga ejecutandose.

La lista de longitudes-ordenadas siempre se convierte en nil cuando el contador llega al fin de la lista. Esto significa que cualquier intento de usar la función definiciones-por-rango con la versión simple de la prueba fallará.

Resolvemos el problema usando la expresion (car longitudes-ordenadas) junto con la expresión and. La expresión (car longitudes-ordenadas) devuelve un valor no nil siempre y cuando la lista contenga al menos un número, pero devuelve nil si la lista está vacía. La expresión and primero evalúa (car longitudes-ordenadas), y si es nil, devuelve falso sin evaluar la expresión < y devuelve este valor como el valor de la expresión and.

De esta manera, evitamos un error. (Para mas información sobre and, consulta la Seccion La función kill-new

He aqui una breve prueba de la función definiciones-por-rango. Primero, evalúa la expresión que enlaza la lista (ordenada) cima-de-rangos a la lista de valores, luego evalúa la expresión que enlaza la lista longitudes-ordenadas, y despues evalúa la función definiciones-por-rango.

;; (La lista ordenada que usaremos después.)
(setq cima-de-rangos
 '(110 120 130 140 150
   160 170 180 190 200))

(setq longitudes-ordenadas
      '(85 86 110 116 122 129 154 176 179 200 265 300 300))

(definiciones-por-rango longitudes-ordenadas cima-de-rangos)

Esta es la lista que devuelve:

(2 2 2 0 0 1 0 2 0 0 4)

De hecho, hay dos elementos de la lista longitudes-ordenadas menores a 110, dos elementos entre 110 y 119, dos elementos entre 120 y 129, etcetera. Hay cuatro elementos con un valor de 200 o superior.

Preparar un grafico

Nuestro objetivo es construir un grafo que muestre el numero de definiciones de función de varios tamaños en el codigo fuente de Emacs lisp.

Como cuestion práctica, si estuvieras creando un grafico, probablemente usarías un programa como gnuplot para hacer el trabajo. (gnuplot está bien integrado dentro de GNU Emacs.) En este caso, sin embargo, creamos uno desde cero, y en el proceso volveremos a familiarizarnos con algo de lo que aprendimos antes y aprenderemos más.

En este capítulo, primero escribiremos una simple funcion de impresion de graficos. Esta primera definición será un prototipo, una función escrita rápidamente que nos permita reconocer este territorio desconocido en la creacion de graficos. Descubriremos dragones, o descubriremos que son mitos. Después de explorar el terreno, nos sentiremos más seguros y mejoraremos la función para etiquetar las coordenadas automáticamente.

Dado que Emacs está diseñado para ser flexible y funcionar con todo tipo de terminales, incluyendo los terminales de solo caracteres, el grafico debera realizarse a partir de uno de los simbolos de ‘maquina de escribir’. Un asterisco servira; a medida que mejoremos la función de impresión del grafico, podremos hacer que el simbolo a usar sea una elección del usuario.

Podemos llamar a esta función imprimir-cuerpo-grafico; tomará como unico argumento una lista-de-numeros. En esta etapa, no etiquetaremos el grafico, sino que solo imprimiremos su cuerpo.

La función imprimir-cuerpo-grafico inserta una columna vertical de asteriscos para cada elemento en la lista-de-numeros. La altura de cada línea está determinada por el valor de ese elemento de la lista-de-numeros.

Insertar columnas es un acto repetitivo; esto significa que esta función puede escribirse con un bucle while o recursivamente.

Nuestro primer reto es descubrir como imprimir una columna de asteriscos. Normalmente, en Emacs, se imprimen caracteres dentro de una pantalla horizontalmente, escribiendo línea a línea. Tenemos dos rutas a seguir: escribir nuestra propia función de insercion-de-columnas o descubrir si ya existe una en Emacs.

Para ver si hay una en Emacs, podemos usar el comando M-x apropos. Este comando es como el comando C-h a (command-apropos), excepto que este último solo encuentra aquellas funciones que son comandos. El comando M-x apropos lista todos los símbolos que coinciden con una expresión regular, incluyendo funciones que no son interactivas.

Lo que queremos buscar es algún comando que imprima o inserte columnas. Muy probablemente, el nombre de la función contendrá la palabra ‘print’ o la palabra ‘insert’ o la palabra ‘column’. Por esta razón, podemos simplemente escribir M-x apropos RET print\|insert\|column RET y ver el resultado. En mi sistema, este comando demoro bastante tiempo, y luego produjo una lista de 79 funciones y variables. Ahora no demora mucho y produce una lista de 211 funciones y variables. Explorando la lista, la única función que parece puede hacer el trabajo es insert-rectangle.

De hecho, esta es la función que queremos; su documentación dice:

insert-rectangle:
Insertar texto de RECTANGLE con la esquina superior izquierda en el punto
La primera línea de RECTANGLE se inserta en el punto
la segunda línea se inserta en un punto verticalmente debajo del punto, etc
RECTANGLE debe ser una lista de cadenas.
Después de este comando, la marca está en la esquina izquierda
superior y el punto en la esquina inferior derecha.

Podemos ejecutar una prueba rápida, para asegurarnos de que hace lo que esperamos.

Este es el resultado de colocar el cursor después de la expresión insert-rectangle y presionar C-u C-x C-e (eval-last-sexp). La función inserta las cadenas "primero", "segundo", y "tercero" debajo del punto. También la función devuelve nil.

(insert-rectangle '("primero" "segundo" "tercero"))primero
                                                   segundo
                                                   terceronil

Por supuesto, no insertaremos el texto de la expresión insert-rectangle en el búfer en el que estamos haciendo el grafico, sino qque llamaremos a la función desde nuestro programa. Sin embargo, tendremos que asegurarnos de que el punto está en el búfer en el lugar donde la función insert-rectangle insertará la columna de cadenas.

Si estás leyendo esto en Emacs, puedes ver como funciona cambiando a otro búfer, como el búfer *scratch*, colocar el punto a algún lugar del búfer, presionar M-:, despues escribir la expresión insert-rectangle dentro del minibúfer en la consola, y presionar RET. Esto hace que Emacs evalúe la expresión en el minibúfer, pero utilice como el valor del punto la posición del punto en el búfer *scratch*. (M-: es el atajo para eval-expression. Tampoco aparece nil en el búfer *scratch*, ya que la expresión se evalúa en el minibúfer.)

Cuando hacemos esto, encontraremos que el punto termina al final de la última línea insertada––es decir, esta función mueve el punto como un efecto secundario. Si repitieramos el comando, con el punto en esta posición, la siguiente inserción estaria debajo y a la derecha de la inserción anterior. ¡No queremos esto!. Si vamos a crear un gráfico de barras, las columnas deben estar una al lado de la otra.

Así descubrimos que cada ciclo de insercion de columnas del bucle while debe reposicionar el punto al lugar que queremos, y ese lugar estará en la parte superior, no en la inferior de la columna. Ademas, recordamos que cuando imprimimos un grafico, no se espera que todas las columnas tengan la misma altura. Esto significa que la parte superior de cada columna puede estar a una altura diferente de la anterior. No podemos simplemente reposicionar el punto en la misma línea cada vez, sino movernos hacia la derecha––o tal vez podamos…

Estamos planeando crear las columnas del grafico de barras con asteriscos. El número de asteriscos en la columna es el número específicado por el elemento actual de la lista-de-numeros. Necesitamos construir una lista de asteriscos de la longitud correcta para cada llamada a insert-rectangle. Si esta lista consiste únicamente del número requerido de asteriscos, entonces tendremos la posición de punto el número correcto de líneas sobre la base del gráfico para imprimirse correctamente. Esto podría ser difícil.

Alternativamente, si podemos encotrar alguna manear de pasar a insert-rectangle una lista de la misma longitud cada vez, entonces podemos posicionar el punto en la misma línea cada vez, pero moverlo una columna a la derecha para cada nueva columna. Si hacemos esto, sin embargo, algunas de las entradas en la lista pasaba a insert-rectangle deben ser espacios en blanco en vez de asteriscos. Por ejemplo, si la altura máxima del grafico es 5, pero la altura de la columna es 3, entonces insert-rectangle requiere un argumento como este:

(" " " " "*" "*" "*")

Esta última propuesta no es tan difícil, siempre y cuando podamos determinar la altura de la columna. Hay dos maneras de especificar la altura de la columna: podemos indicar arbitrariamente cual sera, lo que funcionaría bien para gráficos de esa altura; o podemos buscar a través de la lista de números y usar la altura máxima de la lista como la altura máxima del grafico. Si la segunda operación fuera difícil, entonces el procedimiento anterior seria mas fácil, pero hay una función nativa en Emacs para determinar el máximo de sus argumentos. Podemos usar esta función. La función se llama max y devuelve el mayor de todos sus argumentos, que deben ser números. Asi, por ejemplo,

(max  3 4 6 5 7 3)

devuelve 7. (Una función correspondiente llamada min devuelve el más pequeño de todos sus argumentos.)

Sin embargo, no podemos simplemente llamar a max sobre la lista-de-numeros; la función max espera números como su argumento, no una lista de números. De este modo, la siguiente expresión,

(max '(3 4 6 5 7 3))

produce el siguiente mensaje error:

Tipo incorrecto de argumento: number-or-marker-p, (3 4 6 5 7 3)

Necesitamos una función que pase una lista de argumentos a una función. Esa función es apply. Esta función ‘aplica’ su primer argumento (una función) a los argumentos restantes, el último puede ser una lista.

Por ejemplo,

(apply 'max 3 4 7 3 '(4 8 5))

devuelve 8

(Por cierto, no cómo aprenderias sobre esta función sin un libro como este. Es posible descubrir otras funciones, como search-forward o insert-rectangle, adivinando una parte de sus nombres y luego usando apropos. Aunque su base metafórica es clara––‘apply’ (aplicar) su primer argumento al resto––dudo que a un novato se le ocurra esa palabra en particular usando apropos u otra ayuda. Por supuesto, podría estar equivocado; después de todo, la función fué nombrada por primera vez por alguien que la tuvo que inventar.

El segundo y siguientes argumentos de apply son opcionales, por lo que podemos usar apply para llamar a una función y pasarle los elementos de una lista, de la siguiente manera, que también devuelve 8:

(apply 'max '(4 8 5))

Este ultima forma es la que usaremos para apply. La función lista-de-longitudes-de-muchos-ficheros-recursiva devuelve una lista de números a la que podemos aplicar max (tambien podriamos aplicar max a la lista de números ordenados; no importa si la lista está ordenada o no).

Por lo tanto, la operación para encontrar la altura máxima del grafico es esta:

(setq altura-maxima-del-grafico (apply 'max lista-de-numeros))

Ahora podemos volver a la pregunta de como crear una lista de cadenas para una columna del grafico. Indicando la máxima altura del grafico y el número de asteriscos que aparecerían en la columna, la función devolverá una lista de cadenas para el comando insert-rectangle.

Cada columna se compone de asteriscos o espacios en blanco. Puesto que pasamos a la función la altura de la columna y el número de asteriscos en ella, el número de espacios en blanco se puede encontrar restando los asteriscos de la altura. Dado el número de espacios en blanco y el número de asteriscos, podemos usar dos bucles while para construir la lista:

;;; Primera versión.
(defun columna-del-grafico (altura-maxima-del-grafico altura-real)
  "Devuelve la lista de cadenas que es una columna de un grafico."
  (let ((lista-de-insercion nil)
        (numero-de-espacios
         (- altura-maxima-del-grafico altura-real)))

    ;; Rellenar los asteriscos.
    (while (> altura-real 0)
      (setq lista-de-insercion (cons "*" lista-de-insercion))
      (setq altura-real (1- altura-real)))

    ;; Rellena los espacios.
    (while (> numero-de-espacios 0)
      (setq lista-de-insercion (cons " " lista-de-insercion))
      (setq numero-de-espacios
            (1- numero-de-espacios)))

    ;; Devuelve la lista completa.
    lista-de-insercion))

Si instalas esta función y luego evaluas la siguiente expresión, veras que devuelve la lista como se desea:

(columna-del-grafico 5 3)

devuelve

(" " " " "*" "*" "*")

Como está escrito, columna-del-grafico contiene un defecto importante: los símbolos usados para el espacio y las entradas marcadas en la columna estan ‘incrustados en el codigo’ como un espacio y un asterisco. Esto está bien para un prototipo, pero tu, u otro usuario, pueden querer usar otros símbolos. Por ejemplo, al probar la función del grafico, podrias querer usar un punto en vez del espacio, para asegurar que el punto se está colocando apropiadamente cada vez que se llama a la función insert-rectangle; o tal vez quieras sustituir un signo de asterisco por ‘+’. Incluso es posible querer crear un grafico de columna que tenga mas de una columna de visualizacion. El programa debería ser más flexible. La forma de hacerlo es reemplazar el espacio en blanco y el asterisco con dos variables que podemos llamar simbolo-en-blanco y simbolo-grafico y definir esa variables por separado.

Ademas, la documentación no está bien escrita. Estas consideraciones nos llevan a la segunda versión de la función:

(defvar simbolo-grafico "*"
  "Cadena utilizada como símbolo en el grafico, normalmente un asterisco.")

(defvar simbolo-en-blanco " "
  "Cadena utilizada como un espacio en blanco en el grafico, normalmente un espacio en blanco.
simbolo-en-blanco debe tener el mismo número de columnas de ancho que simbolo-grafico.")

(Para una explicación de defvar, consulta la seccion Inicializando una variable con defvar.)

;;; Segunda versión.
(defun columna-del-grafico (altura-maxima-del-grafico altura-real)
  "Devuelve cadenas con la ALTURA-MAXIMA-DEL-GRAFICO; ALTURA-REAL son símbolos graficos.

Los simbolo-graficos son entradas contiguas al final de la lista.
La lista se insertara como una columna de un grafico.
Las cadenas contienen tanto simbolos en blanco como simbolos graficos."

  (let ((lista-de-insercion nil)
        (numero-de-espacios
         (- altura-maxima-del-grafico altura-real)))

    ;; Rellena con simbolo-grafico.
    (while (> altura-real 0)
      (setq lista-de-insercion (cons simbolo-grafico lista-de-insercion))
      (setq altura-real (1- altura-real)))

    ;; Rellena con simbolo-en-blanco.
    (while (> numero-de-espacios 0)
      (setq lista-de-insercion (cons simbolo-en-blanco lista-de-insercion))
      (setq numero-de-espacios
            (1- numero-de-espacios)))

    ;; Devuelve la lista completa.
    lista-de-insercion))

Si quisieramos, podríamos reescribir columna-del-grafico una tercera vez para proporcionar la opcion de crear un gráfico de líneas, como gráfico de barras. Esto no sería dificil de hacer. Una manera de pensar en un grafico de líneas es que no es más que un grafico de barras en el que la parte de cada barra que está debajo la parte superior esta en blanco. Para construir una columna para gráfico de líneas, la función primero construyen una lista de espacios en blanco que es más pequeña que el valor en uno, luego usa cons para adjuntar un símbolo gráfico a la lista; despues usa cons de nuevo para adjuntar el ‘alto de espacios en blanco’ a la lista.

Es fácil ver como escribir una función de este tipo, pero puesto que no la necesitamos, no se hará. Pero el trabajo podría hacerse, y si se hiciera, se haría con columna-del-grafico. Y lo que es más importante, señalar que pocos cambios tendrían que realizarce en otro lugar. La mejora, si alguna vez la hacemos, es simple.

Ahora, finalmente, llegamos a nuestra primer función real de impresion de graficos. Esta imprime el cuerpo de un grafico, no las etiquetas para los ejes horizontal y vertical, así que podemos nombrarla como imprimir-cuerpo-grafico.

La función imprimir-cuerpo-grafico

Después de nuestra preparación en la sección anterior, la función imprimir-cuerpo-grafico es sencilla. La función imprimirá columna tras columna de asteriscos y espacios en blanco, usando los elementos de la lista de números para especificar el número de asteriscos en cada columna. Este es un acto repetitivo, lo que significa que podemos utilizar un bucle while decreciente o una función recursiva para el trabajo. En esta sección, escribiremos la definición usando un bucle while.

La función columna-del-grafico requiere como argumento la altura del grafico, por lo que debemos determinar y registrarla como una variable local.

Esto nos lleva a la siguiente plantilla para la version con el bucle while de esta funcion:

(defun imprimir-cuerpo-grafico (lista-de-numeros)
  "documentacion…"
  (let ((altura  
         ))

    (while lista-de-numeros
      insertar-columna-y-reposicionar-punto
      (setq lista-de-numeros (cdr lista-de-numeros)))))

Necesitamos completar los espacios de la plantilla.

Claramente, podemos usar la expresión (apply 'max lista-de-numeros) para determinar la altura del grafico.

El bucle while recorrera la lista-de-numeros un elemento a la vez. A medida que se acorta la lista por la expresión (setq lista-de-numeros (cdr lista-de-numeros)), el car de cada instancia de la lista es el valor del argumento para columna-del-grafico.

En cada ciclo del bucle while, la función insert-rectangle inserta la lista devuelta por columna-del-grafico. Dado que la función insert-rectangle, mueve el punto a la parte inferior derecha del rectangulo insertado, necesitamos guardar la ubicacion del punto en el momento que se inserta el rectángulo, volver a esta posición después de insertar el rectángulo, y despues moverlo horizontalmente al siguiente lugar donde se llama a insert-rectangle.

Si las columnas insertadas tienen un carácter de ancho, como será si se utilizan espacios y asteriscos unicos, el comando de reposicionamiento consiste solamente en (forward-char 1); sin embargo, el ancho de una columna puede ser mayor que uno. Esto significa que el comando de reposicionamiento debe escribirse (forward-char ancho-del-simbolo). El ancho del simbolo en si es la longitud de un grafico en blanco y se puede encontrar usando la expresion (length graph-blank). El mejor lugar para asociar la variable ancho-del-simbolo al valor de la columna de grafico está en la varlist de la expresión let.

Estas consideraciones conducen a la siguiente definición de función:

(defun imprimir-cuerpo-grafico (lista-de-numeros)
  "Imprime un gráfico de barras de la LISTA-DE-NUMEROS.
La lista-de-numeros esta formada por los valores del eje Y."

  (let ((altura (apply 'max lista-de-numeros))
        (ancho-del-simbolo (length simbolo-en-blanco))
        desde-la-posicion)

    (while lista-de-numeros
      (setq desde-la-posicion (point))
      (insert-rectangle
       (columna-del-grafico altura (car lista-de-numeros)))
      (goto-char desde-la-posicion)
      (forward-char ancho-del-simbolo)
      ;; Dibuja el grafico columna por columna.
      (sit-for 0)
      (setq lista-de-numeros (cdr lista-de-numeros)))
    ;; Coloca el punto para las etiquetas del eje X.
    (forward-line altura)
    (insert "\n")
))

La unica expresión inesperada en esta función es (sit-for 0) dentro del bucle while. Esta expresión hace que la operacion de impresion de graficos sea más de lo que sería de otro modo. La expresión hace que Emacs ‘pare’ (sit) o no haga nada un periodo de tiempo cero y luego vuelva a dibujar la pantalla. Puesto aquí, hace que Emacs redibuje la pantalla columna por columna. Sin ella, Emacs no redibujaría la pantalla hasta que la función termine.

Podemos probar imprimir-cuerpo-grafico con una pequeña lista de números.

  1. Instala simbolo-grafico, simbolo-en-blanco, columna-del-grafico, que aparecen en las Secciones Preparar un grafico, y #La función imprimir-cuerpo-grafico.

  2. Copia la siguiente expresión:

    (imprimir-cuerpo-grafico '(1 2 3 4 6 4 3 5 7 6 5 2 3))
    
  3. Cambia al búfer *scratch* y coloca el cursor donde quieras que empiece el grafico.

  4. Pulsa M-: (eval-expression).

  5. Pega la expresión imprimir-cuerpo-grafico dentro del minibúfer con C-y (yank).

  6. Presiona RET para evaluar la expresión imprimir-cuerpo-grafico.

Emacs imprimirá un grafico como este:

        *
    *   **
    *  ****
   *** ****
  ********* *
 ************
*************

La función imprimir-cuerpo-grafico-con-recursividad

La función imprimir-cuerpo-grafico también puede escribirse recursivamente. La solución recursiva se divide en dos partes: una ‘envoltura’ externa que utiliza una expresión let para determinar los valores de varias variables que solo es necesario encontrar una vez, como la altura máxima del grafico, y una función interna que se llama recursivamente para imprimir el grafico.

La ‘envoltura’ no es complicada:

(defun imprimir-cuerpo-grafico-con-recursividad (lista-de-numeros)
  "Imprime un gráfico de barras de la LISTA-DE-NUMEROS.
La lista-de-numeros esta formada por los valores del eje Y."
  (let ((altura (apply 'max lista-de-numeros))
        (ancho-del-simbolo (length simbolo-en-blanco))
        desde-la-posicion)
    (imprimir-cuerpo-grafico-con-recursividad-interna
     lista-de-numeros
     altura
     ancho-del-simbolo)))

La función recursiva es un poco más difícil. Tiene cuatro partes: la ‘prueba-hazlo-de-nuevo’, el código de impresion, la llamada recursiva, y la ‘expresion-del-siguiente-paso’. La ‘prueba-hazlo-de-nuevo’ es una expresión when que determina si la lista-de-numeros contiene algun elemento restante; si lo tiene, la función imprime una columna del grafico usando el código de impresion y se vuelve a llamar asi misma. La función se llama así misma de acuerdo al valor producido por la ‘expresion-del-siguiente-paso’ que hace que la llamada actue sobre una versión mas corta de la lista-de-numeros.

(defun imprimir-cuerpo-grafico-con-recursividad-interna
  (lista-de-numeros altura ancho-del-simbolo)
  "Imprime un gráfico de barras.
Se utiliza dentro del cuerpo de la función imprimir-cuerpo-grafico-con-recursividad."

  (when lista-de-numeros
        (setq desde-la-posicion (point))
        (insert-rectangle
         (columna-del-grafico altura (car lista-de-numeros)))
        (goto-char desde-la-posicion)
        (forward-char ancho-del-simbolo)
        (sit-for 0)     ; Dibuja el gráfico columna por columna.
        (imprimir-cuerpo-grafico-con-recursividad-interna
         (cdr lista-de-numeros) altura ancho-del-simbolo)))

Podemos probar esta expresión después de instalarla; aquí hay un ejemplo:

(imprimir-cuerpo-grafico-con-recursividad '(3 2 5 6 7 5 3 4 6 4 3 2 1))

Aquí está el resultado:

    *
   **   *
  ****  *
  **** ***
* *********
************
*************

Cada una de estas dos funciones, imprimir-cuerpo-grafico o imprimir-cuerpo-grafico-con-recursividad, crea el cuerpo de un grafico.

Necesidad de Ejes Impresos

Un grafico necesita ejes impresos para poder orientarse. Para un proyecto de usar una vez, puede ser razonable dibujar los ejes a mano usando el modo Picture de emacs, pero una funcion de dibujo de graficos puede usarse más de una vez.

Por esta razón, he escrito mejoras a la función básica print-graph-body que imprime automáticamente etiquetas para los ejes horizontal y vertical. Como las funciones de impresion de etiquetas no contiene mucho material nuevo, he puesto su descripción en un apéndice. Ver Seccion Apéndice C: Un grafico con ejes etiquetados.

Ejercicio

Escribe una versión de grafico de lineas de las funciones de impresión de graficos.

Tu fichero .emacs

“No te tiene que gustar Emacs para que te guste”––esta declaracion aparentemente paradójica es el secreto de GNU Emacs. En realidad, Emacs es una herramienta genérica. La mayoría de las personas que lo usan, lo personalizan para ajustarlo a sus necesidades.

GNU Emacs está escrito principalmente en Emacs Lisp; esto significa que escribiendo expresiones en Emacs Lisp se puede modificar o extender Emacs.

Hay quienes aprecian la configuración por defecto de Emacs. Después de todo, Emacs inicia en modo C cuando se edita un fichero C, inicia en modo Fortran cuando se edita un fichero Fortran, y en modo Fundamental cuando se edita un fichero sin adornos. Esto tiene sentido, si no sabes quien va a utilizar Emacs. ¿Quién sabe lo que una persona espera hacer con un fichero sin adornos? Para dicho fichero, el modo fundamental es el modo por defecto apropiado, de la misma manera C es el modo predeterminado correcto para editar código C. (Hay suficientes lenguajes de programación con sintaxis que permiten compartir funcionalidades, por lo que el modo C ahora se proporciona para el modo CC, ‘C Collection’.)

Pero cuando sabes quien va a utilizar Emacs––tu mismo––entonces tiene sentido personalizar Emacs.

Por ejemplo, rara vez quiero el modo Fundamental cuando edito un fichero que de otro modo no se distinguiria; quiero el modo Text. Por eso personalizo Emacs: para que se adapte a mí.

Puedes personalizar y ampliar Emacs escribiendo o adaptando un fichero ~/.emacs. Este es tu fichero de inicialización personal; su contenido, escrito en Emacs Lisp, le indica a Emacs qué hacer.14

Un fichero ~/.emacs contiene código Emacs Lisp. Puedes escribir este código tu mismo; o usar la funcion de Emacs customize para que escriba el código por ti. Puedes combinar tus propias expresiones y expresiones auto-escritas en tu fichero .emacs.

(Yo prefiero escribir mis propias expresiones, excepto aquellas, particularmente las fuentes, que encuentro mas fáciles de manipular usando el comando customize. Combino los dos métodos.)

La mayor parte de este capítulo trata sobre como escribir expresiones por uno mismo. Describe un fichero .emacs simple; para más información, consulta la Sección El fichero de inicio en El Manual de GNU Emacs, y la Seccion El fichero de inicio en El Manual de Referencia GNU Emacs Lisp.

Ficheros de inicialización site-wide

Además de tu fichero de inicialización personal, Emacs carga automáticamente varios ficheros de inicialización, si existen. Estos tienen la misma forma que tu fichero .emacs, pero se cargan para todos.

Dos ficheros de incialización para todos los usuarios, site-load.el y site-init.el, se cargan en Emacs y luego se ‘vuelcan’ si se crea una version ‘volcada’ de Emacs, como es más común. (Las copias volcadas de Emacs se cargan más rápidamente. Sin embargo, una vez que un fichero se carga y vuelca, un cambio en el no conduce a un cambio en Emacs a menos que lo cargues por tu cuenta. Consulta la Seccion Construyendo Emacs en El Manual de Referencia de GNU Emacs Lisp, y el fichero INSTALL)

Otros tres ficheros de inicialización se cargan automáticamente cada vez que se inicia Emacs, si existen. Son site-start.el, que se carga antes que tu fichero .emacs, y default.el, y el fichero de tipo de terminal, que se cargan después de .emacs.

Las configuraciones y definiciones en tu fichero .emacs sobreescribirán las configuraciones y definiciones conflictivas del fichero site-start.el, si existe; pero las configuraciones y definiciones en default.el o el fichero de tipo de terminal sobreescribirán las de .emacs. (puede evitar las interferencias del fichero de tipo de terminal configurando term-file-prefix a nil. Consulta la Seccion Una extensión simple: linea-a-lo-alto-de-la-ventana.)

El fichero INSTALL que viene en la distribución contiene descripciones de los ficheros site-init.el y site-load.el.

Los ficheros loadup.el, startup.el, y loaddefs.el controlan la carga. Estos ficheros están en el directorio lisp de la distribución Emacs y vale la pena leerlos.

El fichero loaddefs.el contiene muchas sugerencias sobre que poner en tu propio fichero .emacs, o dentro de un fichero de inicialización amplio.

Especificar variables usando defcustom

Puedes especificar variables usando defcustom para que tu y otros puedan usar la caracteristica de Emacs customize para establecer sus valores. (No se puede usar customize para escribir definiciones de función; pero se pueden escribir defuns en el fichero .emacs. De hecho, se puede escribir cualquier expresión Lisp en el fichero .emacs).

La caracteristicas de configuracion ofrecidas por customize dependen de la forma especial defcustom. Aunque se puede usar defvar o setq para las variables que establecen los usuarios, la forma especial defcustom está diseñada para este trabajo.

Puedes usar tu conocimiento de defvar para escribir los primeros tres argumentos de defcustom. El primer argumento de defcustom es el nombre de la variable. El segundo argumento es el valor inicial de la variable, si lo hay; y este valor se asigna solo si el valor no se ha establecido. El tercer argumento es la documentación.

El cuarto y subsiguientes argumentos de defcustom especifican tipos y opciones; estos no se presentan en defvar. (Estos argumentos son opcionales.)

Cada uno de estos argumentos consiste de una palabra clave seguida de un valor. Cada palabra clave empieza con el caracter dos puntos ‘:’.

Por ejemplo, la variable de opciones personalizable text-mode-hook tiene el siguiente aspecto:

(defcustom text-mode-hook nil
  "El hook normal se ejecuta cuando se ingresa en modo texto y en muchos modos relacionados."
  :type 'hook
  :options '(turn-on-auto-fill flyspell-mode)
  :group 'wp)

El nombre de la variable es text-mode-hook; no tiene valor por defecto; y su cadena de documentación cuenta lo que hace.

La palabra clave :type le indica a Emacs el tipo de datos a los que text-mode-hook sera asignado y como mostrar el valor en un búfer de personalización.

La palabra clave :options especifica una lista sugerida de valores para la variable. Normalmente, :options se asocia a un gancho (hook). La lista es solo una sugerencia; no es excluyente; una persona que establece la variable puede establecerla en otros valores; la lista que se muestra despues de la palabra clave :options tiene la intencion de ofrecer opciones convenientes a el usuario.

Finalmente, la palabra clave :group indica al comando de Personalización de Emacs en que el grupo se encuentra la variable. Esto dice dónde encontrala.

La función defcustom reconoce más de una docena de palabras clave. Para obtener más información, consulta la Seccion Escribir las Definiciones de Personalización en El Manual de Referencia GNU Emacs Lisp.

Considera text-mode-hook como un ejemplo.

Hay dos maneras de personalizar esta variable. Puedes utilizar el comando de personalización o escribir las expresiones apropiadas por tu cuenta.

Utilizando el comando de personalización, puedes escribir:

M-x customize

y encontrar que el grupo para editar ficheros de datos se llama ‘data’. Introduce este grupo. El Gancho del Modo Text es el primer miembro. Puedes hacer click en sus varias opciones, como turn-on-auto-fill, para establecer los valores. Después de hacer click en el botón.

Guárdar para sesiones futuras

Emacs escribirá una expresión en tu fichero .emacs. Se vera asi:

(custom-set-variables
  ;; custom-set-variables fué añadido por Custom.
  ;; Si lo editas a mano, podrías estropearlo, así que ten cuidado.
  ;; Tu fichero init deberia contener solo una de esta instancia.
  ;; Si hay más de una, no funcionara bien.
 '(text-mode-hook (quote (turn-on-auto-fill text-mode-hook-identify))))

(La función text-mode-hook-identify le indica a toggle-text-mode-auto-fill que buffers estan en modo Texto. Se encendera automáticamente)

La función custom-set-variables funciona de una manera algo distinta a setq. Aunque nunca he aprendido las diferencias, modifico las expresiones custom-set-variable en mi fichero .emacs a mano: Hago los cambios de la forma que me parecen razonables y no he tenido ningun problema. Otros prefieren usar el comando de Personalización y dejar que Emacs haga el trabajo por ellos.

Otra función de custom-set-… es custom-set-faces. Esta función establece varios tipos de fuentes. Con el tiempo, he asignado un considerable número de fuentes. Algunas veces, las reseteo usando customize; otras veces, simplemente edito la expresión custom-set-faces en mi fichero .emacs.

La segunda forma de personalizar text-mode-hook es configurarlo directamente en tu fichero .emacs usando código que no tiene nada que ver con las funciones custom-set-….

Al hacer esto, y mas tarde usar customize, verás un mensaje que dice:

CAMBIADO fuera de Customize; operar con el aquí puede no ser confiable.

Este mensaje es solo una advertencia. Si se cliquear en el botón

Guárdar para sesiones futuras

Emacs escribirá una expresión custom-set-… cerca del fin de tu fichero .emacs que será evaluada después de la expresión escrita a mano. Por lo tanto, invalidará tu expresión escrita a mano. No se hara ningún daño. Sin embargo, cuando hagas esto, ten cuidado para recordar que expresión está activa; si lo olvidas, puedes confundirte.

Siempre y cuando recuerdes donde estan establecidos los valores, no habrá ningun problema. En cualquier caso, los valores siempre se establecen en tu fichero de inicialización, que suele llamarse .emacs.

Yo mismo casi nunca uso customize. Mayoritariamente, escribo las expresiones por mí cuenta.

Por cierto, para ser mas completo con respecto a las definiciones: defsubst define una función inline. La sintaxis es igual a la de defun. defconst define un símbolo como una constante. La intencion es que ni los programas ni los usuarios cambien nunca un valor establecido por defconst. (Puede cambiarse; el valor se asigna a una variable; pero por favor no lo hagas.)

Empieza por un fichero .emacs

Cuando inicia Emacs, carga tu fichero .emacs a menos que se indique que no lo haga especificando -q en la línea de comandos. (El comando emacs -q te da un Emacs limpio, y listo para usar.)

Un fichero .emacs contiene expresiones Lisp. Con frecuencia, no son más que expresiones para establecer valores; algunas veces son definiciones de funcion.

Consulta la Seccion El Fichero de Inicio ~/.emacs en El Manual de GNU Emacs, para una breve descripción de los fichero de inicialización.

Este capítulo pasa por el mismo terreno, pero es un paseo entre extractos de un fichero .emacs completo––el mio.

La primera parte del fichero consiste en comentarios: recordatorios a mí mismo. Por ahora, por supuesto, recuerdo estas cosas, pero cuando empecé, no lo hice.

;;;; fichero .emacs de Bob
; Robert J. Chassell
; 26 de Septiembre de 1985

¡Mira esa fecha! Empecé este fichero hace mucho tiempo. Lo he estado extendiendo desde entonces.

; Cada sección en este fichero se inicia por una
; línea empezando con cuatro punto y coma y cada
; entrada inicia por una línea empezando con
; tres punto y coma.

Esto describe las convenciones habituales para comentarios en Emacs Lisp. Todo lo que aparece en una línea despues de un punto y coma es un comentario. Se utilizan dos, tres, y cuatro punto y coma como marcadores de subsección y sección. (Consulta la Seccion Comentarios en El Manual de Referencia GNU Emacs Lisp, para más informacion sobre los comentarios.)

;;;; La Tecla de Ayuda
; Control-h es la tecla de ayuda;
; después de escribir control-h, escribe una letra
; para indicar el asunto sobre el que quieres ayuda.
; Para una explicación de la propia ayuda,
; escribe control-h dos veces seguidas.

Solo recuerda: escribe C-h dos veces para pedir ayudar.

; Para conocer cualquier modo, presiona control-h m
; mientras estés en ese modo. Por ejemplo, para conocer
; sobre del modo correo, ingresa al modo correo y luego
; presiona control-h m.

El ‘Modo ayuda’, como yo lo llamo, es muy útil. Usualmente, dice todo lo que se necesita saber.

Por supuesto, no necesitas incluir comentarios como estos en tu fichero .emacs. Yo los incluí en el mío porque siempre me olvidaba del Modo ayuda o las convenciones para los comentarios––pero pude recordarlos recordármelo a mí mismo.

Modos Text y Auto Fill

Ahora llegamos a la parte que ‘activa’ al modo Text y el modo Auto Fill.

;;; Modo Text y modo Auto Fill
;; Las siguiente dos líneas ponen a en Emacs en modo Text
;; y en el modo Auto Fill, y son para escritores que
;; quieren empezar a escribir en prosa en lugar de código.
(setq-default major-mode 'text-mode)
(add-hook 'text-mode-hook 'turn-on-auto-fill)

¡Aquí está la primera parte de este fichero .emacs que hace algo ademas de ayudarle a recordar a un humano olvidado!

La primera de las dos líneas entre paréntesis le indican a Emacs que active el modo Text cuando se encuentra un fichero, a menos que ese fichero este en algún otro modo, por ejemplo, el modo C.

Cuando Emacs lee un fichero, examina la extensión del nombre del fichero. (La extensión es la parte que esta después de un ‘.’.) Si el fichero finaliza con una extensión ‘.c’ o ‘.h’ entonces Emacs activa el modo C. Ademas, Emacs examina la primer línea no en blanco del fichero; si la línea dice ‘-*- C -*-’, Emacs activa el modo C. Emacs posee una lista de extensiones y especificaciones que usa automáticamente. Además, Emacs busca cerca de la última página por buffer una “lista variables locales”, si la hay.

Consulta las secciones Cómo se Eligen los Modos Mayores y Variables Locales en Ficheros en El Manual de GNU Emacs.

Ahora, volvamos al fichero .emacs.

Aquí está la línea de nuevo; ¿cómo funciona?

(setq major-mode 'text-mode)

Esta corta línea es una expresion Lisp.

Ya estamos familiarizados con setq. Esto establece la siguiente variable, major-mode, al valor subsiguiente, que es text-mode. La comilla simple antes de text-mode le indica a Emacs que trate directamente con el símbolo text-mode, no con lo que pueda significar. Consulta la Seccion Establecer el Valor de una Variable, para recordar como funciona setq. El punto principal es que no hay diferencia entre el procedimiento que se usa utiliza para establecer un valor en tu fichero .emacs y el procedimiento que se usa en cualquier otro lugar de Emacs.

Aquí está la siguiente línea:

(add-hook 'text-mode-hook 'turn-on-auto-fill)

En esta línea, el comando add-hook añade turn-on-auto-fill a la variable.

¡turn-on-auto-fill es el nombre de un programa, que lo adivinaste!, activa el modo Auto Fill.

Cada vez que Emacs activa el modo Text, Emacs ejecuta los comandos ‘hooked’ (conectados) al modo Text. Por lo tanto, cada vez que Emacs activa el modo Text, Emacs también activa el modo Auto Fill.

En resumen, la primer línea hace que Emacs entre en modo Text cuando se edita un fichero, a menos que la extensión del nombre del fichero, una primer línea no en blanco o variables locales le indiquen lo contrario.

El modo Text entre otras acciones, establece la tabla de sintaxis para trabajar adecuadamente a los escritores. En el modo Text, Emacs considera un apóstrofe como parte de una palabra como una letra; pero Emacs no considera un punto o un espacio como parte de una palabra. De este modo, M-f se mueve a través de ‘d'Alcañiz’. Por otro lado, en el modo C, M-f se detiene justo despues de la ‘d’ de ‘d'Alcañiz’.

La segunda línea hace que Emacs active el modo Auto Fill cuando cambia al modo Text. En el modo Auto Fill, Emacs rompe automáticamente una línea que es demasiado ancha y mueve la parte exedente a la siguiente línea. Emacs rompe las líneas entre palabras, no dentro de ellas.

Cuando el modo Auto Fill está desactivado, las líneas continúan hacia la derecha a medida que se escriben. Dependiendo de como se establece el valor de truncate-lines, las palabras que escribas desaparecen del lado derecho de la pantalla, o bien se mostraran, de una manera bastante fea e ilegible, como una línea continua en la pantalla.

Además, en esta parte de mi fichero .emacs, le digo a los comandos de relleno que inserten dos espacios después de dos puntos:

(setq colon-double-space t)

Alias de correo

Aquí hay un setq que ‘activa’ el alias de correo, junto con mas recordatorios.

;;; Modo Correo
; Para entrar en el modo correo, presiona ‘C-x m’
; Para entrar en RMAIL (para leer el correo),
; presiona ‘M-x rmail’
(setq mail-aliases t)

Este comando setq asigna el valor de la variable mail-aliases a t. Ya que t significa verdadero, la línea dice, “Sí, usa alias de correo.”

Los alias de correo son nombres cortos convenientes para direcciones de correo largas o para listas de direcciones de correo. El fichero donde guardas tus ‘aliases’ es ~/.mailrc. Se escribe un alias como este:

alias geo george@foobar.wiz.edu

Cuando escribas un mensaje a Jorge, envialo a la dirección ‘geo’; el correo automáticamente expandirá ‘geo’ a la dirección completa.

Modo Indent Tabs

Por defecto, Emacs inserta tabulaciones en lugar en espacios múltiples cuando se da formato a una región. (Por ejemplo, se podrían indentar muchas líneas de texto a la vez con el comando indent-region.) Los tabuladores se ven bien en un terminal o con impresión ordinaria, pero producen una salida mal indentada cuando se usa TEX o Texinfo ya que TEX ignora los tabuladores.

Lo siguiente desactiva el modo de Indent Tabs:

;;; Prevenir Tabulaciones Extrañas
(setq-default indent-tabs-mode nil)

Observa que esta línea usa setq-default en vez de el comando setq que hemos visto antes. El comando setq-default asigna valores solo en búfers que no tienen sus propios valores locales para la variable.

Consulta las secciones Tabuladores versus Espacios y Variables Locales en Ficheros en El Manual de GNU Emacs.

Algunos Atajos de teclado

Ahora algunos atajos personales:

;;; Compara ventanas
(global-set-key "\C-cw" 'compare-windows)

compare-windows es un comando excelente que compara el texto de la ventana actual con el texto de la siguiente ventana. Realiza la comparación empezando en el punto en cada ventana, moviendose a través del texto en cada ventana en la medida en que coincidan. Uso este comando todo el tiempo.

Esto también muestra como configurar una atajo globalmente, para todos los modos

El comando es global-set-key. Va seguido por el atajo de teclado. En un fichero .emacs, el atajo se escribe como se muestrta: \C-c significa ‘control-c’, lo que significa ‘presionar la tecla de control y la tecla c al mismo tiempo’. La w significa ‘presionar la tecla w’. El atajo esta rodeado por comillas dobles. En el atajo, se presionaria esto como C-c w. (Si estuvieras enlazando una tecla META, como M-c, en lugar de la tecla CTRL, se escribiría \M-c en el fichero .emacs. Consulta la Seccion Teclas de atajos en tu fichero Init en El Manual de GNU Emacs, para más detalles.)

El comando invocado por las teclas es compare-windows. Observa que compare-windows es precedido por una comilla simple; de otro modo, Emacs intentaría primero evaluar el símbolo para determinar su valor.

Estas tres cosas, las comillas dobles, la barra invertida antes de la ‘C’, y la comilla simple son partes necesarias en los atajos de teclado que tiendo a olvidar. Afortunadamente, he llegado a recordarlo mirando mi fichero .emacs, y adaptando lo que hay alli.

En cuanto al atajo: C-c w, combina la tecla prefija, C-c, con un caracter simple, en este caso, w. Este conjunto de teclas, C-c seguido por un solo caracter, esta estrictamente reservado para uso individual. (Llamo a estas teclas ‘propias’, ya que son para mi propio uso). Siempre debes ser capaz de crear un atajo de este tipo para tu propio uso sin pisar el atajo de otra persona. Si alguna vez escribes una extensión a Emacs, por favor, evita tomar cualquiera de estos atajos para uso público. Crea un combinacion C-c C-w en su lugar. De lo contrario, nos quedaremos sin atajos ‘propios’.

Aquí hay otro atajo, con un comentario:

;;; Atajo para ‘occur’
; Uso mucho occur, así que voy a asignarlo a un atajo:
(global-set-key "\C-co" 'occur)

El comando occur muestra todas las líneas en el buffer actual que contiene una coincidencia para una expresión regular. Las líneas que coinciden se muestran en un búfer llamado *Occur*. Esse buffer sirve como un menu para saltar a las ocurrencias.

Aquí se muestra como desasignar un atajo, para que no funcione:

;;; Desasociar ‘C-x f’
(global-unset-key "\C-xf")

Hay una razón para esto: Encuentro que inadvertidamente presiono C-x f en lugar de C-x C-f. En lugar de encontrar un fichero, como pretendia, accidentalmente asigno el ancho del modo Auto Fill, casi siempre a un tamaño que no quería. Como casi nunca reseteó mi ancho por defecto, simplemente desvinculo el atajo.

Lo siguiente reasocia un atajo existente:

;;; Reasocia ‘C-x C-b’ a ‘buffer-menu’
(global-set-key "\C-x\C-b" 'buffer-menu)

Por defecto, C-x C-b ejecuta el comando list-buffers. Este comando lista tus buffers en otra ventana. Como casi siempre quiero hacer algo en esa ventana, prefiero el comando buffer-menu, que no solo lista los buffers, sino que tambien mueve el punto a esa ventana.

Mapas de teclado

Emacs usa keymaps para registrar qué teclas llaman a qué comandos. Cuando se usa global-set-key para asignar los atajos de teclado a un unico comando en todas las partes de Emacs, se esta especificando el atajo en el current-global-map.

Los Modos específicos, como el modo C o el modo Texto, tiene sus propios mapas de teclado; los mapas de teclado específico del modo sobreescriben el mapa global que comparten todos los buffers.

La función global-set-key asocia, o reasocia, el mapa de teclas global. Por ejemplo, lo siguiente vincula el atajo C-x C-b a la función buffer-menu:

(global-set-key "\C-x\C-b" 'buffer-menu)

Los Mapas de teclado específicos de un modo se vinculan mediente la función define-key, que toma un mapa de teclado específico como un argumento, asi como el atajo y el comando. Por ejemplo, mi fichero .emacs contiene la siguiente expresión para vincular el comando texinfo-insert-@group a C-c C-c g:

(define-key texinfo-mode-map "\C-c\C-cg" 'texinfo-insert-@group)

La función texinfo-insert-@group en si misma es una pequeña extensión del modo Texinfo que inserta ‘@group’ dentro de un fichero Texinfo. Utilizo este comando todo el tiempo y prefiero escribir los tres atajos C-c C-c g en vez de los seis caracteres @ g r o u p. (‘@group’ y su acompañante ‘@end group’ son comandos que mantienen todo el texto que encierran en una página; muchos ejemplos multi-línea en este libro están rodeados por ‘@group … @end group’.)

Aquí está la definición de la función texinfo-insert-@group:

(defun texinfo-insert-@group ()
  "Inserta la cadena @group en un búfer Texinfo."
  (interactive)
  (beginning-of-line)
  (insert "@group\n"))

(Por supuesto, podría haber usado el modo Abbrev para evitar la fatiga de escribir, en vez de crear una función para insertar una palabra; pero prefiero tener atajos de teclado coherentes con otros atajos del modo Texinfo.)

Verás numerosas expresiones define-key en loaddefs.el asi como en las diversas librerias de los modos, como cc-mode.el y lisp-mode.el.

Para más información sobre los mapas de teclado, consulta la Seccion Personalizando Atajos de Teclado en El Manual de GNU Emacs, y la Seccion Mapas de Teclado en El Manual de Referencia de GNU Emacs Lisp.

Cargando ficheros

Muchas personas en la comunidad de GNU Emacs han escrito extensiones para Emacs. Con el paso del tiempo, estas extensiones a menudo se incluiyen en las nuevas versiones. Por ejemplo, los paquetes Calendar y Diary son ahora parte del GNU Emacs estándar, como lo es Calc.

Se puede usar el comando load para evaluar un fichero completo y, por lo tanto, instalar todas las funciones y variables del fichero en Emacs. Por ejemplo:

(load "~/emacs/slowsplit")

Esto evalúa, es decir, carga, el fichero slowsplit.el o si existe, el fichero compilado mas rapido slowsplit.elc, desde el subdirectorio emacs en tu directorio home. El fichero contiene la función split-window-quietly, que John Robinson escribió en 1989.

La función split-window-quietly divide una ventana con el mínimo de redisplay. La instalé en 1989 porque funcionaba bien con los viejos terminales de 1200 baudios que usaba entonces. Hoy en dia, solo de vez en cuando me encuentro con una conexión tan lenta, pero sigo usando la función porque me gusta la forma en que deja la mitad inferior de un búfer en la parte inferior de la nueva ventana y la mitad superior en la ventana superior.

Para reemplazar el atajo de teclado por defecto de split-window-vertically, se debe desactivar ese atajo y asociarlo de nuevo a split-window-quietly, asi:

(global-unset-key "\C-x2")
(global-set-key "\C-x2" 'split-window-quietly)

Si cargas muchas extensiones, como yo, entonces en lugar de especificar la ubicacion exacta del fichero de extencion, como se muestra arriba, se puede especificar el directorio como la funcion load-path de Emacs. En ese caso, cuando Emacs carga un fichero, buscará en ese directorio asi como en la lista predeterminada de directorios. (La lista predeterminada se especificada en paths.h cuando se construye Emacs.)

El siguiente comando añade tu directorio ~/emacs a la ruta de carga existente:

;;; Ruta de carga Emacs
(setq load-path (cons "~/emacs" load-path))

Por cierto, load-library es una interfaz interactiva para la función load. La función tiene el siguiente aspecto:

(defun load-library (library)
  "Carga la librería llamada LIBRARY.
Esta es una interfaz para la función ‘load’."
  (interactive
   (list (completing-read "Carga la librería: "
                          (apply-partially 'locate-file-completion-table
                                           load-path
                                           (get-load-suffixes)))))
  (load library))

El nombre de la función, load-libray, proviene del uso de ‘library’ como un sinónimo convencional de ‘fichero’. El codigo del comando load-library está en la librería files.el.

Otro comando interactivo que hace un trabajo ligeramente diferente es load-file. Consulta la Seccion Librerías de Código Lisp para Emacs en El Manual de GNU Emacs, para obtener información sobre la diferencia entre load-library y este comando.

Autocarga

En lugar de instalar una función cargando el fichero que la contiene, o evaluando la definición de función, puedes hacer que la función este disponible pero no instalarla hasta que se llame por primera vez. Este proceso se llama autocarga.

Cuando se ejecuta una función de autocarga, Emacs evalúa automáticamente el fichero que contiene la definición, y entonces llama a la función.

Emacs inicia mas rápido con funciones de autocarga, ya que sus librerías no se cargan de inmediato; pero es necesario esperar un momento cuando se utiliza por primera vez una función de este tipo, mientras se evalua el fichero que la contiene.

Las funciones poco utilizadas con frecuencia se autocargan. La librería loaddefs.el coniene cientos de funciones autocargadas, desde el modo bookmark-set hasta el modo wordstar-mode. Por supuesto, puedes llegar a utilizar una función ‘rara’ con frecuencia, cuando sea asi deberías cargar ese fichero de función con una expresión load en tu fichero .emacs.

En mi fichero .emacs, cargo 14 librerías que contienen funciones que de otro modo serían autocargadas. (En realidad, hubiera sido mejor incluir estos ficheros en mi Emacs ‘volcado’, pero lo olvide. Consulta la Sección Construyendo Emacs en El Manual de Referencia de GNU Emacs Lisp, y el fichero INSTALL para más informacion acerca del volcado.)

Tambien puedes querer incluir expresiones de autocarga en tu fichero .emacs. autoload es una función nativa que toma hasta cinco argumentos, los tres ultimos son opcionales. El primer argumento es el nombre de la función para autocargar. El segundo es el nombre del fichero a cargar. El tercer argumento es la documentación de la función, y el cuarto dice si la función puede llamarse de forma interactiva. El quinto argumento es el tipo de objeto––autoload puede manejar un mapa de teclado o macro asi como una función (el valor por defecto es una función).

Aquí hay un ejemplo típico:

(autoload 'html-helper-mode
  "html-helper-mode" "Editar documentos HTML" t)

(html-helper-mode es una alternativa mas antigua a html-mode, que es una parte estándar de la distribución.)

Esta expresión autocarga la función html-helper-mode. Se toma del fichero html-helper-mode-el (o desde la versión compilada html-helper-mode.elc, si existe). El fichero debe estar ubicado en un directorio específicado por load-path. La documentación dice que este es un modo para ayudar a editar documentos escritos en Lenguaje de Marcas de Hiper Texto. Puedes llamar a este modo interactivamente escribiendo M-x html-helper-mode. (Se necesita duplicar la documentacion de la funcion regular en la expresión de autocarga porque la función regular todavia no está cargada, así que su documentación no está disponible.)

Consulta la Seccion Autocarga en El Manual de Referencia de GNU Emacs Lisp, para más información.

Una extensión simple: linea-a-lo-alto-de-la-ventana

Aquí esta una extensión simple para Emacs que mueve linea donde esta el punto a la parte superior de la ventana. Uso esto todo el tiempo, para hacer que el texto sea mas fácil de leer.

Puedes poner el siguiente código dentro de un fichero separado y luego cargarlo desde tu fichero .emacs, o tambien incluirlo en tu fichero .emacs.

Aquí está la definición

;;; Línea a lo alto de la ventana;
;;; Reemplaza tres pulsaciones de teclado C-u 0 C-l
(defun linea-a-lo-alto-de-la-ventana ()
  "Mueve la línea donde esta el punto a lo alto de la ventana."
  (interactive)
  (recenter 0))

Ahora para el atajo.

Hoy en día, las teclas de función, así como los eventos del ratón y los caracteres no ascii se escriben entre corchetes, sin comillas. (En la versión 18 de Emacs y anteriores, se tenían que escribir diferentes combinaciones de teclas de función para cada terminal.)

Vincule linea-a-lo-alto-de-la-ventana con mi tecla de función F6 así:

(global-set-key [f6] 'linea-a-lo-alto-de-la-ventana)

Para más información, consulta la Seccion Reasociando Teclas en tu fichero init en El Manual de GNU Emacs.

Si ejecutas dos versiones de GNU Emacs, como las versiones 22 y 23, y usas un fichero .emacs, puedes seleccionar qué código evalúar con el siguiente condicional:

(cond
 ((= 22 emacs-major-version)
  ;; evalúa el codigo de la version 22
  (  ))
 ((= 23 emacs-major-version)
  ;; evalúa el codigo de la version 23
  (  )))

Por ejemplo, en las versiones recientes el cursor parpadea por defecto. Odio este tipo de parpadeo, como tambien otras caracteristicas, asi que puse lo siguiente en mi fichero .emacs15:

(when (>= emacs-major-version 21)
  (blink-cursor-mode 0)
  ;; Insertar una nueva línea cuando se presiona ‘C-n’ (next-line)
  ;; al fin del búfer
  (setq next-line-add-newlines t)
  ;; Activar la visualizacion de imagenes
  (auto-image-file-mode t)
  ;; Activa la barra de menu (esta barra tiene texto)
  ;; (Usa un argumento numérico para activarlo)
  (menu-bar-mode 1)
   ;; Desactivar la barra de herramientas (esta barra tiene iconos)
   ;; (Usa un argumento numérico para activarlo)
   (tool-bar-mode nil)
  ;; Desactiva el modo de informacion sobre la barra de herramientas
  ;; (Este modo hace que aparezcan explicaciones emergentes en los iconos)
  ;; (Usa un argumento numérico para activarlo)
  (tooltip-mode nil)
  ;; Si las sugerencias estan activadas, hace que aparezcan rapidamente
  (setq tooltip-delay 0.1)  ; por defecto es 0.7 segundos
   )

Colores X11

Se pueden especificar colores cuando se usa Emacs con el Sistema de Ventanas X del MIT.

No me gustan los colores por defecto y especifico los mios propios.

Aquí están las expresiones en mi fichero .emacs que establecen los valores:

;; Asigna el color del cursor
(set-cursor-color "white")

;; Asigna el color del ratón
(set-mouse-color "white")

;; Asigna el color del texto y del fondo
(set-foreground-color "white")
(set-background-color "darkblue")

;;; Asigna colores para el resaltado de busqueda y registros
(set-face-foreground 'highlight "white")
(set-face-background 'highlight "blue")

(set-face-foreground 'region "cyan")
(set-face-background 'region "blue")

(set-face-foreground 'secondary-selection "skyblue")
(set-face-background 'secondary-selection "darkblue")

;; Asigna colores al calendario
(setq calendar-load-hook
      '(lambda ()
         (set-face-foreground 'diary-face   "skyblue")
         (set-face-background 'holiday-face "slate blue")
         (set-face-foreground 'holiday-face "white")))

Las diferentes tonalidades de azul me alivian la vista y me impiden ver el parpadeo de la pantalla.

Alternativamente, podría haber establecido mis especificaciones en varios ficheros de inicialización X. Por ejemplo, podría establecer los colores del primer plano, fondo, cursor y puntero (es decir, ratón) en mi fichero ~/.Xresources de esta manera:

Emacs*foreground:   white
Emacs*background:   darkblue
Emacs*cursorColor:  white
Emacs*pointerColor: white

En cualquier caso, como no forma parte de Emacs, asigno el color raíz de mi ventana X en mi fichero ~/.xinitrc, asi16

xsetroot -solid Navy -fg white &

Ajustes misceláneos para un fichero .emacs

Aquí hay algunos ajustes misceláneas:

Corrigiendo Atajos de Teclados desagradables

Algunos sistemas vinculan las teclas de forma desagradable. Algunas veces, por ejemplo, la tecla CTRL aparece en un lugar incomodo en lugar de en el extremo izquierdo de la fila de inicio.

Normalmente, cuando las gente arregla este tipo de atajos, no cambia su fichero ~/.emacs. En su lugar, enlazan asocian las teclas apropiadas en la consola con los comandos loadkeys o install-keymap en su script de inicio e incluyen comandos xmodmap en su fichero .xinitrc o .Xsession de X Windows.

Para un script de inicio:

loadkeys /usr/share/keymaps/i386/qwerty/emacs2.kmap.gz

o

install-keymap emacs2

Para un fichero .xinitrc o un fichero .Xsession cuando la tecla Bloq Mayus esta en el extremo izquierdo de la fila de inicio:

# Asocia la tecla ‘Bloq Mayus’ a ‘Control’
# (Una interfaz de usuario tan rota, sugiere que los fabricantes de teclados
# piensan que las computadoras son máquinas de escribir de 1885.)

xmodmap -e "clear Lock"
xmodmap -e "add Control = Caps_Lock"

En un fichero .xinitrc o .Xsession, para convertir una tecla ALT en una tecla META:

# Algunos teclados mal diseñados tienen una tecla etiquetada ALT y no Meta
xmodmap -e "keysym Alt_L = Meta_L Alt_L"

Una línea de Modo modificada

Finalmente, una caracteristica que realmente me gusta: una linea de modo modificada.

Cuando trabajo a través de una red, olvido que máquina estoy usando. También, tiendo a perder la nocion de donde estoy, y en que linea esta el punto.

Así se restableci mi linea de modo para que se viera asi:

::-- foo.texi   rattlesnake:/home/bob/  Line 1  (Texinfo Fill) Top

Estoy visitando un fichero llamado foo.texi, en mi máquina rattlesnake en mi búfer /home/bob. Estoy en la línea 1, en modo Texinfo, y en la parte superior del búfer.

Mi fichero .emacs tiene una sección que luce asi:

;; Configura una linea de modo que me dice en que máquina, directorio,
;; y línea estoy, ademas de la información habitual.
(setq-default mode-line-format
 (quote
  (#("-" 0 1
     (help-echo
      "mouse-1: seleccionar ventana\nmouse-2: eliminar otras ..."))
   mode-line-mule-info
   mode-line-modified
   mode-line-frame-identification
   "    "
   mode-line-buffer-identification
   "    "
   (:eval (substring
           (system-name) 0 (string-match "\\..+" (system-name))))
   ":"
   default-directory
   #(" " 0 1
     (help-echo
      "mouse-1: seleccionar ventana\nmouse-2: eliminar otras ..."))
   (line-number-mode " Line %l ")
   global-mode-string
   #("   %[(" 0 6
     (help-echo
      "mouse-1: seleccionar ventana\nmouse-2: eliminar otras ..."))
   (:eval (mode-line-mode-name))
   mode-line-process
   minor-mode-alist
   #("%n" 0 2 (help-echo "mouse-2: widen" local-map (keymap ...)))
   ")%] "
   (-3 . "%P")
   ;;   "-%-"
   )))

Aquí, redefino la linea de modo por defecto. La mayoría de las partes son del original; pero hago algunos cambios. Estableci el formato de la linea de modo por defecto para permitir que varios modos, como Info, la sobreescriban.

Muchos elementos de la lista se explican por si mismos: mode-line-modified es una variable que indica si el búfer ha sido modificado, mode-name indica el nombre del modo, y así sucesivamente. Sin embargo, el formato parece complicado debido a dos caracteristicas que no hemos discutido.

La primera cadena en la línea de modo es un guión, ‘-’. En los viejos tiempos, se habría especificado simplemente como "-". Pero hoy en día, Emacs puede añadir propiedades a una cadena, como resaltado o, en este caso, una función de ayuda. Si colocas el cursor del ratón sobre el guión, aparece información de ayuda (de forma predeterminada, debe esperar siete décimas de segundo antes de que aparezca la información). Puedes cambiar esa temporización cambiando el valor de tooltip-delay.

El nuevo formato de cadena tiene una sintaxis especial:

#("-" 0 1 (help-echo "mouse-1: seleccionar ventana\n..."))

El #( inicia una lista. El primer elemento de la lista es la cadena en sí, solo un ‘-’. El segundo y tercer elemento especifica el rango al que se aplica el cuarto elemento. Un rango empieza después de un carácter, por lo que un cero significa que el rango empieza justo después del primer caracter; un 1 significa que el rango termina justo después del primer caracter. El tercer elemento es la propiedad del rango. Consiste en una lista de propiedades, un nombre de propiedad, en este caso, ‘help-echo’, seguido de un valor, en este caso, una cadena. El segundo, tercer y cuarto elemento de este nuevo formato de cadena se pueden repetir.

Para más información, consulta las Secciones Propiedades de Texto y Formato de Linea de Modo en El Manual de Referencia de GNU Emacs Lisp.

mode-line-buffer-identification muestra el nombre del buffer. Es una lista que inicia por (#("%12b" 0 4 …. El #( inicia la lista.

El ‘"%12b"’ muestra el nombre del actual búfer, usando la función buffer-name con la que estamos familiarizados; el ‘12’ especifica el número máximo de caracteres que serán mostrados. Cuando un nombre tiene pocos caracteres, el espacio en blanco se añade para rellenar este número. (Los nombres del búfer pueden y con frecuencia tendran mas de 12 caracteres; esta longitud funciona bien en una típica ventana de 80 columnas de ancho.)

:eval dice que hay que evaluar la siguiente forma y usar el resultado como una cadena para mostrar. En este caso, la expresión muestra el primer componente del nombre completo del sistema. El fin del primer componente es un ‘.’ (‘punto’), así que uso la función string-match para contar el tamaño del primer componente. La subcadena desde el caracter cero a este tamaño es el nombre de la máquina.

Esta es la expresión:

(:eval (substring
        (system-name) 0 (string-match "\\..+" (system-name))))

%[’ y ‘%]’ hacen que aparezca un par de corchetes por cada nivel de edición recursiva. ‘%n’ indica ‘Reducido’ cuando la reduccion esta en efecto. ‘%P’ indica el porcentaje del búfer que está por encima de la ventana, o ‘Top’, ‘Bottom’, o ‘All’. (Una ‘p’ minúscula indica el porcentaje por encima de la parte superior de la ventana.) ‘%-’ inserta suficientes guiones para rellenar la línea.

Recuerda, “No tiene que gustarte Emacs para que te guste”––Emacs puede tener diferentes colores, diferentes comandos y diferentes atajos que el Emacs por defecto.

Por otro lado, si quieres ‘sacar un Emacs de la caja’, sin personalización alguna, escribe:

emacs -q

Esto iniciara un Emacs que no cargue tu fichero de inicializacion ~/.emacs. Un Emacs normal y corriente. Nada más.

Depurancion

GNU Emacs tiene dos depuradores, debug y edebug. El primero esta incorporado nativamente dentro de Emacs y siempre está contigo; el segundo requiere que escribas una función antes de poder usarlo.

Ambos depuradores se describen extensivamente en la Seccion Depurando Programas Lisp en El Manual de Referencia de GNU Emacs Lisp. En este capítulo, voy a explicar un breve ejemplo de cada uno de ellos.

debug

Supón que has escrito una definición de función que pretende devolver la suma de los números números desde 1 a un número dado. (Esta es la función triangulo discutida anteriormente. Para mas información, consulta la Seccion Ejemplo con contador decreciente.)

Sin embargo, tu definición de función tiene un error. Has escrito 1= en lugar de 1-. Aquí está la definición rota:

(defun triangulo-defectuoso (numero)
  "Devuelve la suma de números desde el 1 hasta e incluyendo a NUMERO."
  (let ((total 0))
    (while (> numero 0)
      (setq total (+ total numero))
      (setq numero (1= numero)))     ; Error aquí.
    total))

Si estas leyendo esto en Emacs, puedes evaluar la definición de la manera habitual. Veras aparecer triangulo-defectuoso en el área de eco.

Ahora evalúa la función triangulo-defectuoso con un argumento de 4:

(triangulo-defectuoso 4)

En un GNU Emacs reciente, se creará e ingresaras a un búfer *Backtrace* que dice:

---------- Buffer: *Backtrace* ----------
Debugger entered--Lisp error: (void-function 1=)
  (1= numero)
  (setq numero (1= numero))
  (while (> numero 0)
     (setq total (+ total numero))
     (setq numero (1= numero)))
  (let ((total 0))
     (while (> numero 0)
       (setq total (+ total numero))
       (setq numero (1= numero)))
    total)
  triangulo-defectuoso(4)
  eval((triangulo-defectuoso 4) nil)
  elisp--eval-last-sexp(nil)
  eval-last-sexp(nil)
  call-interactively(eval-last-sexp)
---------- Buffer: *Backtrace* ----------

(He reformateado ligeramente este ejemplo; el depurador no corta las lineas largas. Como de costumbre, se puede salir del depurador escribiendo q en el buffer *Backtrace*.)

En la práctica, para un error tan simple como este, la línea de ‘error Lisp’ dira lo que se necesita saber para corregir la definición. La función 1= está ‘vacía’.

Sin embargo, suponiendo que no estas seguro de lo que está pasando, puedes leer el registro completo.

En este caso, necesitas ejecutar una versión reciente de GNU Emacs, que inicie automáticamente el depurador que te coloca en el búfer *Backtrace*; o bien, necesitas iniciar manualmente el depurador como se describe a continuacion.

Lee el búfer *Backtrace* de abajo hacia arriba; emacs te informa la causa que provoco el error. Emacs hizo una llamada interactiva a C-x C-e (eval-last-sexp), que produjo la evaluación de la expresión triangulo-defectuoso. Cada línea anterior cuenta lo que el intérprete Lisp evaluó a continuacion.

La tercera línea iniciando en la parte superior del búfer es

(setq number (1= number))

Emacs intentó evaluar esta expresión; para ello, intentó evaluar la expresión interna mostrada en la segunda línea desde arriba:

(1= number)

Aquí es donde ocurrio el error; como dice en la línea superior:

Debugger entered--Lisp error: (void-function 1=)

Puedes corregir el error, reevalúar la definición de función, y a continuacion ejecutar de nuevo la prueba.

debug-on-entry

Un GNU Emacs actual inicia el depurador automáticamente cuando la función tiene un error.

Por cierto, se puede iniciar el depurador manualmente para todas las versiones de Emacs; la ventaja es que el depurador se ejecuta incluso si no hay ningun error en el código. Algunas veces, ¡tu código estará libre de errores!

Puedes entrar en el depurador llamando a la función debug-on-entry.

Presiona:

M-x debug-on-entry RET triangulo-defectuoso RET

Ahora, evalúa lo siguiente:

(triangulo-defectuoso 5)

Todas las versiones de Emacs crearán un búfer *Backtrace* e informaran que estas iniciando la evaluacion de la función triangulo-defectuoso:

---------- Buffer: *Backtrace* ----------
Debugger entered--entering a function:
* triangulo-defectuoso(5)
  eval((triangulo-defectuoso 5))
  eval-last-sexp-1(nil)
  eval-last-sexp(nil)
  call-interactively(eval-last-sexp)
---------- Buffer: *Backtrace* ----------

Ya en el búfer *Backtrace*, pulsa d. Emacs evaluará la primer expresión en triangulo-defectuoso; el búfer tendra este aspecto:

---------- Buffer: *Backtrace* ----------
Debugger entered--beginning evaluation of function call form:
* (let ((total 0)) (while (> numero 0) (setq total ...)
        (setq numero ...)) total)
* triangulo-defectuoso(5)
  eval((triangulo-defectuoso 5))
  eval-last-sexp-1(nil)
  eval-last-sexp(nil)
  call-interactively(eval-last-sexp)
---------- Buffer: *Backtrace* ----------

Ahora, presiona d de nuevo, ocho veces, lentamente. Cada vez que presionas d Emacs evaluará otra expresión en la definición de función.

Eventualmente, el búfer se vera asi:

---------- Buffer: *Backtrace* ----------
Debugger entered--beginning evaluation of function call form:
* (setq numero (1= numero))
* (while (> numero 0) (setq total (+ total numero))
        (setq numero (1= numero)))
* (let ((total 0)) (while (> numero 0) (setq total ...)
        (setq numero ...)) total)
* triangulo-defectuoso(5)
  eval((triangulo-defectuoso 5))
  eval-last-sexp-1(nil)
  eval-last-sexp(nil)
  call-interactively(eval-last-sexp)
---------- Buffer: *Backtrace* ----------

Finalmente, después de escribir d dos veces más, Emacs llegara al error y las dos líneas superiores del buffer *Backtrace* se veran así:

---------- Buffer: *Backtrace* ----------
Debugger entered--Lisp error: (void-function 1=)
* (1= numero)
…
---------- Buffer: *Backtrace* ----------

Presionando d, fuiste capaz de pasear a través de la función.

Se puede salir de un buffer *Backtrace* pulsando q; esto cierra el registro, pero no cancela debug-on-entry.

Para cancelar el efecto de debug-on-entry, llama a cancel-debug-on-entry y el nombre de la función, asi:

M-x cancel-debug-on-entry RET triangulo-defectuoso RET

(Si está leyendo esto en Emacs, cancela debug-on-entry ahora.)

debug-on-quit y (debug)

Ademas de configurar debug-on-error o llamar a debug-on-entry, hay otras dos maneras de iniciar debug.

Puedes iniciar debug cada vez que presiones C-g (keyboard-quit) configurando la variable debug-on-quit en t. Esto es útil para depurar bucles infinitos.

O bien, puedes insertar un línea que diga (debug) dentro de tu código donde quieras que inicie el depurador, así:

(defun triangulo-defectuoso (numero)
  "Devuelve la suma de números desde el 1 hasta e incluyendo a NUMERO."
  (let ((total 0))
    (while (> numero 0)
      (setq total (+ total numero))
      (debug)                         ; Empieza el depurador.
      (setq number (1= number)))      ; Error aquí.
    total))

La función debug se describe en detalle en El Depurador Lisp en El Manual de Referencia GNU Emacs Lisp.

El depurador de nivel de fuente edebug

Edebug es un depurador a nivel de fuente. Edebug normalmente muestra el codigo fuente que estás depurando, con una flecha a la izquierda que muestra que línea se está ejecutando actualmente.

Es posible desplazarse a través de la ejecución de una función, línea a línea, o ejecutarla rápidamente hasta alcanzar un punto de ruptura donde la ejecución se detenga.

Edebug se describe en la Seccion Edebug en El Manual de Referencia de GNU Emacs Lisp.

Aquí hay una función con errores de triangulo-recursivo. Consulta la Sección Recursión en lugar de un contador, para una revisión de la misma.

(defun triangulo-recursivo-defectuoso (numero)
  "Devuelve la suma de números desde el 1 hasta e incluyendo a NUMERO.
Usa recursion."
  (if (= numero 1)
      1
    (+ numero
       (triangulo-recursivo-defectuoso
        (1= numero)))))                 ; Error aquí.

Normalmente, se instalaría esta definición colocando el cursor después del parentesis que cierra la funcion y presionando C-x C-e (eval-last-sexp) o bien colocando el cursor dentro de la definición y escribiendo C-M-x (eval-defun). (Por defecto, el comando eval-defun solo funciona en modo Emacs Lisp o en el modo interactivo de Lisp.)

Sin embargo, para preparar esta definición de función para Edebug, se debe primero instrumentar el código usando un comando diferente. Se puede hacer esto colocando el cursor dentro o justo después de la definición y escribir

M-x edebug-defun RET

Esto hará que Emacs cargue Edebug automáticamente si aun no está cargado, y que intrumente correctamente la función.

Después de instrumentar la función, coloca el cursor después de la siguiente expresión y escribe C-x C-e (eval-last-sexp):

(triangulo-recursivo-defectuoso 3)

Saltaras de nuevo al codigo fuente de triangulo-recursivo-defectuoso y el cursor estara al principio de la linea if de la función. Ademas, verás la punta de una flecha a la izquierda de esa línea donde se está ejecutando la función. (En los siguientes ejemplos, se muestra la flecha con ‘=>’; en un sistema de ventanas, se puede ver la flecha como un triángulo sólido (‘▸’) en el ‘borde’ de la ventana.)

=>★(if (= numero 1)

En el ejemplo, la posición del punto se muestra con una estrella, ‘★’.

Si ahora presionas SPC, el punto se moverá a la siguiente expresión a ser ejecutada; la línea se vera asi:

=>(if ★(= numero 1)

Al continuar presionando SPC, el punto se moverá de una expresión a otra. Al mismo tiempo, siempre que una expresión devuelva un valor, este valor se mostrara en el área de eco. Por ejemplo, después de mover el punto pasado numero, se verá lo siguiente:

Result: 3 (#o3, #x3, ?\C-c)

Esto significa que el valor de numero es 3, que es tres en octal, tres en hexadecimal, y ‘control-c’ en ASCII (la tercer letra del alfabeto, en caso de que se necesite conocer esta información).

Puedes continuar moviéndote a través del código hasta que llegues a la línea con el error. Antes de la evaluación, esta línea se ve asi:

=>        ★(1= numero)))))               ; Error aquí.

Cuando presiones SPC de nuevo, aparecera un mensaje de error que dice:

Symbol's function definition is void: 1=

Este es el error.

Presiona q para salir de Edebug.

Para eliminar la instrumentación de una definición de función, simplemente se reevalúa con un comando que no la instrumente. Por ejemplo, se podría colocar el cursor después del parentesis que cierra la definición y presionar C-x C-e.

Edebug hace mucho mas que caminar atravez de una función. Se puede configurar para que corra por si solo, deteniendose solo en un error o en puntos específicos, se puede hacer que muestre los valores cambiantes de varias expresiones; encontrar cuantas veces se llama a una función, y mucho más.

Edebug se describe en Edebug en El Manual de Referencia de GNU Emacs Lisp.

Ejercicios de depuración

Conclusión

Hemos llegado al fin de esta Introducción. Has aprendido lo suficiente sobre programación en Emacs Lisp para establecer valores, escribir ficheros .emacs para tí y tus amigos, y escribir personalizaciones y extensiones simples para Emacs.

Este es un lugar para parar. O, si lo deseas, seguir adelante, y aprender por ti mismo.

Se han aprendido algunos de los aspectos basicos de la programación. Pero solo algunos. Todavía hay muchos conceptos que son fáciles de usar y que no hemos tocado.

Otra ruta a seguir ahora mismo se encuentra entre el codigo fuente de Emacs y en El Manual de Referencia de GNU Emacs.

El codigo de Emacs Lisp es una aventura. Cuando lo lees y te encuentras con una función o expresión que no es familiar, necesitas imaginar o averiguar lo qué hace.

Consulta el Manual de Referencia. Es una descripcion completa y bastante fácil de leer de Emacs Lisp. Está escrito no solo para expertos, sino tambien para personas que saben lo que tu sabes. (El Manual de Referencia viene con la distribución de GNU Emacs. Al igual que esta introducción, viene como un fichero fuente de Texinfo, para que puedas leerlo en linea y como una composicion tipografica, o como un libro impreso.)

Puedes acudir a la ayuda en linea que es parte de GNU Emacs: la documentación en linea para todas las funciones y variables, y find-tag, el programa que te lleva al codigo fuente.

He aquí un ejemplo de cómo exploro las fuentes. Por su nombre, simple.el es el fichero que mire primero, hace mucho tiempo. Ocurre que algunas de las funciones en simple.el son complicadas, o al menos parecen complicadas a primera vista. La función open-line, por ejemplo, parece complicada.

Es posible que desees explorar esta función lentamente, como hicimos con la función forward-sentence. (Consulta la Seccion La función forward-sentence.) O tal vez quieras saltarte esta función y mirar otra, como split-line. No es necesario leer todas las funciones. Segun contar-palabras-en-definicion, la función split-line contiene 102 palabras y símbolos.

Aunque es pequeña, split-line contiene expresiones que no hemos estudiado: skip-chars-forward, indent-to, current-column e insert-and-inherit.

Considera la función skip-chars-forward. (Forma parte de la definición de función para back-to-indentation, que se muestra en la Seccion de Repaso.)

En GNU Emacs, puedes encontrar más informacion acerca de skip-chars-forward presionando C-h f (describe-function) y el nombre de la función. Esto te proporciana la documentación de la función.

Puedes ser capaz de adivinar lo que hace una función con un buen nombre como indent-to; o en su defecto puedes buscar su descripcion. Por cierto, la función describe-function en sí misma está en help.el; es una de esas funciones largas, pero descifrables. ¡Puedes buscar describe-function usando el comando C-h f!

En este caso, dado que el código es Lisp, el búfer *Help* contiene el nombre de la librería que contiene el origen de la función. Puedes colocar el punto sobre el nombre de la librería y pulsar la tecla RET, que en está situación está asociada a help-follow, y ser llevado directamente al codigo fuente, de la misma manera que con M-. (find-tag).

La definición para describe-function ilustra como personalizar las expresiones interactive sin usar los códigos de caracter estándar y muestra como crear un búfer temporal.

(La función indent-to esta escrita en C en lugar de Emacs Lisp; es una función ‘nativa’. Cuando se configura correctamente, help-follow te lleva a su fuente al igual que find-tag.)

Puedes mirar la fuente de una función usando find-tag, que está vinculada a M-.. Finalmente, puedes encontrar lo que el Manual de Referencia tiene que decir visitando el manual en Info, e ingresando i (Info-index) y el nombre de la función, o buscando la función en el índice de una copia impresa del manual.

Del mismo modo, puedes encontrar que significa mediante insert-and-inherit.

Otros ficheros fuente interesantes son paragraphs.el, loaddefs.el y loadup.el. El fichero paragraphs.el incluye funciones cortas y faciles de entender, asi como funciones mas extensas. El fichero loaddefs.el contiene muchos autoloads estándar y muchos mapas de teclado. Nunca he visto todo; solo algunas partes. loadup.el es el fichero que carga las partes estándar de Emacs; te dice mucho sobre como se construye Emacs. (Para mas informacion, consulta la Seccion Construyendo Emacs en El Manual de Referencia de GNU Emacs Lisp.)

Como he dicho, has aprendido algunos detalles; sin embargo, y muy importante, apenas hemos tocado aspectos importantes de la programación; no se ha dicho nada acerca de como clasificar la información, excepto usar la función predefinida sort; no he dicho nada acerca de cómo almacenar la información, excepto usar variables y listas; no he dicho nada acerca de como escribir programas que escriben programas. Esto son temas para otro tipo de libro, y otro tipo de aprendizaje.

Lo que has hecho es aprender lo suficiente para hacer mucho trabajo práctico con GNU Emacs. Lo que has hecho es comenzar. Este es el final de un comienzo.

Apéndice A: La función el-el

Algunas veces cuando se se escribe texto, se duplican palabras––como con “se se” cerca del principio de esta frase. En mi caso, encuentro que lo más frecuente, es duplicar “el”; por lo tanto, llamo a la función el-el para detectar las palabras duplicadas.

Como primer paso, se puede utilizar la siguiente expresion regular para buscar duplicados:

\\(\\w+[ \t\n]+\\)\\1

Esta regexp coincide con uno o más caracteres que constituyen palabras seguidas por uno o más espacios, tabuladores, o líneas nuevas. Sin embargo, no detecta palabras duplicadas en líneas diferentes, ya que el final de la la primer palabra, el final de la línea, es diferente del final de la segunda palabra, un espacio. (Para más información acerca de expresiones regulares, mira el Capitulo 12 Búsqueda de Expresiones Regulares, asi como la Seccion Sintaxis de Expresiones Regulares en El Manual de GNU Emacs, y la Seccion Expresiones Regulares en El Manual de Referencia de GNU Emacs Lisp.)

Puedes intentar buscar solo duplicados para caracteres consecutivos que constituyen palabras, pero eso no funciona, ya que el patron detecta dobles como las dos ocurrencias de ‘al’ en ‘igual al’.

Otra posible regexp busca caracteres que constituyen palabras, seguido por caracteres que no constituyen palabras, reduplicadas. Aquí, \\w+ coincide con uno o más caracteres que forman palabras y \\W* con cero o más caracteres que no forman palabras.

\\(\\(\\w+\\)\\W*\\)\\1

De nuevo, no es útil.

Aquí está el patrón que uso. No es perfecto, pero es lo suficientemente bueno. \\b coincide con la cadena vacía siempre y cuando este al principio o al final de una palabra; [^@ \n\t]+ coincide con una o más ocurrencias de cualquier caracter que no son sea el signo @, un espacio, nueva línea, o tabulador.

\\b\\([^@ \n\t]+\\)[ \n\t]+\\1\\b

Uno puede escribir expresiones más complicadas, pero encontre que esta expresión es lo suficientemente buena, así que la uso.

Aquí está la función el-el, tal y como la incluyo en mi fichero .emacs, junto con un practico atajo global:

(defun el-el ()
  "Busca hacia adelante una palabra duplicada."
  (interactive)
  (message "Buscando palabras duplicadas ...")
  (push-mark)
  ;; Este regexp no es perfecto
  ;; pero a pesar de todo, es bastante bueno:
  (if (re-search-forward
       "\\b\\([^@ \n\t]+\\)[ \n\t]+\\1\\b" nil 'move)
      (message "Palabra duplicada encontrada.")
    (message "Fin de búfer")))

;; Asocia ‘el-el’ a  C-c \
(global-set-key "\C-c\\" 'el-el)

Aquí está el texto de prueba:

uno dos dos tres cuatro cinco
cinco seis siete

Puedes sustituir la regexp con las mostradas anteriormente y probar cada una de ellas en esta lista.

Apéndice B: Manejando el anillo de la muerte

El anillo de la muerte es una lista que se transforma en un anillo por el funcionamiento de la función current-kill. Los comandos yank y yank-pop usan la función current-kill.

Este apéndice describe la función current-kill asi como los comandos yank y yank-pop, pero primero, examinemos el funcionamiento del anillo de la muerte.

El anillo de la muerte tiene una longitud maxima predeterminada de sesenta elementos; hacer una explicación del porque de este número quedaría demasiado extenso. En su lugar, pensemos qué ocurre si se establece en cuatro. Por favor, evalúa lo siguiente:

(setq old-kill-ring-max kill-ring-max)
(setq kill-ring-max 4)

Despues, por favor copia cada línea del siguiente ejemplo indentado dentro del anillo de la muerte. Se puede cortar cada línea con C-k o marcarla y copiarla con M-w.

(En un búfer de solo lectura, como el búfer *info*, el comando kill, C-k (kill-line), no eliminará el texto, solamente lo copiara al anillo de la muerte. Sin embargo, el ordenador puede emitir un beep. Alternativamente, para evitarlo, se puede copiar la región de cada línea con el comando M-w (kill-ring-save). Debes marcar cada línea para que este comando tenga exito, pero no importa en que extremo se coloque el punto o la marca.)

Por favor, invoca las llamadas en orden, de modo que los cinco elementos intenten llenar el anillo de la muerte.

primero un poco de texto
segundo fragmento de texto
tercer línea
cuarta línea de texto
quinta parte del texto

Luego encuentra el valor de kill-ring evaluando

kill-ring

que es:

("quinta parte del texto" "cuarta línea de texto"
 "tercer línea" "segundo fragmento de texto")

El primer elemento, ‘primero un poco de texto’, se descarto.

Para volver al valor anterior del tamaño del kill ring, evalúa:

(setq kill-ring-max old-kill-ring-max)

La función current-kill

La función current-kill cambia el elemento en el anillo de la muerte al que apunta kill-ring-yank-pointer. (Ademas, la función kill-new establece kill-ring-yank-pointer para que apunte al último elemento del anillo de la muerte. La función kill-new se utiliza directamente o indirectamente en las funciones kill-append, copy-region-as-kill, kill-ring-save, kill-line, y kill-region.)

La función current-kill es usada por yank y por yank-pop. Aquí está el código para current-kill:

(defun current-kill (n &optional do-not-move)
  "Rota el punto de pegado por N lugares, y entonces devuelve el corte.
Si N es cero, se establece ‘interprogram-paste-function’, y si se llama
devuelve una cadena, entonces esa cadena se añade al frente del
anillo de la muerte y devuelve el último corte.
Si el argumento opcional DO-NOT-MOVE no es nulo, entonces no mueve el
punto de pegado; solo devuelve el Nth corte hacia adelante.
   (let ((interprogram-paste (and (= n 0)
                                  interprogram-paste-function
                                  (funcall interprogram-paste-function)))))
    (if interprogram-paste
        (progn
          ;; Deshabilita la función de corte interprograma cuando se añade
          ;; el nuevo texto al anillo de la muerte, para que Emacs no intente
          ;; apropiarse de la selección con texto idéntico.
          (let ((interprogram-cut-function nil))
            (kill-new interprogram-paste))
          interprogram-paste)
      (or kill-ring (error "El anillo de la muerte esta vacio"))
      (let ((ARGth-kill-element
             (nthcdr (mod (- n (length kill-ring-yank-pointer))
                          (length kill-ring))
                     kill-ring)))
        (or do-not-move
            (setq kill-ring-yank-pointer ARGth-kill-element))
        (car ARGth-kill-element)))))

Recuerda también que la función kill-new asigna kill-ring-yank-pointer al último elemento del anillo de la muerte, lo que significa que todas las funciones que la llaman establecen el valor de forma indirecta: kill-append, copy-region-as-kill, kill-ring-save, kill-line y kill-region.

Aquí está la línea en kill-new, que se explica en la Seccion La función kill-new.

(setq kill-ring-yank-pointer kill-ring)

La función current-kill parece compleja, pero como de costumbre, se puede entender desmontandola pieza por pieza. Primero mírala en forma de esqueleto:

(defun current-kill (n &optional do-not-move)
  "Rota el punto de pegado por N lugares, y entonces devuelve el corte.
  (let varlist
    cuerpo…)

Esta función toma dos argumentos, uno de los cuales es opcional. Tiene una cadena de documentación. No es una función interactiva.

El cuerpo de la definición de función es una expresión let, que a su vez tiene un cuerpo asi como una varlist.

La expresión let declara una variable que solo será utilizable dentro de los limites de esta función. Esta variable se llama interprogram-paste y es para copiar a otro programa. No es para copiar dentro de esta instancia de GNU Emacs. La mayoría de los sistemas de ventanas proveen una facilidad para el pegado interprograma. Tristemente, esta facilidad normalmente solo provee el último elemento. La mayoría de los sistemas de ventanas no han adoptado un anillo de muchas posibilidades, a pesar de que Emacs lo ha proporcionado durante décadas.

La expresión if tiene dos partes, una si existe interprogram-paste y otra si no.

Consideremos la parte ‘si no’ o la parte-else de la función current-kill. (La parte-else utiliza la función kill-new, que ya hemos descrito. Consulta la Seccion La función kill-new.)

(or kill-ring (error "El anillo de la muerte esta vacio"))
(let ((ARGth-kill-element
       (nthcdr (mod (- n (length kill-ring-yank-pointer))
                    (length kill-ring))
               kill-ring)))
  (or do-not-move
      (setq kill-ring-yank-pointer ARGth-kill-element))
  (car ARGth-kill-element))

El código primero comprueba si el anillo de la muerte tiene contenido; de lo contrario, señala un error.

Observa que la expresión or es muy similar en longitud a una prueba con un if:

(if (zerop (length kill-ring))            ; parte-if
    (error "El Kill ring está vacío"))    ; parte-then
  ;; No hay parte-else

Si no hay nada en el anillo de la muerte, su tamaño debe ser cero y un mensaje de error se envía al usuario: ‘El kill ring está vacío’. La función current-kill usa una expresión or que es mas simple. Pero una expresión if nos recuerda lo que ocurre.

Esta expresión if usa la función zerop que devuelve verdadero si el valor que esta probando es cero. Cuando la prueba zerop es verdadera, se evalúa la parte-then del if. La parte-then es una lista que comienza con la función error, que es una función similar a la función message (Consulta la Sección La Función message) en el sentido que imprime un mensaje de una línea en el área de eco. Sin embargo, además de imprimir un mensaje, error también detiene la evaluacion de la funcion dentro de la cual esta embebida. Esto significa que el resto de la función no se evaluara si la longitud del anillo de la muerte es cero.

Entonces la función current-kill selecciona el elemento a devolver. La selección depende del número de lugares en los que current-kill rota y donde apunta kill-ring-yank-pointer.

A continuacion, el argumento opcional do-not-move es verdadero o el valor actual de kill-ring-yank-pointer se establece para apuntar a la lista. Finalmente, otra expresión devuelve el primer elemento de la lista incluso si el argumento do-not-move es verdadero.

En mi opinión, es ligeramente engañoso, al menos para humanos, usar el término ‘error’ como el nombre de la función error. Un término mejor sería ‘cancelar’. Estrictamente hablando, por supuesto, no se puede apuntar, mucho menos rotar un puntero a una lista que no tiene longitud, por lo que desde el punto de vista del ordenador, la palabra ‘error’ es correcta. Pero un humano espera intentar este tipo de cosas, aunque solo sea para averiguar si el anillo de la muerte esté lleno o vacío. Esto es un acto de exploración.

Desde el punto de vista humano, el acto de exploración y descubrimiento no es necesariamente un error, y por lo tanto, no debe etiquetarse como tal, ni siquiera en las entrañas de un ordenador. Tal como estan las cosas, el codigo de Emacs implica que un humano que está actuando virtuosamente, explorando su entorno, está cometiendo un error. Esto está mal. Incluso aunque el ordenador tome los mismos pasos que cuando hay un ‘error’, un término como ‘cancelar’ tendría una connotación mas clara.

Entre otras acciones, la parte-else de la expresión if establece el valor de kill-ring-yank-pointer a ARGth-kill-element cuando el anillo de la muerte tiene alguna cosa dentro y el valor de do-not-move es nil.

El código se ve asi:

(nthcdr (mod (- n (length kill-ring-yank-pointer))
             (length kill-ring))
        kill-ring)))

Esto necesita algún examen. A menos que no se suponga mover el puntero, la función current-kill cambia a donde apunta kill-ring-yank-pointer. Esto es lo que hace la expresión (setq kill-ring-yank-pointer ARGth-kill-element). También, claramente, ARGth-kill-element está siendo asignado para ser igual a algún cdr del anillo de la muerte, usando la función nthcdr que se describio en una sección anterior. (Consulta la Seccion copy-region-as-kill.) ¿Cómo hace esto?

Como vimos antes (en la Sección nthcdr), la función nthcdr funciona tomando repetidamente el cdr de una lista––toma el cdr, del cdr, del cdr

Las dos expresiones siguientes producen el mismo resultado:

(setq kill-ring-yank-pointer (cdr kill-ring))

(setq kill-ring-yank-pointer (nthcdr 1 kill-ring))

Sin embargo, la expresión nthcdr es más complicada. Utiliza la función mod para determinar que cdr seleccionar.

(Recuerda mirar primero las funciones internas, de hecho, tendremos que ir dentro del mod.)

La función mod devuelve el valor de su primer argumento módulo el segundo; es decir, devuelve el resto después de dividir el primer argumento por el segundo. El valor devuelto tiene el mismo signo que el segundo argumento.

Por lo tanto,

> (mod 12 4)
0  ;; porque no hay resto
> (mod 13 4)
1

En este caso, el primer argumento es con frecuencia mas pequeño que el segundo. Eso está bien.

> (mod 0 4)
0
> (mod 1 4)
1

Podemos adivinar lo que hace la función -. Es como + pero resta en lugar de sumar; la función - sustrae su segundo argumento del primero. Ademas, ya sabemos lo que hace la función length (Ver la Sección Descubrir la longitud de una lista: length). Devuelve la longitud de una lista.

Y n es el nombre del argumento requerido para la función current-kill.

Así que cuando el primer argumento de nthcdr es cero, la expresión nthcdr devuelve la lista entera, como puedes ver evaluando lo siguiente:

;; kill-ring-yank-pointer y kill-ring tienen una longitud de cuatro
;; and (mod (- 0 4) 4) ⇒ 0
(nthcdr (mod (- 0 4) 4)
        '("cuarta línea de texto"
          "tercer línea"
          "segundo fragmento de texto"
          "primero un poco de texto"))

Cuando el primer argumento para la función current-kill es uno, la expresión nthcdr devuelve la lista sin su primer elemento.

(nthcdr (mod (- 1 4) 4)
        '("cuarta línea de texto"
          "tercer línea"
          "segundo fragmento de texto"
          "primero un poco de texto"))

Por cierto, tanto kill-ring y kill-ring-yank-pointer son variables globales. Esto significa que cualquier expresión en Emacs Lisp puede acceder a ellas. No son como las variables locales establecidas por let o como los símbolos en una lista de argumentos. Solo se puede acceder a las variables locales dentro del let que las define o la función que las especifica en una lista de argumentos (y dentro de las expresiones llamadas por estas).

yank

Después de aprender sobre current-kill, el código para la función yank es casi fácil de entender.

La función yank no utiliza directamente la variable kill-ring-yank-pointer. Llama a insert-for-yank que llama a current-kill que establece la variable kill-ring-yank-pointer.

El código se ve asi:

(defun yank (&optional arg)
  "Reinserta (\"pega\") el último fragmento del texto cortado.
Más concretamente, vuelve a insertar el texto cortado o pegado más reciente.
Coloca el punto al final, y asigna la marca al principio.
Con solo \\[universal-argument] como argumento, realiza lo mismo pero coloca el
punto al principio (y la marca al final). Con el argumento N, reinserta
el N fragmento más recientemente cortado.

Cuando este comando inserta texto dentro del búfer, respeta
a ‘yank-excluded-properties’ y ‘yank-handler’ como describe
la cadena de documentación de ‘insert-for-yank-1’, que ve.

Ver también el comando \\[yank-pop]."
  (interactive "*P")
  (setq yank-window-start (window-start))
  ;; Si no llegamos hasta el final, hace que last-command
  ;; indique esto para el siguiente comando.
  (setq this-command t)
  (push-mark (point))
  (insert-for-yank (current-kill (cond
                                  ((listp arg) 0)
                                  ((eq arg '-) -2)
                                  (t (1- arg)))))
  (if (consp arg)
      ;; Esto es como exchange-point-and-mark,
      ;;     pero no activa la marca.
      ;; Es mas limpio para evitar la activación, aunque el bucle de
      ;; comandos desactivaría la marca porque insertamos texto.
      (goto-char (prog1 (mark t)
                   (set-marker (mark-marker) (point) (current-buffer)))))
  ;; Si llegamos hasta el final, hace que this-command lo indique.
  (if (eq this-command t)
      (setq this-command 'yank))
  nil)

La expresión clave es insert-for-yank, que inserta la cadena devuelta por current-kill, pero elimina algunas propiedades de texto de la misma.

Sin embargo, antes de llegar a esa expresión, la función establece el valor de yank-window-start a la posición devuelta por la expresión (window-start), la posición en la que la pantalla comienza actualmente. La función yank también asigna this-command y añade la marca.

Después de pegar el elemento apropiado, si el argumento opcional es un cons en vez de un número o nada, coloca el punto al principio del texto pegado y la marca al final.

(La función prog1 es como progn pero devuelve el valor de su primer argumento en vez del valor de su último argumento. Su primer argumento fuerza devolver la marca del búfer como un entero. Puede ver la documentación para estas funciones colocando el punto sobre ellas en este búfer y presionando C-h f (describe-function) seguido por un RET; la funcion por defecto.)

La última parte de la función indica que hacer cuando tiene exito.

yank-pop

Después de entender yank y current-kill, ya sabes como acercarte a la función yank-pop. Dejando fuera la documentación para ahorrar espacio, se ve asi:

(defun yank-pop (&optional arg)
  "…"
  (interactive "*p")
  (if (not (eq last-command 'yank))
      (error "El comando previo no fué un yank"))
  (setq this-command 'yank)
  (unless arg (setq arg 1))
  (let ((inhibit-read-only t)
        (before (< (point) (mark t))))
    (if before
        (funcall (or yank-undo-function 'delete-region) (point) (mark t))
      (funcall (or yank-undo-function 'delete-region) (mark t) (point)))
    (setq yank-undo-function nil)
    (set-marker (mark-marker) (point) (current-buffer))
    (insert-for-yank (current-kill arg))
    ;; Si es posible, vuelve a colocar el inicio de la ventana en el punto
    ;; en el que se encontraba el comando yank.
    (set-window-start (selected-window) yank-window-start t)
    (if before
        ;; Esto es es como exchange-point-and-mark,
        ;;     pero no activa la marca.
        ;; Es mas limpio para evitar la activación, aunque el bucle de
        ;; comandos desactivaría la marca porque insertamos texto.
        (goto-char (prog1 (mark t)
                     (set-marker (mark-marker)
                                 (point)
                                 (current-buffer))))))
  nil)

La función es interactiva con una pequeña p para que el argumento del prefijo sea procesado y pasado a la función. El comando solo se puede utilizar después del yank previo; de lo contrario, se envia un mensaje de error. Esta comprobacion utiliza la variable last-command que se asigna por yank y se discute en otra parte. (Consulta la Seccion copy-region-as-kill.

La cláusula let asigna la variable before a verdadero o falso dependiendo de si el punto está antes o después de la marca y luego se elimina la región entre punto y marca. Esta es la región que acaba de ser insertada por el yank previo y es este texto el que será reemplazado.

funcall llama a su primer argumento como una función, pasando los argumentos restantes. El primer argumento es el que devuelve la expresión or. Los dos argumentos restantes son las posiciones de punto y marca establecidas por el comando yank anterior.

Hay más, pero esta es la parte más dificil.

El fichero ring.el

De manera interesante, GNU Emacs posee un fichero llamado ring.el que proporciona muchas de las caracteristicas que acabamos de discutir. Sin embargo, funciones como kill-ring-yank-pointer no utilizan esta librería, posiblemente porque se escribieron antes.

Apéndice C: Un grafico con ejes etiquetados

Los ejes impresos ayudan a comprender un grafico. Para transmitir escalas. En un capítulo anterior (Ver Sección Preparar un grafico), escribimos código para imprimir el cuerpo de un grafico. Aquí escribimos el código para imprimir y etiquetar los ejes horizontales y verticales, a lo largo del cuerpo en si.

Puesto que las inserciones se colocan en un búfer a la derecha y debajo del punto, la nueva funcion de impresion de graficos debe imprimir primero el eje vertical Y, luego el cuerpo del grafico, y finalmente el eje horizontal X. Esta secuencia nos da el contenido de la función:

  1. Codigo de configuracion.

  2. Imprir el eje Y.

  3. Imprir el cuerpo del grafico.

  4. Imprir el eje X.

He aquí un ejemplo de como se ve un grafico terminado:

10 -
              *
              *  *
              *  **
              *  ***
 5 -      *   *******
        * *** *******
        *************
      ***************
 1 - ****************
     |   |    |    |
     1   5   10   15

En este grafico, tanto el eje vertical como el horizontal se etiquetan con números. Sin embargo, en algunos graficos, el eje horizontal es el tiempo y estaría mejor etiquetarlo con meses, así:

5 -      *
       * ** *
       *******
     ********** **
1 - **************
    |    ^      |
  Enero Junio Enero

De hecho, con un poco de reflexion, podemos encontrar fácilmente una variedad de esquemas de etiquetado vertical y horizontal. Nuestra tarea podría complicarse. Pero las complicaciones generan confusión. En vez de permitir esto, es mejor elegir un esquema de etiquetado simple para nuestro primer esfuerzo, y modificarlo o reemplazarlo después.

Estas consideraciones sugieren el siguiente esquema para la función imprimir-grafico:

(defun imprimir-grafico (lista-de-numeros)
  "documentacion…"
  (let ((altura  
        ))
    (imprimir-eje-Y altura  )
    (imprimir-cuerpo-grafico lista-de-numeros)
    (imprimir-eje-X  )))

Podemos a su vez trabajar en cada parte de la definición de la función imprimir-grafico.

La varlist de imprimir-grafico

Al escribir la función imprimir-grafico, la primera tarea es escribir la varlist en la expresión let. (Por el momento dejaremos de lado cualquier idea sobre hacer que la función sea interactiva o sobre los contenidos de su cadena de documentación.)

La varlist debe establecer varios valores. Claramente, la parte superior de la etiqueta para el eje vertical debe ser al menos la altura del grafico, lo que significa que debemos obtener esta información aquí. Ten en cuenta que la función imprimir-cuerpo-grafico también requiere esta información. No hay ninguna razón para calcular la altura del grafico en dos lugares diferentes, por lo que debemos modificar imprimir-cuerpo-grafico para aprovechar el cálculo.

De manera similar, tanto la función para imprimir la etiqueta del eje X y la función imprimir-cuerpo-grafico necesita aprender el valor del ancho de cada símbolo. Podemos realizar el cálculo aquí y cambiar la definición de imprimir-cuerpo-grafico que definimos en el capítulo anterior.

La longitud de la etiqueta para el eje horizontal debe ser al menos igual que la del grafico. Sin embargo, esta información solo se usa en la función que imprime el eje horizontal, por lo que no es necesario calcularla aqui.

Estos pensamientos nos llevan directamente a la siguiente forma para la varlist en el let de imprimir-grafico:

(let ((altura (apply 'max lista-de-numeros)) ; Primera versión.
      (ancho-del-simbolo (length simbolo-en-blanco)))

Como veremos, esta expresión no es del todo correcta.

La función imprimir-eje-Y

El trabajo de la función imprimir-eje-Y es imprimir una etiqueta para el eje vertical que tenga este aspecto:

10 -




 5 -



 1 -

La función debe pasarse a la altura del grafico, y luego debe construir e insertar los números y marcas apropiados.

Es suficientemente fácil ver en la figura como deberia ser la etiqueta del eje Y; pero decir con palabras, y luego escribir una definición de función para hacer el trabajo es otro asunto. No es del todo cierto decir que queremos un número y una marca cada cinco líneas: solo hay tres líneas entre el ‘1’ y el ‘5’ (líneas 2, 3 y 4), pero cuatro líneas entre el ‘5’ y el ‘10’ (líneas 6, 7, 8 y 9). Es mejor decir que se quiere un número y una marca en la quinta línea desde la parte inferior y en cada línea que sea un múltiplo de cinco.

La siguiente cuestión es que altura debe tener la etiqueta. Supóngamos que la altura máxima de la columna mas alta del grafico es siete. Debe la etiqueta mas alta en el eje Y ser ‘5 -’, ¿y el grafico sobresalir por encima de la etiqueta?, ¿o la etiqueta mas alta ser ‘7 -’, y marcar el pico del grafico? ¿o deberia ser ‘10 -’, que es un múltiplo de cinco, y ser mas alta que el valor más alto del grafico?

Se prefiere esta última forma. La mayoría de los graficos se dibujan dentro de rectángulos cuyos lados son un número integral de pasos de longitud––5, 10, 15, y así sucesivamente para una distancia de paso de cinco. Pero tan pronto como decidimos usar una altura de escalon para el eje vertical, descubrimos que la expresión simple en la varlist para calcular la altura es incorrecta. La expresión es (apply 'max lista-de-numeros). Esto devuelve la altura precisa, no la altura máxima más lo que sea necesario para redondear al múltiplo de cinco. Se requiere una expresión más compleja.

Como es habitual en casos como este, un problema complejo se simplifica si se divide en varios problemas mas pequeños.

Primero, considere el caso cuando el valor superior del grafico es un múltiplo integral de cinco––cuando eso es 5, 10, 15, o algún múltiplo de cinco. Podemos usar este valor como la altura del eje Y.

Una manera bastante simple para determinar si un número es múltiplo de cinco es dividirlo por cinco y ver si la división devuelve un resto. Si no hay ningun resto, el número es un múltiplo de cinco. De este modo, siete dividido por cinco tiene un resto de dos, y siete no es un múltiplo integral de cinco. Dicho de otra manera, en un lenguaje que recuerde al de un salon de clases, cinco se divide en siete una vez y el resto es dos. Sin embargo, cinco se divide en diez dos veces y no tiene resto: diez es un múltiplo integral de cinco.

Viaje lateral: Calcula un resto

En Lisp, la función para calcular un resto es %. La función devuelve el resto de su primer argumento dividido por su segundo argumento. Da la casualidad que % es una función en Emacs Lisp que no se puede descubrir usando apropos: no se encuentra nada al escribir M-x apropos RET resto RET. La unica forma de conocer la existencia de % es leer sobre ello en un libro como este o en las fuentes de Emacs Lisp.

Puedes probar la función % evaluando las dos siguientes expresiones:

(% 7 5)

(% 10 5)

La primer expresión devuelve 2 y la segunda 0.

Para probar si el valor devuelto es cero o algún otro número, podemos usar la función zerop. Esta función devuelve t si su argumento que debe ser un número, es cero.

> (zerop (% 7 5))
nil
> (zerop (% 10 5))
t

Por lo tanto, la siguiente expresión devolverá t si la altura del grafico es divisible por cinco:

(zerop (% altura 5))

(El valor de altura, por supuesto, se puede encontrar con (apply 'max lista-de-numeros).)

Por otro lado, si el valor de altura no es un múltiplo de cinco, queremos reajustar el valor al siguiente múltiplo de cinco. Esto es aritmética sencilla que usa funciones con las que ya estamos familiarizados. Primero, dividimos el valor de altura por cinco para determinar cuantas veces cinco entra en el número. De este modo, cinco entra en doce dos veces. Si agregamos uno a este cociente y lo multiplicamos por cinco, obtendremos el valor del siguiente múltiplo de cinco que es más grande que la altura. Cinco entra en doce dos veces. Se suma uno a dos, y se multiplica por cinco; el resultado es quince, que es el siguiente múltiplo de cinco que es mayor que doce. La expresión Lisp para esto es:

(* (1+ (/ altura 5)) 5)

Por ejemplo, al evaluar lo siguiente, el resultado es 15:

(* (1+ (/ 12 5)) 5)

A lo largo de esta discusión, hemos estado usando ‘cinco’ como el valor de espaciado para las etiquetas en el eje Y; pero es posible que queramos usar algún otro valor. Generalmente, debemos reemplazar ‘cinco’ con una variable a la que podamos asignar un valor. El mejor nombre que puedo pensar para esta variable es distancia-entre-etiquetas-del-eje-Y.

Usando este término, y una expresión if, producimos lo siguiente:

(if (zerop (% altura distancia-entre-etiquetas-del-eje-Y))
    altura
  ;; else
  (* (1+ (/ altura distancia-entre-etiquetas-del-eje-Y))
     distancia-entre-etiquetas-del-eje-Y))

Esta expresión devuelve el valor de la altura, bien, si la altura es un múltiplo del valor de la distancia-entre-etiquetas-del-eje-Y o realizando un calculo si el valor de altura es igual al siguiente múltiplo superior del valor de la distancia-entre-etiquetas-del-eje-Y.

Ahora podemos incluir esta expresión en la expresión let de la función imprimir-grafico (después de establecer el valor de la distancia-entre-etiquetas-del-eje-Y):

(defvar distancia-entre-etiquetas-del-eje-Y 5
  "Número de líneas desde una etiqueta del eje Y a la siguiente.")


(let* ((altura (apply 'max lista-de-numeros))
       (altura-de-la-linea-superior
        (if (zerop (% altura distancia-entre-etiquetas-del-eje-Y))
            altura
          ;; else
          (* (1+ (/ altura distancia-entre-etiquetas-del-eje-Y))
             distancia-entre-etiquetas-del-eje-Y)))
       (ancho-del-simbolo (length simbolo-en-blanco))))

(Observa el uso de la función let*: el valor inicial de la altura se calcula una vez con la expresión (apply 'max lista-de-numeros) y luego se usa el resultado para calcular su valor final. Consulta la Seccion La expresión let*, para conocer más sobre let*.)

Construir un elemento del eje Y

Cuando imprimimos el eje vertical, queremos insertar cadenas como ‘5 - ’ y ‘10 - ’ cada cinco líneas. Además, queremos que los números y los guiones se alineen, por lo que los numeros mas cortos se deben rellenar con espacios en blanco. Si alguna de las cadenas utiliza numeros de dos dígitos, las cadenas con numeros de un solo dígito deben incluir un espacio en blanco delante del número.

Para calcular la longitud del número, se usa la función length. Pero la función length solo funciona con una cadena, no con un número. Así que el número tiene que ser convertido de un número a una cadena. Esto se hace con la función number-to-string. Por ejemplo,

> (length (number-to-string 35))
2
> (length (number-to-string 100))
3

(number-to-string tambien se llama int-to-string; verás este nombre alternativo en varios ficheros fuente.)

Además, en cada etiqueta, cada número va seguido por una cadena ‘ - ’, a la que llamaremos marca-del-eje-Y. Esta variable se define con defvar:

(defvar marca-del-eje-Y " - "
   "La Cadena que sigue al número en una etiqueta del eje Y.")

El tamaño de la etiqueta Y es la suma del tamaño de la marca del eje Y y el tamaño del número de la parte superior del grafico.

(length (concat (number-to-string altura) marca-del-eje-Y)))

Este valor será calculado por la función imprimir-grafico en su varlist como ancho-completo-de-la-etiqueta-Y y luego se pasara. (obverva que no pensamos incluir esto en el varlist cuando se propuso por primera vez.)

Para hacer una etiqueta completa del eje vertical, se concatena la marca de etiqueta con un número; y los dos juntos pueden estar precedidos por uno o más espacios dependiendo de la longitud del número. La etiqueta consiste de tres partes: los espacios iniciales (opcionales), el número, y la marca. La función se pasa al valor del número para la fila específica, y el valor del ancho de la línea superior, que se calcula (solo una vez) por imprimir-grafico.

(defun elemento-del-eje-Y (numero ancho-completo-de-la-etiqueta-Y)
  "Construye una etiqueta NUMERADA
Un elemento numerado se parece a esto ‘  5 - ’,
y se rellena segun sea necesario, de modo que todos se
alineen con el elemento para el número mas grande."
  (let* ((espacios-de-relleno
         (- ancho-completo-de-la-etiqueta-Y
            (length
             (concat (number-to-string numero)
                     marca-del-eje-Y)))))
    (concat
     (make-string espacios-de-relleno ? )
     (number-to-string numero)
     marca-del-eje-Y)))

La función elemento-del-eje-Y concatena juntos los espacios de relleno, si los hay; el número, como una cadena; y la marca de etiqueta.

Para calcular cuantos espacios de relleno necesita la etiqueta, la función resta la longitud actual de la etiqueta––la longitud del numero más el tamaño de la marca––del ancho deseado para la etiqueta.

Los espacios en blanco se insertan utilizando la función make-string. Esta función tiene dos argumentos: el primero dice cuan larga será la cadena y el segundo es un símbolo para el caracter a insertar, en un formato espcial. El formato es un signo de interrogacion seguido por un espacio en blanco, como este, ‘? ’. Consulta la Seccion Tipo de Caracter en El Manual de Referencia de GNU Emacs Lisp, para obtener una descripción de la sintaxis de los caracteres. (Por supuesto, es posible que quieras reemplazar el espacio en blanco por algún otro caracter…. Ya sabes qué hacer.)

La función number-to-string se utiliza en la expresión de concatenación, para convertir el número en una cadena que se concatena con los espacios de relleno y la marca de la etiqueta.

Construir una columna del eje Y

Las funciones anteriores proporcionan todas las herramientas necesarias para construir una función que genere una lista de cadenas enumeradas y en blanco para inserta como la etiqueta del eje vertical:

(defun columna-del-eje-Y (altura ancho-de-la-etiqueta)
  "Construye la lista de etiquetas y cadenas en blanco del eje Y.
Para la ALTURA de la línea sobre la base y ANCHO-DE-LA-ETIQUETA."
  (let (eje-Y)
    (while (> altura 1)
      (if (zerop (% altura distancia-entre-etiquetas-del-eje-Y))
          ;; Insertar etiqueta.
          (setq eje-Y
                (cons
                 (elemento-del-eje-Y altura ancho-de-la-etiqueta)
                 eje-Y))
        ;; de lo contrario, insertar espacios en blanco.
        (setq eje-Y
              (cons
               (make-string ancho-de-la-etiqueta ? )
               eje-Y)))
      (setq altura (1- altura)))
    ;; Insertar línea base.
    (setq eje-Y
          (cons (elemento-del-eje-Y 1 ancho-de-la-etiqueta) eje-Y))
    (nreverse eje-Y)))

En esta función, empezamos con el valor de altura y restamos repetitivamente uno desde su valor. Después de cada resta, comprobamos si el valor es un multiplo integral de la distancia-entre-etiquetas-del-eje-Y. Si lo es, construimos una etiqueta numerada usando la función elemento-del-eje-Y; si no, construimos una etiqueta en blanco usando la función make-string. La línea base consiste en el número uno seguido por una marca de etiqueta.

La versión no del todo definitiva de imprimir-eje-Y

La lista construida por la función columna-del-eje-Y se pasa a la función imprimir-eje-Y, que inserta la lista como una columna.

(defun imprimir-eje-Y (altura ancho-completo-de-la-etiqueta-Y)
  "Inserta el eje Y usando ALTURA y ANCHO-COMPLETO-DE-LA-ETIQUETA-Y.
La altura debe ser la altura máxima del grafico.
El ancho completo es el ancho del mayor elemento de la etiqueta"
;; El valor de altura y ancho-completo-de-la-etiqueta-Y
;; se pasan por ‘imprimir-grafico’.
  (let ((inicio (point)))
    (insert-rectangle
     (columna-del-eje-Y altura ancho-completo-de-la-etiqueta-Y))
    ;; Coloca el punto en posicion para inserta el grafico.
    (goto-char inicio)
    ;; Mueve el punto hacia adelante segun el valor de ancho-completo-de-la-etiqueta-Y
    (forward-char ancho-completo-de-la-etiqueta-Y)))

imprimir-eje-Y usa la función insert-rectangle para inserta el eje Y creado por la función columna-del-eje-Y. Además, coloca el punto en la posición correcta para imprimir el cuerpo del grafico.

Puedes probar imprimir-eje-Y:

  1. Instala

    distancia-entre-etiquetas-del-eje-Y
    marca-del-eje-Y
    elemento-del-eje-Y
    columna-del-eje-Y
    imprimir-eje-Y
    
  2. Copia la siguiente expresión:

    (imprimir-eje-Y 12 5)
    
  3. Cambia al búfer *scratch* y coloca el cursor donde quieras que inicien las etiquetas de los ejes.

  4. Presiona M-: (eval-expression).

  5. coloca la expresión imprimir-cuerpo-grafico dentro del minibúfer con C-y (yank).

  6. Presiona RET para evaluar la expresión

Emacs imprimirá las etiquetas verticalmente, siendo la parte superior ‘10 - ’. (La función imprimir-grafico pasará el valor de altura-de-la-linea-superior, que en este caso terminara en 15, eliminando asi lo que podria aparecer como un error.)

La función imprimir-eje-X

Las etiquetas del eje X son muy parecidas a las etiquetas del eje Y, excepto que las marcas estan una linea por encima de los números. Las etiquetas deberian verse asi:

|   |    |    |
1   5   10   15

La primer marca está debajo de la primer columna del grafico y va precedida por varios espacios en blanco. Estos espacios proporcionan espacio en las filas para las etiquetas del eje Y. La segunda, tercera, cuarta, y subsiguientes marcas estan todas espaciadas por igual, de acuerdo al valor de distancia-entre-etiquetas-del-eje-X.

La segunda fila del eje X consiste en números, precedidos por varios espacios en blanco y también separados de acuerdo al valor de la variable distancia-entre-etiquetas-del-eje-X.

El valor de la variable distancia-entre-etiquetas-del-eje-X debe medirse en unidades de ancho-del-simbolo, ya que es posible que se desee cambiar el ancho de los símbolos utilizados para imprimir el cuerpo del grafico sin cambiar la forma en que se etiqueta el grafico.

La función imprimir-eje-X se constituye más o menos del mismo modo que la función imprimir-eje-Y excepto que tiene dos líneas: la línea de marcas y los números. Escribiremos una función separada para imprimir cada línea y luego combinarlas con la función imprimir-eje-X.

Este es un proceso de tres pasos:

  1. Escribir una función para imprimir las marcas del eje X, imprimir-linea-de-marcas-del-eje-X.

  2. Escribir una función para imprimir los números X, imprimir-linea-numerada-del-eje-X.

  3. Escribir una función para imprimir ambas líneas, la función imprimir-eje-X, usando imprimir-linea-de-marcas-del-eje-X e imprimir-linea-numerada-del-eje-X.

Marcas del Eje X

La primera función debe imprimir las marcas de etiqueta del eje X. Debemos especificar las marcas en sí y su espaciado:

(defvar distancia-entre-etiquetas-del-eje-X
  (if (boundp 'simbolo-en-blanco)
      (* 5 (length simbolo-en-blanco)) 5)
  "Número de unidades de una etiqueta del eje X a la siguiente.")

(Ten en cuenta que el valor de simbolo-en-blanco lo establece otro defvar. El predicado boundp comprueba si ya ha sido establecido; boundp devuelve nil si no lo ha sido. Si simbolo-en-blanco no hubiera sido enlazado y no usaramos esta construcción condicional, estrariamos el depurador y veriamos un mensaje de error diciendo ‘Debugger entered--Lisp error: (void-variable simbolo-en-blanco)

Aquí está el defvar para marca-del-eje-X:

(defvar marca-del-eje-X "|"
  "Cadena a insertar para apuntar a una columna en el eje X.")

El objetivo es crear una línea que se vea asi:

   |   |    |    |

La primer marca esta indentada de manera que quede debajo de la primera columna, que se indenta para dejar espacio para las etiquetas del eje Y.

Un elemento de marca consiste en espacios en blanco que se extienden desde una marca a la siguiente más el símbolo de la propia marca. El número de espacios en blanco se determinan por el ancho del símbolo de marca y la distancia-entre-etiquetas-del-eje-X.

El código se ve asi:

;;; elemento-de-marca-del-eje-X

(concat
 (make-string
  ;; Crea una cadena de espacios en blanco.
  (-  (* ancho-del-simbolo distancia-entre-etiquetas-del-eje-X)
      (length marca-del-eje-X))
  ? )
 ;; Concatena los espacios en blanco con el símbolo de marca.
 marca-del-eje-X)

A continuacion, determinamos cuantos espacios en blanco se necesitan para indentar la primer marca en la primera columna del grafico. Utilizamos el valor de ancho-completo-de-la-etiqueta-Y pasado por la función imprimir-grafico.

El código para crear espacios-de-relleno-del-eje-X se ve asi:

;; espacios-de-relleno-del-eje-X

(make-string ancho-completo-de-la-etiqueta-Y ? )

También necesitamos determinar la longitud del eje horizontal, que es el tamaño de la lista de números, y el número de marcas del eje horizontal:

;; longitud-X

(length lista-de-numeros)

;; longitud-marca

(* ancho-del-simbolo distancia-entre-etiquetas-del-eje-X)

;; numero-de-marcas-X
(if (zerop (% (longitud-X longitud-marca)))
    (/ (longitud-X longitud-marca))
  (1+ (/ (longitud-X longitud-marca))))

Todo esto nos lleva directamente a la función para imprimir la linea de marcas del eje X:

(defun imprimir-linea-de-marcas-del-eje-X
  (numero-de-marcas-X espacios-de-relleno-del-eje-X elemento-de-marca-del-eje-X)
  "Imprime marcas para el eje X."
    (insert espacios-de-relleno-del-eje-X)
    (insert marca-del-eje-X)  ; Debajo de la primer columna.
    ;; Inserta la segunda marca en el lugar adecuado.
    (insert (concat
             (make-string
              (-  (* ancho-del-simbolo distancia-entre-etiquetas-del-eje-X)
                  ;; Inserta el espacio en blanco al segundo símbolo de marca.
                  (* 2 (length marca-del-eje-X)))
              ? )
             marca-del-eje-X))
    ;; Inserta las marcas restantes.
    (while (> numero-de-marcas-X 1)
      (insert elemento-de-marca-del-eje-X)
      (setq numero-de-marcas-X (1- numero-de-marcas-X))))

La línea de números es igualmente sencilla:

Primero, creamos un elemento numerado con espacios en blanco antes de cada número:

(defun elemento-del-eje-X (numero)
  "Construye un elemento numerado del eje X."
  (let ((espacios-de-relleno
         (-  (* ancho-del-simbolo distancia-entre-etiquetas-del-eje-X)
             (length (number-to-string numero)))))
    (concat (make-string espacios-de-relleno ? )
            (number-to-string numero))))

A continuacion, creamos la función para imprimir la línea numerada, empezando con el número “1” para la primer columna:

(defun imprimir-linea-numerada-del-eje-X
  (numero-de-marcas-X espacios-de-relleno-del-eje-X)
  "Imprime linea de números del eje X"
  (let ((numero distancia-entre-etiquetas-del-eje-X))
    (insert espacios-de-relleno-del-eje-X)
    (insert "1")
    (insert (concat
             (make-string
              ;; Inserta espacios en blanco hasta el siguiente número.
              (-  (* ancho-del-simbolo distancia-entre-etiquetas-del-eje-X) 2)
              ? )
             (number-to-string numero)))
    ;; Inserta los números restantes.
    (setq numero (+ numero distancia-entre-etiquetas-del-eje-X))
    (while (> numero-de-marcas-X 1)
      (insert (elemento-del-eje-X numero))
      (setq numero (+ numero distancia-entre-etiquetas-del-eje-X))
      (setq numero-de-marcas-X (1- numero-de-marcas-X)))))

Finalmente, necesitamos escribir imprimir-eje-X para usar imprimir-linea-de-marcas-del-eje-X e imprimir-linea-numerada-del-eje-X.

La función debe determinar los valores locales de las variables utilizadas por imprimir-linea-de-marcas-del-eje-X e imprimir-linea-numerada-del-eje-X, y luego debe llamarlas. Ademas, debe imprimir el retorno de carro que separe las dos líneas.

La función consiste de una varlist que especifica cinco variables locales, y llamadas a cada una de las dos funciones de impresion de líneas:

(defun imprimir-eje-X (lista-de-numeros)
  "Imprime las etiquetas del eje al tamaño de LISTA-DE-NUMEROS."
  (let* ((espacios-de-relleno
          (make-string ancho-completo-de-la-etiqueta-Y ? ))
         ;; ancho-del-simbolo es provisto por imprimir-cuerpo-grafico
         (longitud-marca (* ancho-del-simbolo distancia-entre-etiquetas-del-eje-X))
         (longitud-X (length lista-de-numeros))
         (marca-X
          (concat
           (make-string
            ;; Crea una cadena de espacios en blanco.
            (-  (* ancho-del-simbolo distancia-entre-etiquetas-del-eje-X)
                (length marca-del-eje-X))
            ? )
           ;; Concatena espacios en blanco con el símbolo de la marca.
           marca-del-eje-X))
         (numero-de-marcas
          (if (zerop (% longitud-X longitud-marca))
              (/ longitud-X longitud-marca)
            (1+ (/ longitud-X longitud-marca)))))
    (imprimir-linea-de-marcas-del-eje-X numero-de-marcas espacios-de-relleno marca-X)
    (insert "\n")
    (imprimir-linea-numerada-del-eje-X numero-de-marcas espacios-de-relleno)))

Para probar imprimir-eje-X:

  1. Instala marca-del-eje-X, distancia-entre-etiquetas-del-eje-X, imprimir-linea-de-marcas-del-eje-X, tambien elemento-del-eje-X, imprimir-linea-numerada-del-eje-X, e imprimir-eje-X.

  2. Copia la siguiente expresión:

    (progn
     (let ((ancho-completo-de-la-etiqueta-Y 5)
           (ancho-del-simbolo 1))
       (imprimir-eje-X
        '(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16))))
    
  3. Cambia al búfer *scratch* y coloca el cursor donde quieras que comiencen las etiquetas del eje.

  4. Presiona M-: (eval-expression).

  5. Pega la expresión de prueba dentro del minibuffer con C-y (yank).

  6. Presiona RET para evaluar la expresión

Emacs imprimirá el eje horizontal así

     |   |    |    |    |
     1   5   10   15   20

Imprimir el grafico completo

Ahora estamos casi listos para imprimir el grafico completo.

La función para imprimir el grafico con las etiquetas apropiadas sigue el esquema que creamos anteriormente (Ver Sección Apéndice C: Un Grafico con Ejes Etiquetados), pero con adiciones.

Aquí está el esquema:

(defun imprimir-grafico (lista-de-numeros)
  "documentacion…"
  (let ((altura  
        ))
    (imprimir-eje-Y altura  )
    (imprimir-cuerpo-grafico lista-de-numeros)
    (imprimir-eje-X  )))

La versión final es diferente de lo que planeamos de dos maneras: primero, contiene valores adicionales calculados una vez en la varlist; segundo, tiene una opción para específicar el encremento de las etiquetas por fila. Esta última funcionalidad resulta esencial; de otro modo, un grafico puede tener más filas de las que caben en una pantalla o en una hoja de papel.

Esta nueva caracteristica requiere un cambio a la función columna-del-eje-Y, para añadirle un paso-vertical. Esta función se ve asi:

;;; Versión Final.
(defun columna-del-eje-Y
  (altura ancho-de-la-etiqueta &optional paso-vertical)
  "Construye una lista de etiquetas para el eje Y.
ALTURA es la altura máxima del grafico.
ANCHO-DE-LA-ETIQUETA es el ancho máximo de la etiqueta.
PASO-VERTICAL, una opción, es un entero positivo que
especifica cuanto se incrementa una etiqueta del eje Y en
cada línea. Por ejemplo, un paso de 5 significa que cada
línea es cinco unidades del grafico."
  (let (eje-Y
        (numero-por-linea (or paso-vertical 1)))
    (while (> altura 1)
      (if (zerop (% altura distancia-entre-etiquetas-del-eje-Y))
          ;; Inserta etiqueta.
          (setq eje-Y
                (cons
                 (elemento-del-eje-Y
                  (* altura numero-por-linea)
                  ancho-de-la-etiqueta)
                 eje-Y))
        ;; de otra forma, inserta espacios en blanco.
        (setq eje-Y
              (cons
               (make-string ancho-de-la-etiqueta ? )
               eje-Y)))
      (setq altura (1- altura)))
    ;; Inserta línea base.
    (setq eje-Y (cons (elemento-del-eje-Y
                        (or paso-vertical 1)
                        ancho-de-la-etiqueta)
                       eje-Y))
    (nreverse eje-Y)))

Los valores para la altura máxima del grafico y el ancho de un símbolo se calculan por medio de imprimir-grafico es su expresión let; por lo tanto, imprimir-cuerpo-grafico debe cambiarse para aceptarlos.

;;; Versión Final.
(defun imprimir-cuerpo-grafico (lista-de-numeros altura ancho-del-simbolo)
  "Imprime un gráfico de barras de la LISTA-DE-NUMEROS.
La lista-de-numeros consiste en los valores del eje Y.
ALTURA es la altura máxima del grafico.
ANCHO-DEL-SIMBOLO es el número de cada columna."
  (let (desde-la-posicion)
    (while lista-de-numeros
      (setq desde-la-posicion (point))
      (insert-rectangle
       (columna-del-grafico altura (car lista-de-numeros)))
      (goto-char desde-la-posicion)
      (forward-char ancho-del-simbolo)
      ;; Dibuja el grafico columna por columna.
        (sit-for 0)
        (setq lista-de-numeros (cdr lista-de-numeros)))
    ;; Coloca el punto para las etiquetas del eje X.
    (forward-line altura)
    (insert "\n")))

Finalmente, el código para la función imprimir-grafico:

;;; Versión Final.
(defun imprimir-grafico
  (lista-de-numeros &optional paso-vertical)
  "Imprime el gráfico de barras etiquetado de la LISTA-DE-NUMEROS.
La lista-de-numeros consiste en los valores del eje Y.

Opcionalmente, PASO-VERTICAL, un entero positivo, especifica cuanto
se incrementa la etiqueta del eje Y en cada línea. Por ejemplo, un
paso de 5 significa que cada fila tiene cinco unidades."
  (let* ((ancho-del-simbolo (length simbolo-en-blanco))
         ;; ALTURA es tanto el número más grande
         ;; como el número con mas digitos.
         (altura (apply 'max lista-de-numeros))
         (altura-de-la-linea-superior
          (if (zerop (% altura distancia-entre-etiquetas-del-eje-Y))
              altura
            ;; else
            (* (1+ (/ altura distancia-entre-etiquetas-del-eje-Y))
               distancia-entre-etiquetas-del-eje-Y)))
         (paso-vertical (or paso-vertical 1))
         (ancho-completo-de-la-etiqueta-Y
          (length
           (concat
            (number-to-string
             (* altura-de-la-linea-superior paso-vertical))
            marca-del-eje-Y))))

    (imprimir-eje-Y
     altura-de-la-linea-superior ancho-completo-de-la-etiqueta-Y paso-vertical)
    (imprimir-cuerpo-grafico
     lista-de-numeros altura-de-la-linea-superior ancho-del-simbolo)
    (imprimir-eje-X lista-de-numeros)))

Probando imprimir-grafico

Podemos probar la función imprimir-grafico con una pequeña lista de números:

  1. Instala las versiones finales de columna-del-eje-Y, imprimir-cuerpo-grafico, e imprimir-grafico (además del resto del código.)

  2. Copia la siguiente expresión:

    (imprimir-grafico '(3 2 5 6 7 5 3 4 6 4 3 2 1))
    
  3. Cambia al búfer *scratch* y coloca el cursor donde quieras que inicien las etiquetas de los ejes.

  4. presiona M-: (eval-expression).

  5. Pegua la expresión de prueba dentro del minibuffer con C-y (yank).

  6. Presiona RET para evaluar la expresión

Emacs imprimirá un grafico con este aspecto:

10 -


         *
        **   *
 5 -   ****  *
       **** ***
     * *********
     ************
 1 - *************

     |   |    |    |
     1   5   10   15

Por otro lado, si se pasa a imprimir-grafico un valor de paso-vertical de 2, evaluando esta expresión:

(imprimir-grafico '(3 2 5 6 7 5 3 4 6 4 3 2 1) 2)

El grafico tiene el siguiente aspecto:

20 -


         *
        **   *
10 -   ****  *
       **** ***
     * *********
     ************
 2 - *************

     |   |    |    |
     1   5   10   15

(Una pregunta: ¿es el ‘2’ en la parte inferior del eje vertical un error o una caracteristica? Si crees que es un error, y deberia ser un ‘1’, (o incluso un ‘0’), puedes modificar el codigo.)

Graficar números de palabras y símbolos

Ahora el gráfico para el que todo este código fué escrito: un gráfico que muestra cuantas definiciones de función contienen menos de 10 palabras y símbolos, cuantas contienen entre 10 y 19, cuantas entre 20 y 29 palabras, y así sucesivamente.

Este es un proceso de varios pasos. Primero asegúrate que has cargado todo el código requerido.

Es una buena idea restablecer el valor de cima-de-rangos en caso de que lo hayas asignado a algún valor diferente. Puedes evaluar lo siguiente:

(setq cima-de-rangos
 '(10  20  30  40  50
   60  70  80  90 100
  110 120 130 140 150
  160 170 180 190 200
  210 220 230 240 250
  260 270 280 290 300))

Luego crea una lista del número de palabras y símbolos en cada rango.

Evalúa lo siguiente:

(setq lista-para-el-grafico
       (definiciones-por-rango
         (sort
          (lista-de-longitudes-de-muchos-ficheros-recursiva
           (directory-files "/usr/local/emacs/lisp"
                            t ".+el$"))
          '<)
         cima-de-rangos))

En mi vieja máquina, esto tardaba una hora. Veia atraves de 303 ficheros Lisp en mi copia de Emacs version 19.23. Después de toda esa computación, lista-para-el-grafico tenía este valor:

(537 1027 955 785 594 483 349 292 224 199 166 120 116 99
 90 80 67 48 52 45 41 33 28 26 25 20 12 28 11 13 220)

Esto significa que mi copia de Emacs tiene 537 definiciones de funcion con poco menos de 10 palabras o símbolos en ellas, 1027 definiciones con 10 a 19 palabras o símbolos, 955 con 20 a 29, etc.

Claramente, con solo mirar esta lista se puede ver que la mayoría de definiciones de función contienen de diez a treinta palabras y símbolos.

Ahora a imprimirla. No queremos imprimir un grafico de 1030 líneas de alto …. En su lugar, debemos imprimir un grafico que tenga menos de venticinco líneas de alto. Un grafico cuya altura puede ser mostrada en casi cualquier monitor, e imprimirse fácilmente en una hoja de papel.

Esto significa que cada valor en lista-para-el-grafico debe reducirse a un quincuagesimo de su valor actual.

Aquí hay una breve función para hacer exactamente eso, usando dos funciones que no hemos visto todavía, mapcar y lambda.

(defun un-cincuentavo (rango-completo)
  "Devuelve una lista con la quincuagesima parte de cada numero."
 (mapcar '(lambda (arg) (/ arg 50)) rango-completo))

Una expresión lambda: Anonimato útil

lambda es el símbolo para una función anónima, una función sin nombre. Cada vez que se use una función anónima, es necesario incluir todo su cuerpo.

De este modo,

(lambda (arg) (/ arg 50))

es una definición de función que dice ‘devuelve el valor resultante de dividir lo que se pasa como arg por 50’.

Anteriormente, por ejemplo, teniamos una función multiplicar-por-siete; multiplica su argumento por 7. Esta función es similar, excepto que divide su argumento por 50; y, no tiene nombre. El equivalente anónimo de multiplicar-por-siete es:

(lambda (numero) (* 7 numero))

(Consulta la Seccion La forma especial defun.)

Si queremos multiplicar 3 por 7, podemos escribir:

(multiplicar-por-siete 3)
 \___________________/ ^
           |           |
        función    argumento

Esta expresión devuelve 21.

De manera similar, podemos escribir:

((lambda (numero) (* 7 numero)) 3)
 \____________________________/ ^
               |                |
       función anónima     argumento

Si queremos dividir 100 por 50, podemos escribir:

((lambda (arg) (/ arg 50)) 100)
 \______________________/  \_/
             |              |
     función anónima    argumento

Esta expresión devuelve 2. El 100 se pasa a la función, que divide este número por 50.

Cosulta la Seccion Expresiones Lambda en El Manual de Referencia de GNU Emacs Lisp, para más informacion acerca de lambda. Lisp y las expresiones Lambda se derivan del Cálculo Lambda.

La función mapcar

mapcar es una función que llama a su primer argumento con cada elemento de su segundo argumento. El segundo argumento debe ser una secuencia.

La parte ‘map’ del nombre viene de la frase matemática, ‘mapeo sobre un dominio’, lo que significa aplicar una función a cada uno de los elementos en un dominio. La frase matemática se basa en la metáfora de un topografo que camina, un paso a la vez, sobre un área que está mapeando. Y ‘car’, por supuesto, proviene de la noción Lisp del primer elemento de una lista.

Por ejemplo,

> (mapcar '1+ '(2 4 6))
(3 5 7)

La función 1+ añade uno a su argumento, se ejecuta en cada elemento de la lista, y se devuelve una nueva lista.

En contraste con apply, que aplica su primer argumento a todos los demas. (Consulta la Seccion Preparar un grafico, para una explicación de apply.)

En la definición de un-cincuentavo, el primer argumento es la función anónima:

(lambda (arg) (/ arg 50))

y el segundo argumento es rango-completo, que será vinculado a lista-para-el-grafico.

La expresión completa se ve asi:

(mapcar (lambda (arg) (/ arg 50)) rango-completo))

Consulta la Seccion Mapeando Funciones en El Manual de Referencia de GNU Emacs Lisp, para más informacion sobre mapcar.

Usando la función un-cincuentavo, podemos generar una lista en la que cada elemento es un cincuentavo del tamaño del elemento correspondiente en la lista-para-el-grafico.

(setq cincuentavo-de-lista-para-el-grafico
      (un-cincuentavo lista-para-el-grafico))

La lista resultante luce asi:

(10 20 19 15 11 9 6 5 4 3 3 2 2
 1 1 1 1 0 1 0 0 0 0 0 0 0 0 0 0 0 4)

¡Ya estamos casi listos para imprimir! (También notamos la pérdida de información: muchos de los rangos mas altos son 0, lo que significa que menos de 50 funciones tenían tantas palabras o símbolos––pero no necesariamente significa que niguna tenía tantas palabras o símbolos.)

Otro error … más insidioso

¡Dije ‘casi listos para imprimir’! De acuerdo, hay un error en la función imprimir-grafico …. Tiene una opción paso-vertical, pero no una opción paso-horizontal. La escala de cima-de-rango va desde 10 a 300 por decenas. Pero la función imprimir-grafico se imprimirá solo por unos.

Este es un ejemplo clásico de lo que algunos consideramos el tipo de error más insidioso, el error de omisión. Este no es el tipo de error que se puede encontrar estudiando el código, ya que no esta en el código; es una caracteristica omitida. Tus mejores acciones son probar tu programa temprano y con frecuencia; e intentar arreglar, tanto como se pueda, escribir código que sea fácil de comprender y fácil de cambiar. Intenta ser consciente, siempre que puedas, de que todo lo que has escrito, será reescrito, si no pronto, eventualmente. Una dura máxima a seguir.

Es la función imprimir-linea-numerada-del-eje-X la que necesita trabajo; y luego hay que adaptar las funciones imprimir-eje-X e imprimir-grafico. No hay mucho que hacer; hay una cosa buena: los números deben alinearse con marcas de etiquetado. Esto requiere un poco de reflexion.

Aquí está imprimir-linea-numerada-del-eje-X corregido:

(defun imprimir-linea-numerada-del-eje-X
  (numero-de-marcas-X espacios-de-relleno-del-eje-X
   &optional paso-horizontal)
  "Imprime linea de números del eje X"
  (let ((numero distancia-entre-etiquetas-del-eje-X)
        (paso-horizontal (or paso-horizontal 1)))
    (insert espacios-de-relleno-del-eje-X)
    ;; Elimina espacios de relleno sobrantes.
    (delete-char
     (- (1-
         (length (number-to-string paso-horizontal)))))
    (insert (concat
             (make-string
              ;; Inserta espacio en blanco.
              (-  (* ancho-del-simbolo
                     distancia-entre-etiquetas-del-eje-X)
                  (1-
                   (length
                    (number-to-string paso-horizontal)))
                  2)
              ? )
             (number-to-string
              (* numero paso-horizontal))))
    ;; Inserta los números restantes.
    (setq numero (+ numero distancia-entre-etiquetas-del-eje-X))
    (while (> numero-de-marcas-X 1)
      (insert (elemento-del-eje-X
               (* numero paso-horizontal)))
      (setq numero (+ numero distancia-entre-etiquetas-del-eje-X))
      (setq numero-de-marcas-X (1- numero-de-marcas-X)))))

A continuacion, las líneas que cambian en imprimir-eje-X e imprimir-grafico.

(defun imprimir-eje-X (lista-de-numeros paso-horizontal)
  
    (imprimir-linea-numerada-del-eje-X
     numero-de-marcas espacios-de-relleno paso-horizontal))

(defun imprimir-grafico
  (lista-de-numeros
   &optional paso-vertical paso-horizontal)
  
    (imprimir-eje-X lista-de-numeros paso-horizontal))

El gráfico impreso

Cuando está hecho e instalado, puedes llamar al comando imprimir-grafico asi:

(imprimir-grafico cincuentavo-de-lista-para-el-grafico 50 10)

Aquí está el gráfico:

1000 -  *
        **
        **
        **
        **
 750 -  ***
        ***
        ***
        ***
        ****
 500 - *****
       ******
       ******
       ******
       *******
 250 - ********
       *********                     *
       ***********                   *
       *************                 *
  50 - ***************** *           *
       |   |    |    |    |    |    |    |
      10  50  100  150  200  250  300  350

El grupo mas grande de funciones contienen de 10 a 19 palabras y símbolos.

Apéndice D: Software Libre y Manuales Libres (por Richard M. Stallman)

La mayor deficiencia de los sistemas operativos libres no está en el software––sino en la falta de buenos manuales libres que podamos incluir en estos sistemas. Muchos de nuestros programas más importantes no vienen con manuales completos. La documentación es una parte esencial de cualquier paquete de software; cuando un paquete de software libre no viene con un manual libre, eso es un gran vacio. Hoy en dia tenemos muchos vacios de este tipo.

Érase una vez, hace muchos años, que pense que aprendería Perl. Consegui una copia de un manual libre, pero me resulto difícil de leer. Cuando pregunte a los usuarios de Perl sobre alternativas, me dijeron que habia mejores manuales introductorios––pero estos no eran libres.

¿Por qué fue asi? Los autores de los buenos manuales los habían escrito para O'Reilly Associates, que los publicaron con términos restrictivos––sin copia, sin modificacion, sin ficheros fuente disponibles––que los excluyen de la comunidad del software libre.

No era la primera vez que ocurrían estas cosas, y para nuestra comunidad es una gran pérdida que estaba lejos de ser la ultima. Las editoriales de manuales privativos han logrado que muchos autores restrinjan sus manuales desde entonces. Muchas veces he oido a un habil usuario de GNU hablarme con entusiasmo de un manual que está escribiendo, con el que espera ayudar al proyecto GNU––y luego mis esperanzas se vieron frustradas, mientras procedia a explicarme que habia firmado un contrato con una editorial que lo restringiría para que no pudieramos usarlo.

Debido a que escribir buen inglés es una habilidad poco comun entre los programadores, no podemos darnos el lujo de perder manuales de esta manera.

La documentación, como el software, es una cuestión de libertad, no de precio. El problema con estos manuales no eran que O'Reilly Associates impusiera un precio por las copias impresas––eso en si mismo esta bien. La Free Software Foundation tambien vende copias impresas de los manuales libres de GNU. Pero los manuales de GNU están disponibles en forma de código fuente, mientras que estos manuales están disponibles solo en papel. Los manuales de GNU vienen con permiso para copiar y modificar; los manuales de Perl no. Estas restricciones son un problema.

El criterio para un manual libre es practicamente el mismo que para el software libre: es una cuestión de dar a todos los usuarios ciertas libertades. Se debe permitir la redistribución (incluida la redistribución comercial), de modo que el manual puede acompañar cada copia del programa, en linea o en papel. El permiso para la modificacion tambien es crucial.

Como regla general, no creo que sea esencial que la gente tenga permiso para modificar todo tipo de artículos y libros. Las cuestiones relativa a los escritos no son necesariamente las mismas que las del software. Por ejemplo, no creo que usted o yo estemos obligados a dar permisos para modificar artículos como este, que describen nuestras acciones y nuestros puntos de vista.

Pero hay una razón particular por la qué la libertad de modificar es crucial para la documentación del software libre. Cuando las personas ejercen su derecho a modificar el software, y añadir o cambiar sus funcionalidades, si son concienzudos, tambien cambiarán el manual––de modo que puedan proporcionar documentación precisa y usable con el programa modificado. Un manual que prohibe a los programadores ser concienzudos y terminar el trabajo, o más precisamente requiere que escriban un nuevo manual desde cero si cambian el programa, no se ajusta a las necesidades de nuestra comunidad.

Si bien una prohibicion general de la modificación es inaceptable, algunos tipos de límites sobre el método de modificacion no plantea ningun problema. Por ejemplo, los requisitos para preservar el aviso de copyright del autor original, los términos de distribución, o la lista de autores, estén bien. Tampoco es un problemas requerir que las versiones modificadas incluyan un aviso de que fueron modificadas, incluso para tener secciones enteras que no pueden ser eliminadas o cambiadas, siempre y cuando estas secciones traten asuntos no técnicos. (Algunos manuales de GNU los tienen.)

Este tipo de restricciones no son un problema porque, en la práctica, no impiden que el programador concienzudo adapte el manual al programa modificado. En otras palabras, no impiden que la comunidad del software libre haga pleno uso del manual.

Sin embargo, debe ser posible modificar todo el contenido técnico del manual, y luego distribuir el resultado en todos los medios habituales, a través de todos los canales; de otro modo, las restricciones bloquean la comunidad, el manual no es libre, por lo que necesitamos otro manual.

Desafortunadamente, con frecuencia es dificil encontrar a alguien que escriba otro manual cuando existe el privativo. El obstáculo es que muchos usuarios piensan que un manual privativo es lo suficientemente bueno––por lo que no ven la necesidad de escribir un manual libre. Ellos no ven que el sistema operativo tiene un hueco que necesita se llenado.

¿Por qué los usuarios piensan que los manuales privativos son lo suficientemente buenos? Algunos no han considerado el tema. Espero que este artículo haga algo para cambiar eso.

Otros usuarios considera que los manuales privativos son aceptables por la misma razón que muchas personas consideran que el software privativo es aceptable: juzgan en términos puramente prácticos, sin usar la libertad como criterio. Estas personas tiene derecho a sus opiniones, pero como esas opiniones provienen de valores que no incluyen la libertad, no son una guia para aquellos de nosotros que valoramos la libertad.

Por favor, haga correr la voz sobre este tema. Seguimos perdiendo manuales debido a la publicacion privativa. Si hacemos correr la voz de que los manuales privativos no son suficientes, quizás la siguiente persona que quiera ayudar a GNU escribiendo documentación se dara cuenta, antes de que sea demasiado tarde, que sobre todo debe hacerlo libre.

También podemos animar a las editoriales comerciales a vender manuales libres o con copyleft en lugar de manuales privativos. Una forma de ayudar es comprobar los términos de distribución de un manual antes de comprarlo, y preferir manuales con copyleft a los no copyleft.

Nota: La Free Software Foundation mantiene una página en su sitio Web que lista libros libres disponibles en otras editoriales: http://www.gnu.org/doc/other-free-books.html

Appendix E: GNU Free Documentation License

Version 1.3, 3 November 2008

Copyright (C) 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc. <http://fsf.org/>

Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.

0. PREAMBLE

The purpose of this License is to make a manual, textbook, or other functional and useful document "free" in the sense of freedom: to assure everyone the effective freedom to copy and redistribute it, with or without modifying it, either commercially or noncommercially. Secondarily, this License preserves for the author and publisher a way to get credit for their work, while not being considered responsible for modifications made by others.

This License is a kind of "copyleft", which means that derivative works of the document must themselves be free in the same sense. It complements the GNU General Public License, which is a copyleft license designed for free software.

We have designed this License in order to use it for manuals for free software, because free software needs free documentation: a free program should come with manuals providing the same freedoms that the software does. But this License is not limited to software manuals; it can be used for any textual work, regardless of subject matter or whether it is published as a printed book. We recommend this License principally for works whose purpose is instruction or reference.

1. APPLICABILITY AND DEFINITIONS

This License applies to any manual or other work, in any medium, that contains a notice placed by the copyright holder saying it can be distributed under the terms of this License. Such a notice grants a world-wide, royalty-free license, unlimited in duration, to use that work under the conditions stated herein. The "Document", below, refers to any such manual or work. Any member of the public is a licensee, and is addressed as "you". You accept the license if you copy, modify or distribute the work in a way requiring permission under copyright law.

A "Modified Version" of the Document means any work containing the Document or a portion of it, either copied verbatim, or with modifications and/or translated into another language.

A "Secondary Section" is a named appendix or a front-matter section of the Document that deals exclusively with the relationship of the publishers or authors of the Document to the Document's overall subject (or to related matters) and contains nothing that could fall directly within that overall subject. (Thus, if the Document is in part a textbook of mathematics, a Secondary Section may not explain any mathematics.) The relationship could be a matter of historical connection with the subject or with related matters, or of legal, commercial, philosophical, ethical or political position regarding them.

The "Invariant Sections" are certain Secondary Sections whose titles are designated, as being those of Invariant Sections, in the notice that says that the Document is released under this License. If a section does not fit the above definition of Secondary then it is not allowed to be designated as Invariant. The Document may contain zero Invariant Sections. If the Document does not identify any Invariant Sections then there are none.

The "Cover Texts" are certain short passages of text that are listed, as Front-Cover Texts or Back-Cover Texts, in the notice that says that the Document is released under this License. A Front-Cover Text may be at most 5 words, and a Back-Cover Text may be at most 25 words.

A "Transparent" copy of the Document means a machine-readable copy, represented in a format whose specification is available to the general public, that is suitable for revising the document straightforwardly with generic text editors or (for images composed of pixels) generic paint programs or (for drawings) some widely available drawing editor, and that is suitable for input to text formatters or for automatic translation to a variety of formats suitable for input to text formatters. A copy made in an otherwise Transparent file format whose markup, or absence of markup, has been arranged to thwart or discourage subsequent modification by readers is not Transparent. An image format is not Transparent if used for any substantial amount of text. A copy that is not "Transparent" is called "Opaque".

Examples of suitable formats for Transparent copies include plain ASCII without markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly available DTD, and standard-conforming simple HTML, PostScript or PDF designed for human modification. Examples of transparent image formats include PNG, XCF and JPG. Opaque formats include proprietary formats that can be read and edited only by proprietary word processors, SGML or XML for which the DTD and/or processing tools are not generally available, and the machine-generated HTML, PostScript or PDF produced by some word processors for output purposes only.

The "Title Page" means, for a printed book, the title page itself, plus such following pages as are needed to hold, legibly, the material this License requires to appear in the title page. For works in formats which do not have any title page as such, "Title Page" means the text near the most prominent appearance of the work's title, preceding the beginning of the body of the text.

The "publisher" means any person or entity that distributes copies of the Document to the public.

A section "Entitled XYZ" means a named subunit of the Document whose title either is precisely XYZ or contains XYZ in parentheses following text that translates XYZ in another language. (Here XYZ stands for a specific section name mentioned below, such as "Acknowledgements", "Dedications", "Endorsements", or "History".) To "Preserve the Title" of such a section when you modify the Document means that it remains a section "Entitled XYZ" according to this definition.

The Document may include Warranty Disclaimers next to the notice which states that this License applies to the Document. These Warranty Disclaimers are considered to be included by reference in this License, but only as regards disclaiming warranties: any other implication that these Warranty Disclaimers may have is void and has no effect on the meaning of this License.

2. VERBATIM COPYING

You may copy and distribute the Document in any medium, either commercially or noncommercially, provided that this License, the copyright notices, and the license notice saying this License applies to the Document are reproduced in all copies, and that you add no other conditions whatsoever to those of this License. You may not use technical measures to obstruct or control the reading or further copying of the copies you make or distribute. However, you may accept compensation in exchange for copies. If you distribute a large enough number of copies you must also follow the conditions in section 3.

You may also lend copies, under the same conditions stated above, and you may publicly display copies.

3. COPYING IN QUANTITY

If you publish printed copies (or copies in media that commonly have printed covers) of the Document, numbering more than 100, and the Document's license notice requires Cover Texts, you must enclose the copies in covers that carry, clearly and legibly, all these Cover Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on the back cover. Both covers must also clearly and legibly identify you as the publisher of these copies. The front cover must present the full title with all words of the title equally prominent and visible. You may add other material on the covers in addition. Copying with changes limited to the covers, as long as they preserve the title of the Document and satisfy these conditions, can be treated as verbatim copying in other respects.

If the required texts for either cover are too voluminous to fit legibly, you should put the first ones listed (as many as fit reasonably) on the actual cover, and continue the rest onto adjacent pages.

If you publish or distribute Opaque copies of the Document numbering more than 100, you must either include a machine-readable Transparent copy along with each Opaque copy, or state in or with each Opaque copy a computer-network location from which the general network-using public has access to download using public-standard network protocols a complete Transparent copy of the Document, free of added material. If you use the latter option, you must take reasonably prudent steps, when you begin distribution of Opaque copies in quantity, to ensure that this Transparent copy will remain thus accessible at the stated location until at least one year after the last time you distribute an Opaque copy (directly or through your agents or retailers) of that edition to the public.

It is requested, but not required, that you contact the authors of the Document well before redistributing any large number of copies, to give them a chance to provide you with an updated version of the Document.

4. MODIFICATIONS

You may copy and distribute a Modified Version of the Document under the conditions of sections 2 and 3 above, provided that you release the Modified Version under precisely this License, with the Modified Version filling the role of the Document, thus licensing distribution and modification of the Modified Version to whoever possesses a copy of it. In addition, you must do these things in the Modified Version:

  1. Use in the Title Page (and on the covers, if any) a title distinct from that of the Document, and from those of previous versions (which should, if there were any, be listed in the History section of the Document). You may use the same title as a previous version if the original publisher of that version gives permission.

  2. List on the Title Page, as authors, one or more persons or entities responsible for authorship of the modifications in the Modified Version, together with at least five of the principal authors of the Document (all of its principal authors, if it has fewer than five), unless they release you from this requirement.

  3. State on the Title page the name of the publisher of the Modified Version, as the publisher.

  4. Preserve all the copyright notices of the Document.

  5. Add an appropriate copyright notice for your modifications adjacent to the other copyright notices.

  6. Include, immediately after the copyright notices, a license notice giving the public permission to use the Modified Version under the terms of this License, in the form shown in the Addendum below.

  7. Preserve in that license notice the full lists of Invariant Sections and required Cover Texts given in the Document's license notice.

  8. Include an unaltered copy of this License.

  9. Preserve the section Entitled "History", Preserve its Title, and add to it an item stating at least the title, year, new authors, and publisher of the Modified Version as given on the Title Page. If there is no section Entitled "History" in the Document, create one stating the title, year, authors, and publisher of the Document as given on its Title Page, then add an item describing the Modified Version as stated in the previous sentence.

  10. Preserve the network location, if any, given in the Document for public access to a Transparent copy of the Document, and likewise the network locations given in the Document for previous versions it was based on. These may be placed in the "History" section. You may omit a network location for a work that was published at least four years before the Document itself, or if the original publisher of the version it refers to gives permission.

  11. For any section Entitled "Acknowledgements" or "Dedications", Preserve the Title of the section, and preserve in the section all the substance and tone of each of the contributor acknowledgements and/or dedications given therein.

  12. Preserve all the Invariant Sections of the Document, unaltered in their text and in their titles. Section numbers or the equivalent are not considered part of the section titles.

  13. Delete any section Entitled "Endorsements". Such a section may not be included in the Modified Version.

  14. Do not retitle any existing section to be Entitled "Endorsements" or to conflict in title with any Invariant Section.

  15. Preserve any Warranty Disclaimers.

If the Modified Version includes new front-matter sections or appendices that qualify as Secondary Sections and contain no material copied from the Document, you may at your option designate some or all of these sections as invariant. To do this, add their titles to the list of Invariant Sections in the Modified Version's license notice. These titles must be distinct from any other section titles.

You may add a section Entitled "Endorsements", provided it contains nothing but endorsements of your Modified Version by various parties--for example, statements of peer review or that the text has been approved by an organization as the authoritative definition of a standard.

You may add a passage of up to five words as a Front-Cover Text, and a passage of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover Text may be added by (or through arrangements made by) any one entity. If the Document already includes a cover text for the same cover, previously added by you or by arrangement made by the same entity you are acting on behalf of, you may not add another; but you may replace the old one, on explicit permission from the previous publisher that added the old one.

The author(s) and publisher(s) of the Document do not by this License give permission to use their names for publicity for or to assert or imply endorsement of any Modified Version.

5. COMBINING DOCUMENTS

You may combine the Document with other documents released under this License, under the terms defined in section 4 above for modified versions, provided that you include in the combination all of the Invariant Sections of all of the original documents, unmodified, and list them all as Invariant Sections of your combined work in its license notice, and that you preserve all their Warranty Disclaimers.

The combined work need only contain one copy of this License, and multiple identical Invariant Sections may be replaced with a single copy. If there are multiple Invariant Sections with the same name but different contents, make the title of each such section unique by adding at the end of it, in parentheses, the name of the original author or publisher of that section if known, or else a unique number. Make the same adjustment to the section titles in the list of Invariant Sections in the license notice of the combined work.

In the combination, you must combine any sections Entitled "History" in the various original documents, forming one section Entitled "History"; likewise combine any sections Entitled "Acknowledgements", and any sections Entitled "Dedications". You must delete all sections Entitled "Endorsements".

6. COLLECTIONS OF DOCUMENTS

You may make a collection consisting of the Document and other documents released under this License, and replace the individual copies of this License in the various documents with a single copy that is included in the collection, provided that you follow the rules of this License for verbatim copying of each of the documents in all other respects.

You may extract a single document from such a collection, and distribute it individually under this License, provided you insert a copy of this License into the extracted document, and follow this License in all other respects regarding verbatim copying of that document.

7. AGGREGATION WITH INDEPENDENT WORKS

A compilation of the Document or its derivatives with other separate and independent documents or works, in or on a volume of a storage or distribution medium, is called an "aggregate" if the copyright resulting from the compilation is not used to limit the legal rights of the compilation's users beyond what the individual works permit. When the Document is included in an aggregate, this License does not apply to the other works in the aggregate which are not themselves derivative works of the Document.

If the Cover Text requirement of section 3 is applicable to these copies of the Document, then if the Document is less than one half of the entire aggregate, the Document's Cover Texts may be placed on covers that bracket the Document within the aggregate, or the electronic equivalent of covers if the Document is in electronic form. Otherwise they must appear on printed covers that bracket the whole aggregate.

8. TRANSLATION

Translation is considered a kind of modification, so you may distribute translations of the Document under the terms of section 4. Replacing Invariant Sections with translations requires special permission from their copyright holders, but you may include translations of some or all Invariant Sections in addition to the original versions of these Invariant Sections. You may include a translation of this License, and all the license notices in the Document, and any Warranty Disclaimers, provided that you also include the original English version of this License and the original versions of those notices and disclaimers. In case of a disagreement between the translation and the original version of this License or a notice or disclaimer, the original version will prevail.

If a section in the Document is Entitled "Acknowledgements", "Dedications", or "History", the requirement (section 4) to Preserve its Title (section 1) will typically require changing the actual title.

9. TERMINATION

You may not copy, modify, sublicense, or distribute the Document except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, or distribute it is void, and will automatically terminate your rights under this License.

However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation.

Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice.

Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, receipt of a copy of some or all of the same material does not give you any rights to use it.

10. FUTURE REVISIONS OF THIS LICENSE

The Free Software Foundation may publish new, revised versions of the GNU Free Documentation License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. See http://www.gnu.org/copyleft/.

Each version of the License is given a distinguishing version number. If the Document specifies that a particular numbered version of this License "or any later version" applies to it, you have the option of following the terms and conditions either of that specified version or of any later version that has been published (not as a draft) by the Free Software Foundation. If the Document does not specify a version number of this License, you may choose any version ever published (not as a draft) by the Free Software Foundation. If the Document specifies that a proxy can decide which future versions of this License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Document.

11. RELICENSING

"Massive Multiauthor Collaboration Site" (or "MMC Site") means any World Wide Web server that publishes copyrightable works and also provides prominent facilities for anybody to edit those works. A public wiki that anybody can edit is an example of such a server. A "Massive Multiauthor Collaboration" (or "MMC") contained in the site means any set of copyrightable works thus published on the MMC site.

"CC-BY-SA" means the Creative Commons Attribution-Share Alike 3.0 license published by Creative Commons Corporation, a not-for-profit corporation with a principal place of business in San Francisco, California, as well as future copyleft versions of that license published by that same organization.

"Incorporate" means to publish or republish a Document, in whole or in part, as part of another Document.

An MMC is "eligible for relicensing" if it is licensed under this License, and if all works that were first published under this License somewhere other than this MMC, and subsequently incorporated in whole or in part into the MMC, (1) had no cover texts or invariant sections, and (2) were thus incorporated prior to November 1, 2008.

The operator of an MMC Site may republish an MMC contained in the site under CC-BY-SA on the same site at any time before August 1, 2009, provided the MMC is eligible for relicensing.

ADDENDUM: How to use this License for your documents

To use this License in a document you have written, include a copy of the License in the document and put the following copyright and license notices just after the title page:

Copyright (c)  YEAR  YOUR NAME.
Permission is granted to copy, distribute and/or modify this document
under the terms of the GNU Free Documentation License, Version 1.3
or any later version published by the Free Software Foundation;
with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
A copy of the license is included in the section entitled "GNU
Free Documentation License".

If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, replace the "with...Texts." line with this:

with the Invariant Sections being LIST THEIR TITLES, with the
Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST.

If you have Invariant Sections without Cover Texts, or some other combination of the three, merge those two alternatives to suit the situation.

If your document contains nontrivial examples of program code, we recommend releasing these examples in parallel under your choice of free software license, such as the GNU General Public License, to permit their use in free software.

Acerca del Autor

Robert J. Chassell ha trabajado con GNU Emacs desde 1985. Él escribe, edita y enseña Emacs y Emacs Lisp, y habla en todo el mundo sobre la libertad del software. Chassell fue Director fundador y Tesorero de la Free Software Foundation, Inc. Es coautor del manual Texinfo y ha editado mas de una docena de libros. Se graduó en la Universidad de Cambridge, en Inglaterra. Tiene un interés permanente en historia económica y social y vuela su propio aeroplano

footnotes

1

El apóstrofo o comilla es una abreviación para la función quote; no es necesario pensar en las funciones ahora; las funciones se definen en la Seccion Generar un mensaje de error.

2

Es curioso seguir el camino por el que la palabra ‘argumento’ llego a tener dos significados distintos, uno en matemáticas y otro en el inglés cotidiano. De acuerdo al Oxford English Dictionary, la palabra deriva del Latín para ‘dejar claro, probar’; asi llego a significar, por un hilo de derivación, ‘la evidencia ofrecida como prueba’, es decir ‘la informacion que se ofrece’, lo que conduce a su significado en Lisp. Pero en el otro hilo de la derivación, paso a significar ‘afirmar de una manera contra la cual otros pueden hacer afirmaciones contrarias’, lo que llevó al significado de la palabra como disputa. (Notese aqui que la palabra Inglésa tiene dos definiciones distintas al mismo tiempo. En contraste, en Emacs Lisp, un símbolo no puede tener dos definiciones de funcion diferentes al mismo tiempo.)

3

(quote hola) es una expansión de la abreviatura 'hola.

4

En realidad, se puede utilizar %s para imprimir un número. No es específico. %d solo imprime la parte de un número a la izquierda del punto decimal, excluyendo cualquier cosa que no sea un número.

5

De hecho, por defecto, si el búfer desde el que acabas de cambiar es visible por tí en otra ventana, other-buffer elegirá el búfer más reciente que no puedas ver; esta es una sutileza que a menudo olvido.

6

O mejor dicho, para evitar escribir, probablemente solo necesites pulsar RET si *scratch* es el buffer por defecto, de ser diferente, solo escribe parte del nombre, por ejemplo *sc, luego presiona la tecla TAB para hacer que se expanda el nombre completo, y finalmente pulsa RET.

7

Recuerda, esta expresión te desplaza al buffer diferente más reciente que no puedas ver. Si realmente quieres ir al ultimo búfer seleccionado, incluso si es visible, es necesario evaluar la siguiente expresión más compleja:

(switch-to-buffer (other-buffer (current-buffer) t))

En este caso, el primer argumento de other-buffer le dice a que búfer saltar––el actual––y el segundo argumento other-buffer le indica que esta BIEN cambiar a un búfer visible. La utilidad de switch-to-buffer es llevarte a una ventana invisible ya que probablemente usarias C-x o (other-window) para ir a otro búfer visible.

8

Segun Jared Diamond en Guns, Germs, and Steel, “… las cebras se vuelven increiblemente peligrosas a medida que envejecen” pero la afirmacion aquí es que no se vuelven feroces como un tigre. (1997, W. W. Norton and Co., ISBN 0-393-03894-2, pagina 171)

9

Actualmente, se puede aplicar cons a un elemento y un átomo para producir a par punteado. Los pares punteados no se discuten aquí; Consulta la Seccion Dotted Pair Notation en El Manual de Referencia de GNU Emacs Lisp.

10

Más precisamente, y requiriendo un conocimiento más experto para entenderlo, los dos enteros son del tipo ‘Lisp_Object’, que también puede ser una unión C en lugar de un tipo entero.

11

Puedes escribir funciones recursivas para que sean frugales o derrochen recursos mentales o computacionales; como sucede, los métodos que la gente encuentra fáciles––que son frugales de ‘recursos mentales’––algunas veces usan considerables recursos del computador. Emacs fué diseñado para ejecutarse en máquinas que ahora consideramos limitadas y su configuracion por defecto es conservadora. Es posible que desees incrementar los valores de max-specdl-size y max-lisp-eval-depth. En mi fichero .emacs, yo los asigno a 15 o 30 veces su valor predeterminado.

12

La frase recursion de cola se utiliza para describir tal proceso, uno que usa ‘espacio constante’.

13

La jerga es ligeramente confusa: triangulo-recursivo-auxiliar usa un proceso que es iterativo en un procedimiento que es recursivo. El proceso se llama iterativo porque el ordenador solo necesita registrar los tres valores, suma, contador, y número: el procedimiento es recursivo porque la función ‘se llama a sí misma’. Por otra parte, tanto el proceso como el procedimiento usado por triangulo-recursivo se denominan recursivos. La palabra ‘recursivo’ tiene significados diferentes en los dos contextos.

14

Se puede añadir .el a ~/.emacs y llamarlo ~/.emacs.el. En el pasado, se prohibia escribir los atajos de teclado extra que el nombre ~/.emacs.el requiere, pero ahora se puede hacer. El nuevo formato es consistente con las conveniciones de nombres de ficheros Emacs Lisp; el viejo formato mantiene la escritura.

15

Cuando inicio instancias de Emacs que no cargan mi fichero .emacs o cualquier fichero, también desactivo el parpadeo:

> emacs -q --no-site-file -eval '(blink-cursor-mode nil)'

O tambien usando un conjunto más sofisticado de opciones,

> emacs -Q -D
16

También ejecuto gestores de ventanas más modernos, como Enlightenment, Gnome, o KDE; en esos casos, a menudo especifico una imagen en lugar de un color plano.