% -*- coding: iso-8859-1-unix; -*- % cap05.tex % % Programación Lógica y Funcional % Álvaro Tasistro - Jorge Vidart % III E.B.A.I. (1988) % % maquetación LaTeX por Raquel Rebaque % % Copyright (C) 1988 Álvaro Tasistro - Jorge Vidart. % % Permission is granted to copy, distribute and/or modify this document % under the terms of the GNU Free Documentation License, Version 1.2 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'' % % $Id$ % \chapter{Un caso de diseño con lenguaje funcional} \label{cha:casodisenolenguajefuncional} % -------------------------------------------------------- \begin{abstract}\pagina{81} \label{sec:casdislenfunresumen} La idea central de este capítulo es introducir algunas características de la programación funcional, vinculando ésta a las técnicas de diseño modular. El punto tiene interés por cuanto luego se verá que las propiedades más importantes de los lenguajes funcionales están relacionadas con la facilidad que proveen para expresar la aplicación de dichas técnicas. Se presenta un caso de estudio (tomado de~\cite{Hen80} y un lenguaje funcional, (inspirado en un similar usado en~\cite{Gla84} que no se define formalmente. A pesar de este hecho, puede verse como su estructura insinúa las características esenciales de los lenguajes funcionales las que se tratarán, con mayor rigor, a partir del capítulo~\ref{cha:calculolambda}. \end{abstract} % -------------------------------------------------------- \section{Funciones, problemas y diseño modular} \label{sec:funcionesproblemasydiseniomodular} El\pagina{82} primer presupuesto del paradigma funcional es caracterizar un problema como una función entre conjuntos, a ser definida. Tal función podría definirse por extensión, si todos los pares de correspondientes fueran conocidos, o bien, en el caso más general, por una expresión que, convenientemente manipulada, determine la imagen de cada objeto del dominio. Un ejemplo, de aspecto seguramente familiar, de una tal definición es: $$ f(x) = 2 * x + 1 $$ En definiciones como la anterior, se llama a $x$ un argumento nominal de $f$, y será, en todos los casos, una variable que denota un valor genérico del dominio de la función. A la derecha del símbolo de igualdad hay una expresión que denota la imagen del valor del argumento nominal en la función que está siendo definida. Es útil analizar esta expresión con detalle: En ella aparecen los símbolos $*$ y $+$, que (es de suponer) denotan las funciones producto y suma, aplicadas a constantes y al argumento $x$. De este modo se indica que, para calcular la imagen de $x$ en $f$ deben usarse otras funciones, aplicándolas a $x$ y a ciertos valores convenientes. De modo que, en definitiva, la definición de $f$ se muestra como una combinación adecuada de otras funciones. En general, podría requerirse que las funciones así usadas tuvieran que ser, a su vez, definidas. Esto conduciría, en el caso anterior, a complementar la definición dada con un par de expresiones, de la misma forma, para $*$ y $+$, en las cuales habrán de introducirse, a su vez, nuevas funciones. De esta manera: \begin{enumerate}[(a).] \item La estructura de definiciones sucesivas se organiza, a grandes rasgos, en forma jerárquica, según como cada función aparece en la definición de otra «más compleja». \item En\pagina{83} cada definición, las funciones utilizadas a la derecha de la igualdad se referencian simplemente por un nombre. El significado preciso de éste puede conocerse si se lo reemplaza por su propia definición. \item Debe convenirse un modo de terminar esta sucesión de definiciones. La convención consiste en establecer que ciertos nombres no necesitan ser definidos, aceptándose que tienen un significado «sobreentendido». \end{enumerate} Este esquema evoca uno de los aspectos más importantes de las técnicas de diseño modular, en particular, la idea de descomposición de un problema complejo en componentes más simples (módulos) que se combinan convenientemente. Cada uno de ellos representa, nuevamente, un problema que, o bien tiene una solución sobreentendida, o bien es, a su vez, descompuesto de igual forma. Como en el caso de $*$ y $+$, los módulos pueden ser referenciados apenas por un nombre. Esta facilidad es importante, puesto que hace posible que el empleo de un módulo no involucre el conocimiento de su estructura interna, que podría ser compleja, sino simplemente su afecto. Luego, tal estructura puede desarrollarse por separado, asociándola al nombre utilizado a través de otra definición. Algunos de estos nombres no necesitan tener sus propias definiciones, conviniendose que sus significados son sobreentendidos: corresponderán a modulos \textit{primitivos}. Los que no sean primitivos, se llamarán \textit{abstractos}. Nótese que unos y otros no se distinguen desde el punto de vista de su forma de uso. En ambos casos, ésta consiste, en principio, en citar su nombre. De hecho, los módulos abstractos pueden pensarse, en virtud de esta característica, como «primitivas potentes». La idea caracteriza el diseño por refinamientos sucesivos: La estructura de un problema complejo puede diseñarse en términos de componentes pensados como «ya resueltos», que además, pueden efectivamente usarse como tales. Tales componentes se definirán en un siguiente nivel de detalle, si no son primitivos. Sin\pagina{84} embargo, la elección de estos componentes para un problema arbitrario dado no es, en modo alguno, trivial. El enfoque anterior se complementa, en este sentido, con la idea de elegir la descomposición de modo que puedan emplearse, en todo lo posible, soluciones ya desarrolladas, con un mínimo costo de adaptación. En términos de los lenguajes de programación, ésto hace interesante el estudio de las facilidades que ellos proveen para definir y emplear módulos de aplicación general. Como se verá más adelante, dichas facilidades constituyen uno de los principales atributos de los lenguajes funcionales. Algunas de estas ideas comenzarán a ser aplicadas enseguida. % -------------------------------------------------------- \section{El caso de estudio} \label{sec:elcasodeestudio} Se trata de un problema clásico de las ciencias físicas: el análisis dimensional de fórmulas. En estas ciencias, las fórmulas usadas (para denotar ciertos fenómenos) son formadas por operadores aplicados a variables o constantes, las cuales tienen asociadas dimensiones. Estas son, a su vez, expresiones formadas a partir de ciertas magnitudes fundamentales, por ejemplo: masa~(M), longitud~(L) y tiempo~(T). En este caso, las dimensiones asociadas a las variables o constantes de estas fórmulas serán expresiones definidas sobre las constantes M, L, T. Algunos ejemplos se muestran a continuación: Una variable o constante que representa: \begin{center} velocidad tiene dimensión \begin{math} LT^{-1} \end{math} \end{center} \hfill por ser cociente de una cantidad de \hfill longitud por una cantidad de tiempo. \begin{center} área tiene dimensión \begin{math} L^{2} \end{math} \end{center} \hfill por ser producto de dos longitudes. \begin{center} fuerza\pagina{85} tiene dimensión MLT \end{center} \hfill por ser producto de una cantidad de masa \hfill por otra de aceleración que, a su vez, es \hfill cociente entre una cantidad de velocidad y \hfill una cantidad de tiempo.\\ Ahora, a partir de las dimensiones de las variables y constantes, pueden deducirse las dimensiones asociadas a las fórmulas definidas sobre ellas. Para esto es necesario establecer: \begin{enumerate}[i.] \item \label{i} cuáles son los operadores que pueden ser empleados en la construcción de fórmulas. \item \label{ii} para cada operador, cuál es la dimensión del resultado, en función de las dimensiones de los operandos. \end{enumerate} A efectos de~\ref{i} se considerarán solamente los operadores aritméticos\\ usuales: +, -, *, /. Para establecer~\ref{ii} es necesario adoptar algunos supuestos: \begin{itemize} \item Se asumirá que existen tres magnitudes fundamentales y que \item Las dimensiones de las constantes y variables tienen, en todos los casos, la forma de un producto de esas magnitudes, cada una afectada por un exponente entero. \end{itemize} Así, si las magnitudes fundamentales fueran masa, longitud y tiempo, una dimensión genérica sería: \begin{center} mlt\\ MLT con m, l y t enteros \end{center} \noindent y para una cantidad de velocidad, en particular, valdría: $$ m=0 \mathrm{,}\ l=1\ \mathrm{,}\ t = -1 $$ En este punto, el lector puede ensayar una primera versión de las reglas que caracterizan la dimensión del resultado de cada operador artimético a partir de las dimensiones de sus operandos. % -------------------------------------------------------- \section{Desarrollo del diseño} \label{sec:desarrollodeldisenio} Como\pagina{86} se ha dicho, el enfoque funcional caracteriza un problema como una función a ser definida. Desde el punto de vista de la programación interesan, fundamentalmente, las definiciones de funciones que son dadas en la forma de reglas que definen la construcción del correspondiente de cualquier objeto del dominio. Una forma (en principio, simplificada) de una tal definición es: \begin{center} \textbf{«f»} es la función que a cada valor de \textbf{x} hace corresponder \textbf{«exp»} \end{center} donde: \begin{quote} \begin{description} \item[«f»] es el nombre de la función a definir, \item[ x] es una variable que representa un objeto genérico del dominio, \item[«exp»] es la expresión que define el correspondiente de \textbf{x} en \textbf{f}. Como ya se vió, consistirá en general, de aplicaciones de otras funciones. \end{description} \end{quote} Se usan las comillas para denotar objetos cuya forma concreta se determinará como consecuencia de decisiones tomadas durante el proceso de diseño. Este estará terminado cuando todos ellos hayan sido sustituidos por construcciones de significado convenido. La frase usada: «es la función que a cada valor de x hace corresponder» cumple dos roles: por una parte asocia el nombre de la función a su definición. Por otra, indica cuál es el argumento nominal de esta definición. Para abreviar, se usará en su lugar: \textbf{:- (x)}, donde la asociación \textit{nombre - definición} es representada por \textbf{:-} y la indicación de argumento nominal, por su nombre entre paréntesis. Puede parecer que la notación es extraña y, más aún, invertida en relación a la usanza corriente. Sin embargo, se verá luego que es más razonable, incluso, que la tenida por normal. El\pagina{87} caso más general parecería exigir la posibilidad de definir funciones de más de un argumento. De otro modo, funciones elementales, como la simple suma de enteros, no podrían ser expresadas. Aunque más adelante esta tesis será rebatida, se aceptará de momento y, de hecho, será aplicada al caso de estudio, como surge de las siguientes consideraciones: El argumento obvio de la función a definir es una fórmula, cuya dimensión debe ser construída como resultado. Sin embargo, éste depende también de las dimensiones de los componentes más primitivos de la fórmula, es decir, de las constantes y variables a partir de las cuales ésta se construye. Por explicación adicional de lo anterior véase que $$ ma - f $$ \noindent por ejemplo, tendrá distintas dimensiones según si $m$ representa una cantidad de masa o de velocidad, e idénticamente ocurre para las demás variables. De modo que las dimensiones de las variables y constantes intervinientes en la fórmula deben constituir un segundo argumento de esta función, y así aparecerá en su definición, que será de la forma: \begin{center} \textit{dimension :- (f,datom) «exp»} \end{center} donde \begin{quote} \begin{description} \item[f] representa una fórmula, \item[datom] representa la asociación de dimensiones a los componentes atómicos (variables, constantes) de \textbf{f}, \item[«exp»] es una expresión que define el correspondiente de todo par (f, datom) posible en la función dimension. \end{description} \end{quote} Es ya inevitable iniciar la discusión acerca de la forma de \textbf{«exp»}. Para ésto es necesario definir el conjunto de fórmulas con precisión. Una técnica general que puede usarse con este próposito es la de inducción. Como se verá, constituye una poderosa herramienta de diseño, que será aplicada reiteradamente. Para el caso de las fórmulas pueden usarse las siguientes ideas: \begin{description} \item[(Base)] Las\pagina{88} fórmulas primitivas son las que consisten de apenas una variable o una constante. Se reunirán éstas en una clase, que llamaremos de fórmulas atómicas. \item[(Inducción)] El resto de las fórmulas puede construirse usando cuatro funciones constructoras: \textit{Suma, Resta, Producto, Cociente}. La idea será denotar dichas fórmulas como expresiones que sean aplicaciones de estas funciones. \end{description} Entonces, si $f$ y $g$ son fórmulas ya construidas, \begin{verbatim} Suma (f, g) Resta (f, g) Producto (f, g) y Cociente (f, g) \end{verbatim} \noindent son expresiones cada una de las cuales denota una nueva fórmula, respectivamente la suma, resta, producto y cociente de las denotadas por las $f$ y $g$ originales. Se exigen propiedades de los constructores: Para cualesquiera constructores $K$ y $K'$ y fórmulas $f1$, $f2$, $g1$, $g2$ vale: \begin{itemize} \item Si $f$ es una fórmula atómica, entonces \begin{math} f \not= K (f1,g1) \end{math} \item \begin{math} K (f1,g1) = K' (f2,g2) \end{math} implica $K$ idéntico a $K'$, \begin{math} f1 = f2 \end{math} y \begin{math} g1 = g2 \end{math} \end{itemize} Es decir, las fórmulas construidas por aplicación de constructores son todas diferentes entre sí y diferentes de las atómicas. En adelante se llamará fórmula a toda expresión formada de acuerdo con las reglas anteriores. Estas permiten establecer que una fórmula cumple una (y sólo una) de las condiciones siguientes: \begin{itemize} \item es atómica (variable o constante) \item es Suma aplicada a dos fórmulas \item es Resta aplicada a dos fórmulas \item es Producto aplicado a dos fórmulas \item es Cociente aplicado a dos fórmulas \end{itemize} De\pagina{89} este modo, se clasifican las fórmulas en cinco clases, según como han sido construídas. Ahora bien, volviendo al problema, dada una fórmula genérica deberá ser posible distinguir entre sus cinco posibles formas y definir las respectivas dimensiones asociadas por separado. En base a esta idea puede resolverse al problema en cuestión. Algunas funciones, con significado conveniente, deben ser usadas para expresar lo anterior. En principio, puede pensarse en las siguientes: \begin{enumerate}[i.] \item \label{op1}Una función asociada a cada clase de las arriba definidas, teniendo como dominio el conjunto de fórmulas y como codominio el conjunto de valores de verdad (\mbox{Verdadero}, \mbox{Falso}). Que una tal función aplicada a una fórmula $f$ dé como resultado \emph{Verdadero} significará que $f$ está en la clase asociada a dicha función. \item \label{op2}Una función cuyo resultado pueda escogerse de entre varios en base a la verificación de cierta condición. \end{enumerate} El candidato para~\ref{op2} está sugerido en una construcción conocida desde los lenguajes imperativos; se usará: \begin{center} if b then v1 else v2 \end{center} \noindent con el significado habitual. Es interesante enfatizar que esta construcción denota una función aunque esta idea sea ajena a su pariente imperativo. El carácter funcional podría quedar más de manifiesto si se usara la notación \emph{prefija}: \begin{center} if-then-else (b, v1, v2) \end{center} \noindent más familiar cuando se habla de funciones, en lugar de la empleada, llamada \emph{infija}. Cualquiera\pagina{90} sea la notación usada, el significado de if-then-else se establece como sigue: \begin{itemize} \item $b$ puede tomar uno de los dos valores: Verdadero o Falso. \item si $b$ vale Verdadero, entonces if-then-else toma el valor de $v1$ y, en caso contrario, el de $v2$. \end{itemize} A poco de razonar sobre el problema puede verse que los requisitos exigidos en~\ref{op1} pueden relajarse. En efecto, es necesario usar funciones de la forma citada para distinguir todos los posibles comportamientos de una fórmula genérica en relación a su dimensión asociada, pero también es cierto que los comportamientos diferentes no coinciden con las cinco clases de construcciones definidas. El lector que haya pensado en la relación entre los operadores aritméticos y las dimensiones de sus resultados habrá detectado que la suma y la resta se comportan idénticamente (si no fue así, he aquí una nueva oportunidad para pensarlo). De modo, pues, que sólo cuatro casos deben ser distinguidos. Uno de ellos puede decidirse por el simple descarte de los otros, si se asume que el argumento $f$ es siempre una fórmula correctamente formada. Estas consideraciones conducen a la siguiente versión de la función: \begin{verbatim} dimension :- (f,datom) if Atómica? (f) then «exp_atom» else if Suma-o-Resta? (f) then «exp_+_-» else if Producto? (f) then «exp_*» else «exp_/» \end{verbatim} El proceso de descomposición comienza a insinuarse. La función dimensión se expresa en términos de otras funciones, en el caso: \textbf{if-then-else, Atómica?, Suma-o-Resta?, Producto?} y de las que se usen para especificar las aún desconocidas expresiones denotadas entre comillas, que serán precisamente las que construyan los resultados para cada caso. Nótese\pagina{91} que, a la manera de lo anunciado en la sección anterior, las funciones son usadas sólo citando su nombre y no su propia estructura, como «módulos abstractos» de significado convenido. Obviamente deberán continuar el diseño de dimensión. Este debe ocuparse ahora de las «expresiones» todavía no especificadas. Entonces: \begin{enumerate}[i.] \item si la fórmula es atómica, el argumento \textbf{datom} define su dimensión asociada, como ya ha sido dicho; \textbf{datom} mismo puede verse, de hecho, como una función (dada) cuyo dominio es el conjunto de las fómulas atómicas y cuyo codominio es el conjunto de las dimensiones posibles. No importando como estén definidas las dimensiones, lo anterior establece que si \textbf{f} es atómica, entonces \textbf{datom (f)} es su dimensión. Esto resuelve la especificación de «exp\_atom».\\ Nótese el uso de una función como argumento de otra. Esto es permitido ya en algunos lenguajes imperativos, pero como se verá, alcanza toda su generalidad dentro del esquema funcional: las funciones podrán ser, sin restricciones, argumentos y resultados de otras funciones. \item si la fórmula no es atómica, entonces es el resultado de aplicar un constructor a dos operandos que son fórmulas y su dimensión puede expresarse como la aplicación de una transformación conveniente a las dimensiones de los operandos, siguiendo un esquema recursivo.\\ Para ésto será necesario disponer de funciones que, aplicadas a una fórmula no atómica, devuelvan las fómulas operandos a partir de las cuales aquella fue construida. Tales funciones son, usualmente, denominadas \textbf{selectores}. En el caso, los selectores de fómulas serán las funciones $PrimerOperando$ y $SegundoOperando$.\\ Hay tres casos interesantes de fórmulas no atómicas, como ya se ha visto. Estas definen tres formas de combinar las dimensiones de los operandos para dar la de la fómula compuesta.\\ Se llamarán \begin{verbatim} DimAd (adición de dimensiones), DimProd (producto de dimensiones) y DimCoc (cociente de dimensiones) \end{verbatim} \end{enumerate} Lo\pagina{92} analizado justifica la nueva versión: \begin{verbatim} dimension :- (f, datom) if Atómica? (f) then datom (f) else if Suma-o-Resta? (f) then DimAd ( dimension (PrimerOperando (f), datom), dimension (segundoOperando (f), datom) ) else if Producto? (f) then DimProd ( dimension (PrimerOperando (f), datom), dimension (segundoOperando (f), datom) ) else DimCoc ( dimension (PrimerOperando (f), datom), dimension (segundoOperando (f), datom) ) \end{verbatim} Esto completa un nivel del diseño. En efecto, todas las construcciones «entrecomilladas» han sido sustituídas por expresiones cuyo significado, a este nivel, se da por conocido, siguiendo la idea de la sección~\ref{sec:funcionesproblemasydiseniomodular}. Estas expresiones son siempre de la forma: \begin{verbatim} «nombre de función» («lista de argumentos») \end{verbatim} \noindent denotando aplicaciones de funciones sobre argumentos convenientes. De este modo, el significado de la función dimensión, se resuelve en términos de los significados de otras funciones, cada uno de los cuales debería, en principio, ser definido en niveles inferiores más detallados. Se dará un nuevo paso en esa dirección, definiendo las funciones de adición, producto y cociente de dimensiones. Estas son funciones sobre el conjunto de las dimensiones (más precisamente sobre el conjunto de pares de dimensiones). Como en el caso de las fómulas, debe definirse este conjunto. En\pagina{93} la sección~\ref{sec:elcasodeestudio} se sugirió caracterizar las dimensiones por el producto de tres mangitudes fundamentales afectadas por exponentes enteros. Con esta aproximación, construir una dimensión requiere proveer los valores de esos exponentes, es decir, una terna de dimensiones, que se llamará \textbf{DimCons}. El conjunto de todas las dimensiones es el conjunto de las aplicaciones de \textbf{DimCons} a toda posible terna de enteros. Inversamente, dada una dimensión genérica \textbf{b}, será necesario obtener los valores de sus exponentes. Para esto se usarán tres funciones: $PrimerExp$, $SegundoExp$, $TercerExp$, a la manera de \textbf{selectores}. También se usará una función que detecta si dos dimensiones dadas son idénticas. Su codominio es, obviamente, (\mbox{Verdadero}, \mbox{Falso}) y su nombre \textbf{DimEq}. Su comportamiento, el esperable. A partir de estos supuestos, se desarrollan las tres funciones requeridas: \begin{enumerate}[i.] \item La adición de dos dimensiones está definida sólo para el caso en que éstas sean idénticas, y el resultado es la misma dimensión.\\ La restricción impuesta es la manera de expresar el hecho de que no se puedan sumar (ni restar) velocidades con fuerzas, naranjas con libros, o cualquier otro par de cantidades que no hayan sido expresadas idénticamente en relación a las magnitudes fundamentales que se manejen.\\ En el lenguaje de funciones: \begin{verbatim} DimAd :- (d1, d2) if DimEq (d1, d2) then d1 else ERROR \end{verbatim} ERROR juega aquí como un valor especial distinguido, empleado para denotar casos de excepción como el anotado. En particular representa un caso de dimensión especial (la de las fómulas mal formadas, que intentan combinar operandos «incompatibles»). \item\pagina{94} El producto de dos dimensiones es, en todos los casos, otra dimensión cuyos exponentes son, cada uno, la suma de los respectivos exponentes de los operandos: \begin{verbatim} DimProd :- (d1, d2) DimCons (PrimerExp (d1) + PrimerExp (d2), SegundoExp (d1) + SegundoExp (d2), TercerExp (d1) + TercerExp (d2)) \end{verbatim} \item El cociente de dos dimensiones es otra dimensión, cuyos exponentes son, cada uno, la diferencia de los respectivos exponentes de los operandos: \begin{verbatim} DimProd :- (d1, d2) DimCons (PrimerExp (d1) - PrimerExp (d2), SegundoExp (d1) - SegundoExp (d2), TercerExp (d1) - TercerExp (d2)) \end{verbatim} \end{enumerate} Aquí han aparecido ya funciones que el lector puede, sin duda, aceptar como «primitivas»: son las denotadas por los conocidos símbolos $+$ y $-$ y que representan la suma y resta de enteros. Seguramente no extrañará que no se den definiciones sobre dimensiones, por ejemplo. A la vez, durante el desarrollo se ha hecho referencia a otras funciones que parecerían merecer una definición detallada, como las llamadas \textbf{Atómica?}, \textbf{Suma-o-Resta?}, etc. En definitiva, debe convenirse acerca de cuál es el conjunto de funciones primitivas. Tal convención debe establecerse en forma precisa y definitiva para todos los problemas que quieran ser resueltos y determina el nivel de explicación al que debe llegarse, en un lenguaje dado, para que una solución se considere suficientemente descripta. En este caso, el lenguaje no ha sido definido con precisión de antemano, lo que autoriza a detener el proceso de diseño en el momento en que las explicaciones\pagina{95} dadas se consideren suficientes para ilustrar la técnica de descomposición modular. Ese momento ya ha llegado. En efecto, se ha mostrado la utilización del concepto de «módulo abstracto» en su forma funcional y definiciones de algunos de éstos han sido desarrolladas con independencia de la del módulo de mayor jerarquía que los utilizaba. En capítulos siguientes habrá ocasión de someterse a reglas rigurosas que obliguen a llegar a niveles de refinamiento precisamente establecidos. También se ha mostrado el empleo de algunas técnicas de diseño generales, como las de definiciones inductivas, y algunas características novedosas de los lenguajes funcionales han comenzado a insinuarse, en especial la posibilidad de tratar a las funciones como argumentos y resultados de otras funciones. Estos puntos serán especialmente considerados en el siguiente capítulo. Problemas. \begin{enumerate}[5.1] \item Escribir la definición de la función que calcula el máximo común divisor de dos naturales dados. \item Usando selectores y funciones auxiliares apropiadas (que no se definirán) escribir la definición de una función que sume todos los elementos de un árbol binario. \item \label{it3} Escribir la definición de una función que, aplicada a una lista de enteros, devuelve un árbol binario «ordenado». \item \label{it4} Escribir definiciones de funciones que, aplicada a un árbol binario, devuelvan la lista de sus elementos, en: \begin{enumerate}[a)] \item pre orden \item orden central \item post orden \end{enumerate} \item Usando~\ref{it3} y~\ref{it4} escribir la definición de una función que ordene una lista dada. \end{enumerate} % EOF cap05.tex %%% Local Variables: %%% mode: latex %%% TeX-master: "plyf" %%% End: