Sunday, July 25, 2010

Entendiendo Lisp

La primera vez que vi código en Lisp me pareció algo extraño.
Vi a alguien escribiendo macros para AutoCad...
Lo primero que pensé es que faltaban algunas comas,
después vino el resto de la sintaxis:
Llamar:
 (funcion parametro1 parametro2)
En vez de:
 funcion(parametro1, parametro2)

No pareció mucha complicación...
pero hacer lo mismo con los operadores era demasiado.

¿Qué se gana con escribir "(+ (* 2 x) 1)"
en vez de "2 * x + 1" ?

Lo primero es que es mas sencillo hacer un intérprete.
No hay que preocuparse por precedencias de operadores...
operadores y funciones son lo mismo.

La sintaxis es muy sencilla:
Existen paréntesis, espacio y otros caracteres.
- Los paréntesis marcan el inicio y fin de una lista.
- Los espacios separan elementos dentro de la lista.
(puede haber espacios dentro de cadenas de caracteres entre comillas)
- Secuencias de otros caracteres identifican un valor simple o átomo.

Siendo así:
+, -, >=, algo+-*?,
1, 3/4, 2.5, 1/3+4.5i,
"algún texto", "otro texto"
representan cada uno un átomo.
Son símbolos números y cadenas de caracteres.

Para evaluar una expresión:
- Los símbolos se usan como identificadores de valores
- Los demás átomos se mantienen sin cambio
- Para las listas se evalúa el primer elemento y en base a él se decide que hacer con el resto.

Por ejemplo, en Scheme, al evaluar:
(define x 3)
El símbolo "define" es una sintaxis que evalúa el tercer elemento de la lista y lo guarda en una variable identificada usa el segundo.
Otros tipos de Lisp usan nombres diferentes para definir variables.

Para evaluar "(+ (* 2 x) 1)":
1- El primer elemento de la lista ("+") es una función, así que se evalúan los siguientes y luego se ejecuta la función.
2- Al evaluar el segundo elemento "(* 2 x)" éste también es una lista, así que se ejecuta la
misma operación con él:
2.1- El primer elemento de la lista ("*") es una función, así que se evalúan los siguientes y luego se ejecuta la función.
2.2- 2 es un número, se mantiene igual.
2.3- x es un identificador, se busca la variable mas próxima con ese nombre en este caso le asignamos 3 con el "(define x 3)"
2.4 Se evalúa la función "*" con los valores obtenidos:
"(* 2 3)" cuyo resultado es 6.
3- Al evaluar el tercer elemento, es directamente un número.
4- Se evalúa la función "+" con los valores obtenidos:
"(+ 6 1)" cuyo resultado es 7.

Existen valores booleanos:
En Common Lisp, la lista vacía se considera falso y cualquier otro valor verdadero.
En Scheme existen 2 valores #t y #f para verdadero y falso, aunque cualquier valor diferente a #f se considera verdadero.
La sintaxis "if" usa un valor booleano para decidir si evalúa los siguientes valores.
Por ejemplo, en:
 (if (>= x 0)
(display "positivo")
(display "negativo"))

Para x=3 (>= x 0) es cierto. En ese caso se evalúa el siguiente parámetro:
(display "positivo"), que muestra el texto "positivo" en la pantalla.
Si "if" fuera una función, tanto el (display "positivo") como el (display "negativo") se ejecutarían antes de llamarla,
por lo que no serviría para mostrar el valor correcto. Por eso es algo diferente.

En algunos tipos de Lisp hay identificadores que al evaluarse retornan el mismo identificador,
en Common Lisp los identificadores precedidos de ":" se comportan así, y se los llama keywords (palabras claves).

La declaración de variables locales se hace con la sintaxis "let":
(let ((variable valor)
(variable2 valor2)...)
instrucciones...)
(hay varias formas de let)
Las variables son visibles en las instrucciones que aparecen dentro del let.

En Lisp se definen las funciones con la sintaxis lambda en donde se quieran usar:
(lambda (x)
(* x x))

Normalmente es conveniente asignarle un nombre usando define o let:
(define cuadrado
(lambda (x)
(* x x)))


El Lisp es muy útil para representar información que se pueda manejar como un árbol.
Actualmente es muy popular el xml para hacer eso, pero se puede hacer una equivalencia entre ambos:
<xml>
<node1 attr1="Valor1" attr2="Valor2" attr3="Valor3">
<node2 attr21="Valor1" attr22="Valor2"/>
</node1>
</xml>



se podría representar como:
(xml ()
(node1 ((attr1 "Valor1") (attr2 "Valor2") (attr3 "Valor3"))
(node2 ((attr21 "Valor1") (attr22 "Valor2"))))