Niveles de abstracci´on. Lisp y la pedagog´ıa F. Javier Gil Chica versi´ on de enero de 2015 Resumen Estudiar implica alcanzar sucesivos niveles de abstracci´ on. En este art´ıculo se muestra c´omo estos niveles pueden hacerse expl´ıcitos mediante el uso de un lenguaje de programaci´ on y hacemos una excursi´ on desde los niveles inferiores del aprendizaje de las matem´ aticas hasta el nivel de los estudios superiores, mostrando c´omo estas abstracciones se pueden formalizar al tiempo que obtenemos herramientas u ´tiles y adquirimos formas de pensar y enfrentar problemas. 1. Motivaci´ on El curso de los estudios supone la adquisici´on de niveles de abstracci´on cada vez m´as elevados, construidos a partir de abstracciones anteriores. Esto es v´alido al menos para las disciplinas cient´ıficas. Por otro lado, la computaci´on personal es ya corriente. Por otro, finalmente, en muchos textos se exponen procedimientos algor´ıtmicos, escritos en pseudoc´odigo, reconociendo impl´ıcitamente que ´esta es una forma conveniente y u ´til de formalizar ciertos conocimientos. Nosotros nos proponemos mostrar c´omo, mediante un lenguaje de programaci´on de sintaxis trivial, el alumno puede formalizar estas sucesivas abstracciones, lo que tiene a nuestro modo de ver varias importantes ventajas. En primer lugar, el dominio o al menos la familiaridad con un lenguaje de programaci´on resulta m´as que conveniente para las disciplinas cient´ıficas, desde el punto de vista de la mera utilidad. En segundo lugar, un nivel mediano en programaci´on es suficiente para que el estudiante se de cuenta y aproveche algunos patrones generales, como pueden ser: la descomposici´on de un problema en una serie de problemas m´as peque˜ nos, el uso de la recursividad, estrategias generales para abordar problemas (fuerza bruta, dividir para vencer, b´ usqueda aleatoria, exhaustiva, heur´ısitca,...), el reconocimiento de estructuras de datos generales para organizar la informaci´on, etc. En 1 tercer lugar, la programaci´on de una abstracci´on reci´en adquirida obliga a plantear esta abstracci´on de una forma clara, libre de ambig¨ uedades. En cuarto lugar, con el paso del tiempo, el alumno puede adquirir una biblioteca personal de valor permanente, que le ayude en sus estudios posteriores. Esto es mucho m´as interesante que instruirlo en el uso de programas comerciales, que nunca ser´an tan flexibles. No sugerimos que estos programas deban ser descartados, s´olo que, con frecuencia, existen soluciones sencillas para problemas sencillos (o no tanto) que est´an al alcance de un usuario corriente y que pueden alcanzarse con poco esfuerzo y un alto nivel de satisfacci´on intelectual. Adem´as, la habilidad de los lenguajes para combinar herramientas sencillas formando otras m´as complejas hace que la destreza que se adquiera hoy se revalorizar´a con el tiempo: a medida que adquirimos nuevas herramientas, crecen las posibilidades para combinarlas con otras que ya poseemos. Para mostrar nuestro punto de vista, vamos a guiar al lector por una serie de niveles de abstracci´on creciente, y veremos c´omo estos niveles se pueden expresar en un lenguaje de programaci´on. En definitiva, vamos a crear peque˜ nos programas, casi todos triviales, para formalizar los niveles crecientes que se alcanzan en el estudio de las matem´aticas. Elegimos el lenguaje Lisp por varias razones. En primer lugar, es el u ´nico lenguaje del que puede decirse que es bello. Su uso recompensa al intelecto. En segundo lugar, porque su sintaxis es trivial, lo que nos ahorra tener que mostrar una sintaxis particular. De hecho, al lector le bastar´a leer los ejemplos para darse cuenta de la sintaxis. En tercer lugar, porque este lenguaje va mucho m´as all´a de estos primeros niveles que nosotros vamos a desbrozar. En cuarto lugar, porque es uno de los m´as antiguos lenguajes de programaci´on (junto con Cobol y Fortran), y su estabilidad en el pasado dice mucho sobre su posible estabilidad futura, de modo que el esfuerzo que se hace hoy en aprenderlo se podr´a rentabilizar durante d´ecadas. En quinto lugar, porque es interactivo: basta escribir una funci´on y ´esta funci´on puede ejecutarse, con independencia de que pertenezca a un programa m´as complejo. Adem´as, su interactividad nos libra del molesto ciclo de edici´on-compilaci´on-ejecuci´on. En sexto lugar, porque dispone de una aritm´etica completa y potente, que puede trabajar con enteros, reales, racionales y complejos, sin estar limitado por los bits del procesador. Con seis es suficiente, pero se pueden presentar otras seis, al menos. ¿Qu´e haremos a partir de aqu´ı? Presentaremos nivel a nivel, y c´omo puede usarse Lips en cada uno de ellos. 2 2. Nivel 1 Cuando un estudiante se encuentra en este nivel, es seguramente demasiado joven para introducirle un lenguaje de programaci´on. No obstante, para hacer un recorrido completo, partimos de aqu´ı. En este nivel, el alumno, probablemente en primaria, reconoce algunos n´ umeros a primera vista. Cuando se le presentan conjuntos de objetos, reconoce inmediatamente cu´antos de estos objetos hay en el conjunto, sin necesidad de contar. Esto sucede para conjuntos de 1, 2, 3 y probablemente 4 objetos. La suma es entonces algo natural, pues consiste s´olo en asignar nombres al cardinal de los conjuntos mostrados. Por ejemplo, si a un infante se le muestran dos conjuntos, uno con 1 elemento y otro con 2 elementos, la suma de ambos cardinales es el cardinal de un conjunto de 3 elementos, reconocible inmediatamente. En Lisp, esta operaci´on con n´ umeros sencillos se escribe as´ı: > (+ 1 2) > 3 y el resultado es 3. Suponemos que tras escribir la primera l´ınea pulsamos la tecla INTRO, y como resultado se imprime la segunda. Se observa ya que en este lenguaje las operaciones se encierran entre par´entesis, y que tras el par´entesis de entrada se escribe la operaci´on que se desea realizar, y a continuaci´on sus argumentos. Pr´acticamente, esta es toda la sintaxis que hay que saber. 3. Nivel 2 En el siguiente nivel de abstracci´on, el alumno aprende a sumar n´ umeros cualesquiera. Esto requiere de un algoritmo, y el algoritmo es independiente de qu´e n´ umeros concretos se desean sumar. Esto expande las posibilidades. En Lisp: > (+ 234 907) > 1141 4. Nivel 3 A partir del nivel anterior, se alcanzan enseguida operaciones adicionales, como la resta, la multiplicaci´on (que no es m´as que una suma repetida) y la divisi´on (que no es m´as que una resta repetida). En Lisp: 3 > > > > > > > > (+ 101 202) 303 (- 34 16) 18 (* 20 20) 400 (/ 100 25) 4 En este punto, el estudiante puede usar Lisp como una calculadora potente, si bien limitada a n´ umeros enteros. Sin duda, es una calculadora mejor que una de sobremesa, pues no est´a limitada por el tama˜ no de los n´ umeros y la introducci´on de las operaciones es m´as c´omoda. ¿Por qu´e entonces no usar Lisp como calculadora? > > > > > > (+ 34535345289 8976568464) 43511913753 (- 986796796969698769875 858758765876443) 986795938210932893432 (* 987654321012345 123456789012345) 121932631126351935653102399025 5. Nivel 4 En este nivel, el alumno conoce distintas clases de n´ umeros. Los enteros no son suficientes, y se le presentan los naturales, y despu´es los racionales, los reales y, finalmente, los complejos. Nuestro lenguaje es capaz de operar con distintos tipos de n´ umeros. Por ejemplo con racionales: > (+ 12/67 34/101) > 3490/6767 Con reales: > (* 1.0098 12.713) > 12.837587 y con complejos: > (/ #C(12 -9) #C(-9 7)) > #C(-171/130 -3/130) 4 6. Nivel 5 El siguiente nivel borra las diferencias entre los distintos tipos de n´ umeros: un n´ umero es s´olo un n´ umero. Lisp permite usar tipos distintos en la misma expresi´on, y da el resultado en el tipo adecuado. Por ejemplo, la suma de dos racionales es un racional, pero el producto de un racional por un real es un real: > (* 7/11 2.0) > 1.2727273 Adem´as, se puede operar con complejos cuyas partes real e imaginaria sean de cualquier tipo: > (/ #C(5.0 45/118) #C(-9 14)) > #C(-0.14318056 -0.26509818) 7. Nivel 6 En el nivel 6, generalizamos las operaciones b´asicas haciendo que puedan tomar un n´ umero cualquiera de argumentos: > > > > (+ 1 2/90 4.45 -6) -0.5277777 (/ 1.0 2.0 4.0) 0.125 8. Nivel 7 En el nivel 6, tenemos ya los operadores elementales, que pueden tomar un n´ umero arbitario de operandos. Ahora ampliamos a´ un m´as las capacidades de c´alculo, admitiendo que un operando puede ser no s´olo un n´ umero, sino otra expresi´on, es decir, el resultado de otro u otros operandos. Adem´as, el proceso puede anidarse tanto como se desee o necesite: > > > > > > (+ 2 (* 3 3)) 18 (* 4 (+ (/ 10 5) 1)) 12 (- 10 (* 2 (+ 9 1 (- 3 5)))) -6 5 9. Nivel 8 El nivel 8 es crucial, pues es el paso desde el concepto de operando al concepto de funci´on. Sabemos que existen funciones como las exponenciales, ra´ıces, logar´ıtimas y trigonom´etricas que, en u ´ltima instancia, se reducen a una secuencia, quiz´as larga, de operaciones elementales de suma, resta, divisi´on y multiplicaci´on. Podemos ahora ver las operaciones elementales como funciones sencillas e incluirlas en la gran familia de las ”funciones”. Estas otras funciones tambi´en se pueden calcular en Lisp: > > > > > > > > > > (sqrt 25.3) 5.0299106 (log 12) 2.4849067 (sin 0.34) 0.3334871 (cos 2.71) -0.9083007 (atan 3.0) 1.2490458 10. Nivel 9 El n´ umero de funciones que pueden definirse es infinito. Lisp permite definir nuevas funciones, combinando funciones ya existentes. Existe una funci´on especial, defun, para definir nuevas funciones. Para hacerlo adem´as, necesitamos una abstracci´on adicional: la variable. En efecto, podemos escribir (sqrt 8), (sqrt 34) o de cualquier otro valor. El hecho de que la funci´on pueda aplicarse a cualquier argumento de su dominio implica la separaci´on entre el procedimiento de c´alculo y los valores a los que se aplica este procedimiento. Mediante el uso de variables podemos designar no un valor en concreto, sino cualquier valor. Como ejemplo, definimos una funci´on que calcula la media de dos n´ umeros. Para representar a dos n´ umeros cualesquiera, usamos dos variables, a y b: > > > > > (defun media (a b) (/ (+ a b) 2)) (media 4 9) 13/2 (media 3.0 7.6) 5.3 6 Inmediatamente despu´es, es posible usar esta funci´on como si fuese parte del lenguaje, con el mismo rango que las funciones elementales o trigonom´etricas: > (* 2.0 (media 3 4)) > 7.0 11. Nivel 10 A veces una funci´on puede calcularse recursivamente. Recursivas son aquellas funciones que entran dentro de su propia definici´on. Por ejemplo, el factorial de un n´ umero n se puede definir en funci´on del factorial de n − 1: n! = n × (n − 1)!. Hay muchas funciones que pueden calcularse as´ı. Para el factorial: (defun factorial (n) (if (= n 0) 1 (* n (factorial (- n 1))))) Recordemos que Lisp no est´a limitado a la representaci´on de los n´ umeros por hardware, es decir, puede trabajar con cantidades que superan en mucho las que se pueden representar con los 32 o 64 bits de los procesadores: > (factorial 543) > 87289155679026045684358636693446257021740446758498686215 79516602180334762072835529399733505370692175375494781142 29971312262991181125700413163606116971183695586311579361 17391585219324184468358556411453708009915701308257614957 35233353697384423555998167108040688653556845999420474539 68407440725166640261015140054933542126214585902983116193 90712911174766519604185503266095609588369497418699892424 77495293359310940924297256406583100421996682142026379246 31140057800119930627800378541111012271243379033822559451 08531541695567291279139523793259525765347547902120897915 43895916185954498557799257513083033148700674640758302108 93226767964127916083986194604372529850059441599156750579 67006711041599794976786096343900548525251434242518056433 00563926595522814735023531592849186104934114678005580760 01750687023376509841526414457026571388047691226042613278 04707607839582633502846804740587194154655813419655463834 04799081864291782417945589548785060786465318535054281875 07441610683354213870320835243780540993795316813622383436 7 15133564225574181769668968212741580998469316749061133683 03540213516072472617031543849136200880060209239477452800 00000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000 0000000000000000000 Entretanto, el mayor entero que se puede representar con 32 bits es 4294967296; con 64 bits: 18446744073709551616 y una hipot´etica futura generaci´on de procesadores de 128 bits podr´a representar enteros hasta 340282366920938463463374607431768211456. Para calcular estos n´ umeros, hemos escrito en Lisp la funci´on ”elevado a” que toma dos enteros y calcula el primero de ellos elevado al segundo: > (defun elv (x y) (if (= y 0) 1 (* x (elv x (- y 1))))) > (elv 2 32) > 4294967296 12. Intermedio: listas La lista es la estructura de datos m´as general, si admitimos que un elemento de una lista puede a su vez ser una lista. Entonces, las listas permiten representar vectores, matrices, pilas, colas, ´arboles y, por supuesto, listas. Estas estructuras de datos son abstractas, en el sentido de que pueden contener elementos de muchos tipos distintos, como n´ umeros, palabras o combinaciones de ambos. Por ejemplo, un diccionario es una lista de listas de dos elementos, cada una de las cuales contiene un t´ermino y su definici´on. Todos los datos que se organizan jer´arquicamente encajan en el tipo de datos abstracto ”´arbol”. Por ejemplo, la taxonom´ıa de los reinos vegetal y animal se pueden representar como un ´arbol. En Lisp, una lista se representa encerrando a sus elementos entre par´entesis: (+ 1 2 3) (hola adios luna) Como se ve, en Lisp el c´odigo y los datos se representan de la misma forma. La u ´nica diferencia es que en una lista de c´odigo se supone que el primer elemento es un operador y el resto de elementos sus argumentos. Pero si Lisp encontrase la segunda lista buscar´ıa en vano el operador hola y se emitir´ıa un mensaje de error. Para evitar esto, se indica a Lisp con un ap´ostrofo que la lista que viene a continuaci´on contiene s´olo datos: 8 ’(hola adios luna) Dos funciones son u ´tiles: (list ) y (cons ). La primera crea una lista a partir de los argumentos que se le ofrezcan: > (list 1 2 3 4) > (1 2 3 4) La segunda toma como argumentos un objeto y una lista, y a˜ nade el objeto al principio de la lista. El objeto, a su vez, puede ser una lista: > > > > (cons 3 ’(2 1 0)) (3 2 1 0) (cons ’(1 2) ’(3 4 5)) ( (1 2) 3 4 5) Como ejemplo de uso, escribimos una funci´on que toma una lista de n´ umeros como argumento y devuelve otra cuyos elementos son los elementos de la lista original multiplicados por 2: (defun duplica (lista) (if (null lista) nil (cons (* 2 (firs lista)) (duplica (rest lista))))) 13. Nivel 11 Operar sobre listas es un nuevo nivel de abstracci´on, puesto que se opera sobre ellas con independencia de qu´e contengan: si n´ umeros, cadenas, combinaciones de ambos u otras listas. Por ejemplo, la longitud de una lista es 0 si la lista est´a vac´ıa y 1 m´as la longitud de la lista que resulta de saltar el primer elemento en caso contrario. Esta definici´on es naturalmente recursiva, y se puede formaliza as´ı: (defun longitud (lista) (if (null lista) 0 (+ 1 (longitud (rest lista))))) La funci´on (first ) aplicada a una lista da como resultado el primer elemento. La funci´on (rest ) aplicada a una lista da como resultado la sublista formada por el segundo y sucesivos elementos. Ahora podemos probar: 9 > (longitud ’(1 2 3 (4 5))) > 4 Volviendo a la funci´on (media ) definida anteriormente, es natural escribir una nueva versi´on que calcule la media no de dos n´ umeros, sino de una lista de longitud arbitraria. Para ello ser´a preciso calcular la suma de una lista. Si la lista est´a vac´ıa, la suma de sus elementos es 0, si no, es el valor del primer elemento m´as la suma del resto de elementos. Esta definici´on es tambi´en naturalmente recursiva, y se escribe inmediatamente: (defun suma (lista) (if (null lista) 0 (+ (first lista) (suma (rest lista))))) Y ahora: (defun media (lista) (/ (suma lista) (longitud lista))) Probemos: > (media ’(1 2 3 4 3 2 1)) > 16/7 Un ejemplo m´as. Para calcular el m´aximo de una lista, suponiendo que no est´a vac´ıa: (defun maxlista (lista) (if (null (rest lista)) (first lista) (if (> (first lista) (maxlista (rest lista))) (first lista) (maxlista (rest lista))))) En lenguaje claro: si la lista contiene un s´olo elemento, ´el es el m´aximo, si no, puede suceder que el primer elemento sea mayor que el m´aximo del resto, y entonces es ´el el m´aximo, o puede que no, y entonces el m´aximo es el m´aximo del resto de la lista. La recursi´on parece magia. 10 14. Nivel 12 Es una operaci´on frecuente aplicar una funci´on sobre todos los elementos de una lista. En una secci´on anterior hemos escrito una funci´on llamada (duplica ). En este nivel, generalizamos la idea y nos proponemos aplicar una funci´on cualquiera a los elementos de una lista. Esto implica que las funciones puedan pasarse como par´ametros. Para completar, una funci´on deber´ıa tambi´en ser capaz de devolver como resultado otra funci´on. Por ejemplo, la suma de una serie es un ejemplo de lo primero: la funci´on encargada de la suma ha de recibir como par´ametro la funci´on f que se aplica a cada n en el intervalo desde n = a a n = b (f (n) es el t´ermino general). Un ejemplo de lo segundo es la derivada: dada una funci´on, se obtiene otra. Cubriremos estos aspectos en este nivel. Lisp incorpora tres funciones que pueden tomar a otras funciones como argumento. La primera es (funcall ), que toma una funci´on y los argumentos sobre los que se aplicar´a esa funci´on: > (funcall #’+ 2 3 4 5) > 15 La segunda es (apply ) que toma una funci´on y una lista. La funci´on se aplicar´a a los elementos de la lista: > (apply #’+ ’(2 3 4 5 6)) > 21 La tercera es (mapcar ), que toma una funci´on de una variable y una lista y aplica la funci´on a cada elemento de la lista, obteniendo otra: > (mapcar #’sqrt ’(2 3 4 5 6)) > (1.4142135 1.7320508 2 2.236068 2.4494898) Aplicaremos estas ideas para escribir una funci´on capaz de calcular la integral num´erica por la f´ormula de los trapecios. Esta funci´on tomar´a como argumentos: 1) la funci´on que se quiere integrar; 2) el extremo inferior a de la integral; 3) el extremo superior b de la integral y 4) el n´ umero de puntos N >= 2 que se tomar´an para la integral, incluyendo a a y b. En primer lugar, escribiremos una funci´on que devuelva una lista con valores igualmente espaciados, entre a y b, de N elementos: (defun valores (a b N) (if (= N 2) (cons a (cons b nil)) (cons a (valores (+ a (/ (- b a)(- N 1))) b (- N 1))))) 11 A continuaci´on, la funci´on (mapcar ) producir´a una lista de valores, que ser´an los valores que toma la funci´on que se desea integrar en cada punto de la lista primera. Una vez hecho esto la integral se obtiene enseguida, pues es el producto de (b − a)/(N − 1) por: la suma de la lista menos la semi-suma de los extremos. Sabemos c´omo sumar una lista, y sabemos c´omo obtener el primer elemento. ¿C´omo obtener el u ´ltimo? Con la funci´on (last ). Pero, al contrario que (first ), que devuelve el primer elemento de la lista, (last ) devuelve una lista que contiene s´olo el u ´ltimo elemento. Por tanto, (car (last lista)) devuelve el u ´ltimo elemento. Ahora, la funci´on que suma la lista completa y le resta la semi-suma de los elementos primero y u ´ltimo es trivial: (defun sumatorio (lista) (- (suma lista) (/ (+ (first lista) (car (last lista))) 2.0))) Con todo esto, es posible escribir la funci´on que integra num´ericamente una funci´on cualquiera entre dos extremos: (defun trapecios (f a b N) (* (/ (- b a) (- N 1)) (sumatorio (mapcar f (valores a b N))))) Ahora podemos calcular la integral de una funci´on pas´andola como argumento a (trapecios ). Por ejemplo: > (defun cuadrado (x) (* x x)) > (trapecios #’cuadrado 1.0 10.0 1000) > 332.99918 15. Segundo intermedio Una macro es un programa que escribe un programa, que despu´es se ejecuta. El alcance de las macros en Lisp va much´ısimo m´as lejos de lo que pretendemos en estas notas, pero a t´ıtulo ilustrativo, escribiremos una macro que tome como argumento una funci´on y escriba la expresi´on que despu´es se ejecutar´a para darnos la integral: > (defmacro trapmac (f a b N) ‘(* (/ (- ,b ,a)(- ,N ,1)) (sumatorio (mapcar #’,f (valores ,a ,b ,N))))) 12 despu´es de lo cual es posible escribir > (trapmac cuadrado 1.0 2.0 10) > 2.3353913 16. Nivel 12, segunda parte La segunda parte de este nivel consiste en ver que una funci´on puede devolver otra funci´on. La funci´on devuelta es creada en el interior de otra, y devuelta como resultado, de la misma forma que se calculan y devuelven n´ umeros. La sintaxis es casi trivial: (defun f (x) #’(lambda (n) (+ x n))) f es una funci´on que toma un argumento x y que devuelve otra funci´on que toma un argumento n y devuelve la suma (+ x n). lambda es un nombre especial con el que Lisp se refiere a funciones an´onimas. A veces se quiere hacer un peque˜ no c´alculo dentro de una funci´on y para eso se usa otra cuya utilidad es local: no vale la pena ponerle un nombre especial porque no va a ser llamada desde otras instancias. Entonces se usa la forma lambda. De manera que si se llama (f 3), el resultado no ser´a un n´ umero, sino una funci´on, que a su vez requerir´a de otro argumento y dar´a un resultado. > > > > > > (funcal (f 3) 5) 8 (apply (f 3) ’(5)) 8 (mapcar (f 3) ’(1 2 3 4)) (4 5 6 7) De manera que, en este punto, las funciones realizan c´alculos donde pueden manipular no s´olo n´ umeros, sino tambi´en otras funciones, y pueden tomarlas como argumento y devolverlas como resultado. 17. Nivel 13 Este es el u ´ltimo nivel que vamos a presentar aqu´ı, pero no el u ´ltimo al que permite acceder Lisp. De hecho, Lisp est´a hecho de tal forma que puede generar nuevas abstracciones a partir de otras anteriores, del nivel que se 13 quiera. Trataremos ahora de la forma en que es posible combinar funciones en una sola unidad. Hasta ahora, hemos escrito funciones individuales, o funciones que usaban funciones anteriores, aut´onomas. Por ejemplo, en el programa que calcula la integral de una funci´on por el m´etodo de los trapecios, hemos usado las funciones (sumatorio ) y (valores ), que fueron definidas antes. A su vez, (sumatorio ) hace uso de (suma ). Puede ser que todas estas funciones tengan utilidad en s´ı mismas o puede que no. Por ejemplo (suma ) puede ser u ´til fuera del contexto del c´alculo de la integral, pues hace la operaci´on gen´erica de sumar los elementos de una lista. Pero (sumatorio ) no parece tener otra utilidad que la de servir a (trapecios ). Entonces, se plantea la necesidad, o al menos la conveniencia, de poder aglutinar junto con una determinada funci´on, todas aquellas funciones auxiliares que no tienen otro prop´osito que el de ayudar dentro de su contexto, y no tienen por qu´e ser conocidas fuera de ese contexto. Estas funciones auxiliares se declaran en Lisp mediante (labels ). Vamos a reescribir la funci´on (trapecios ) de tal forma que las funciones en que se apoya sean parte suya y s´olo conocidas por ella. Antes de eso, un par de ejemplos para aclarar la sintaxis: (defun f (n) (labels ( (a (n) (+ n 4))) (* n (a 7)))) Aqu´ı, inmediatamente despu´es de declarar nuestra intenci´on de crear una funci´on de nombre f que tomar´a como argumento un n´ umero n, abrimos un ´ambito con (labels . Este ´ambito se extiende hasta el pen´ ultimo par´entesis, que cierra (labels , justo antes del u ´ltimo, que cierra (defun . Dentro de ese ´ambito, y conocida s´olo ah´ı, se declara la funci´on a. Obs´ervese que no es preciso usar (defun ). Esa funci´on hace un peque˜ no c´alculo, y servir´a de auxiliar a f. El c´alculo es simple: toma su argumento y le suma 4. Una vez terminada de definir a, se pasa a definir f, que toma su argumento y lo multiplica por el resultado de hacer (a 7). Obs´ervese que hemos llamado n tanto al argumento de f como al de a. Pero no son la misma n. La primera es el argumento de f, y la segunda es el argumento de a. Son dos variables distintas, pero tienen el mismo nombre. (a ) es llamada con argumento 7: cuando se realiza la llamada, se calcula (a ) con n=7. Cuando se llama a f, se llama con el argumento que se quiera. Por supuesto, tambi´en podr´ıamos haber escrito: (defun f (n) (labels ( (a (x) (+ x 4))) (* n (a 7)))) 14 (labels ) puede contener m´as de una funci´on, y esas funciones locales pueden llamarse entre s´ı, e incluso pueden ser recursivas. Un segundo ejemplo, en el que usamos una indentaci´on diferente para hacer m´as claro el ´ambito determinado por (labels ): (defun g (n) (labels ( (a (x) (+ x 5)) (b (y) (* y (a 4))) ) (* n (a 1) (b 2)) ) ) y ahora > (mapcar #’g ’(1 2 3 4)) > (108 216 324 432) La funci´on g se apoya en a y b, y b se apoya a su vez en a. Ni a ni b son conocidas fuera de g, y por tanto no pueden ser llamadas: si se intenta, se obtendr´a un mensaje de error. Ahora, podemos reescribir la funci´on (trapecios ). Usamos una indentaci´on ”generosa” para hacer expl´ıcito cada bloque. (defun trapecios (f a b N) (labels ( (suma (lista) (if (null lista) 0 (+ (first lista) (suma (rest lista))) ) ) (valores (a b N) (if (= N 2) (cons a (cons b nil)) (cons a (valores (+ a (/ (- b a)(- N 1))) b (- N 1))) ) ) (sumatorio (lista) (- (suma lista) (/ (+ (first lista) (car (last lista))) 2.0) 15 ) ) ) (* (/ (- b a) (- N 1)) (sumatorio (mapcar f (valores a b N))) ) ;; ;; trapecios ;; ) ) Ahora se puede calcular: > (trapecios #’cuadrado 0.0 1.0 1000) > 0.33333328 18. Conclusi´ on Aunque hemos s´olo ara˜ nado la superficie de Lisp, ha sido suficiente para hacer el recorrido t´ıpico que lleva a un estudiante desde la escuela primaria a la Universidad: desde la suma de peque˜ nas cantidades al an´alisis funcional, donde los elementos que se manipulan no son n´ umeros, sino funciones. Hemos visto c´omo cada nuevo nivel de abstracci´on puede formalizarse mediante un lenguaje de programaci´on, y c´omo esa formalizaci´on tiene un valor a˜ nadido: no s´olo se expresan conceptos en un nuevo lenguaje, lo que obliga a expresarlos de forma clara y precisa, sino que como resultado se adquieren herramientas y utilidades duraderas. Como ejemplo trivial, una vez que se dispone de la funci´on (media ) se tiene la herramienta para calcular medias de listas de n´ umeros de cualquier tipo y longitud arbitraria. (media ) puede ser u ´til en muchos contextos y, sobre todo, puede usarse de forma independiente o como una pieza para construir funciones m´as complejas. En el camino, tambi´en se adquieren formas de pensar u ´tiles. Por ejemplo, se aprende a plantear problemas usando recursividad. Dado el car´acter de estas notas, no hemos explorado las abstracciones de datos, con lo cual se podr´a organizar y manipular la informaci´on con estructuras naturales para cada tipo de problema. Para finalizar, tenemos que citar la obra Structure and Interpretation of Computer Programas, de Harold Abelson, Gerald Jay Sussman y Julie Sussman. Es una obra maestra y una fuente de placer intelectual. El lector interesado puede acudir a otras muchas obras: Practical Common Lisp, de Peter Seibel; Successful Lisp: How to Understand and Use Common Lisp, de David B. Lamkins; Land of Lisp, de Conrad Barski M. D.; Common Lisp: a Gentle Introduction to symbolic computation, de David S. Touretzky; Common 16 Lisp, de Paul Graham. Omitimos otros libros, de gran calidad pero muy alto nivel. Por supuesto, hay muchas p´aginas sobre Lisp que pueden consultarse tambi´en. 1 1 Sugerencias, erratas: [email protected] 17
© Copyright 2024