Chapter 13
Clases y funciones
13.1 Hora
Como otro ejemplo de un tipo definido por el usuario, definiremos una clase
llamada Hora que registra la hora del día. La definición de la clase es
como sigue:
\begin{verbatim
class Hora:
pass
\end{verbatim}
\afterverb
%
Podemos crear un nuevo objeto {\tt Hora} y asignar atributos para contener las
horas, minutos y segundos:
\beforeverb
\begin{verbatim}
hora = Hora()
hora.horas = 11
hora.minutos = 59
hora.segundos = 30
\end{verbatim}
\afterverb
%
El diagrama de estado del objeto {\tt Hora} es así:
\beforefig
\centerline{\psfig{figure=illustrations/time.eps}}
\afterfig
\begin{quote}
{\em A modo de ejercicio, escriba una función {\tt imprimeHora} que acepte un
objeto {\tt Hora} como argumento y lo imprima en el formato {\tt
horas:minutos:segundos}.}
\end{quote}
\begin{quote}
{\em Como un segundo ejercicio, escriba una función booleana {\tt after} que
tome dos objetos {\tt Hora}, {\tt t1} y {\tt t2}, como argumentos y devuelva
verdadero (1) si {\tt t1} sigue cronológicamente a {\tt t2} y falso (0) en caso
contrario.}
\end{quote}
\section{Funciones puras}
\index{función pura}
\index{tipo de función!pura}
En las próximas secciones, escribiremos dos versiones de una función llamada
{\tt sumaHora} que calcule la suma de dos {\tt Hora}s. Mostrarán dos tipos de
funciones: funciones puras y modificadores.
Éste es un esbozo de {\tt sumaHora}:
\beforeverb
\begin{verbatim}
def sumaHora(t1, t2):
suma = Hora()
suma.horas = t1.horas + t2.horas
suma.minutos = t1.minutos + t2.minutos
suma.segundos = t1.segundos + t2.segundos
return suma
\end{verbatim}
\afterverb
%
La función crea un nuevo objeto {\tt Hora}, inicializa sus atrobutos y devuelve
una referencia al nuevo objeto. A esto se le llama {\bf función pura} porque no
modifica ninguno de los objetos que se le pasan y no tiene efectos laterales,
como mostrar un valor o tomar una entrada del usuario.
Aquí tiene un ejemplo de cómo usar esta función. Crearemos dos objetos {\tt
Time}: {\tt horaActual}, que contiene la hora actual, y {\tt horaPan}, que
contiene la cantidad de tiempo que necesita un panadero para hacer pan. Luego
usaremos {\tt sumaHora} para averiguar cuándo estará hecho el pan. Si aún no ha
terminado de escribir {\tt imprimeHora}, eche un vistazo a la
Sección~\ref{imprimeHora} antes de probar esto:
\beforeverb
\begin{verbatim}
>>> horaActual = Hora()
>>> horaActual.horas = 9
>>> horaActual.minutos = 14
>>> horaActual.segundos = 30
>>> horaPan = Hora()
>>> horaPan.horas = 3
>>> horaPan.minutos = 35
>>> horaPan.segundos = 0
>>> horaHecho = sumaHora(horaActual, horaPan)
>>> imprimeHora(horaHecho)
\end{verbatim}
\afterverb
%
La salida de este programa es {\tt 12:49:30}, lo que es correcto. Por otra
parte, hay casos en los que el resultado no es correcto. ¿Puede imaginar uno?
El problema es que esta función no trata los casos en los que el número de
segundos o minutos suma más que sesenta. Cuando ocurre eso, debemos "llevar"
los segundos sobrantes a la columan de los minutos o los minutos extras a la
columna de las horas.
He aquí una versión corregida de la función:
\beforeverb
\begin{verbatim}
def sumaHora(t1, t2):
suma = Hora()
suma.horas = t1.horas + t2.horas
suma.minutos = t1.minutos + t2.minutos
suma.segundos = t1.segundos + t2.segundos
if suma.segundos >= 60:
suma.segundos = suma.segundos - 60
suma.minutos = suma.minutos + 1
if suma.minutos >= 60:
suma.minutos = suma.minutos - 60
suma.horas = suma.horas + 1
return suma
\end{verbatim}
\afterverb
%
Aunque esta función es correcta, empieza a ser grande. Más adelante sugeriremos
una aproximación alternativa que nos dará un código más corto.
\section{Modificadores}
\label{incremento}
\index{modificador}
\index{tipo de función!modifcador}
Hay veces en las que es útil que una función modifique uno o más de los objetos
que recibe como parámetros. Normalmente, el llamante conserva una referencia a
los objetos que pasa, así que cualquier cambio que la función haga será visible
para el llamante. Las funciones que trabajan así se llaman {\bf modificadores}.
{\tt incremento}, que añade un número dado de segundos a un objeto {\tt Hora},
se escribiría de forma natural como un modificador. Un esbozo rápido de la
función podría ser éste:
\adjustpage{-2}
\pagebreak
\beforeverb
\begin{verbatim}
def incremento(hora, segundos):
hora.segundos = hora.segundos + segundos
if hora.segundos >= 60:
hora.segundos = hora.segundos - 60
hora.minutos = hora.minutos + 1
if hora.minutos >= 60:
hora.minutos = hora.minutos - 60
hora.horas = hroa.horas + 1
\end{verbatim}
\afterverb
%
La primera línea realiza la operación básica, las restantes tratan con los
casos especiales que vimos antes.
¿Es correcta esta función? ¿Qué ocurre si el parámetro {\tt segundos} es mucho
mayor que sesenta? En tal caso, no es suficiente con acarrear una vez; debemos
seguir haciéndolo hasta que {\tt segundos} sea menor que sesenta. Una solución
es sustituir las sentencias {\tt if} por sentencias {\tt while}:
\beforeverb
\begin{verbatim}
def incremento(hora, segundos):
hora.segundos = hora.segundos + segundos
while hora.segundos >= 60:
hora.segundos = hora.segundos - 60
hora.minutos = hora.minutos + 1
while hora.minutos >= 60:
hora.minutos = hora.minutos - 60
hora.horas = hroa.horas + 1
\end{verbatim}
\afterverb
%
Ahroa esta función es correcta, pero no es la solución más eficiente.
\begin{quote}
{\em Como ejercicio, reescriba esta función de modo que no contenga tantos
bucles.}
\end{quote}
\begin{quote}
{\em Como un segundo ejercicio, reescriba {\tt incremento} como una función
pura, y escribe una función que llame a ambas versiones.}
\end{quote}
\section{¿Qué es mejor?}
\index{estilo funcional de programación}
\adjustpage{1}
Todo lo que se pueda hacer con modificadores puede hacerse también con
funciones puras. En realidad, algunos lenguajes de programación sólo permiten
funciones puras. Hay ciertas evidencias de que los programas que usan funciones
puras son más rápidos de desarrollar y menos propensos a los errores que los
programas que usan modificadores. Sin embargo, a veces los modificadores son
útiles, y en algunos casos los programas funcionales son menos eficientes.
En general, recomendamos que escriba funciones puras siempre que sea razonable
hacerlo así y recurra a los modificadores sólo si hay una ventaja convincente.
Este enfoque podría llamarse {\bf estilo funcional de programación}.
\section{Desarrollo de prototipos frente a planificación}
\label{convertir}
\index{desarrollo de prototipos}
En este capítulo mostramos una aproximación a al desarrollo de programas a la
que llamamos {\bf desarrollo de prototipos}. En cada caso, escribimos un esbozo
basto (o prototipo) que realizaba el cálculo básico y luego lo probamos sobre
unos cuantos casos, corrigiendo los fallos tal como los encontrábamos.
Aunque este enfoque puede ser efecitvo, puede conducirnos a código que es
innecesariamente complicado, ya que trata con muchos casos especiales, y poco
fiable, porque es difícil saber si encontró todos los errores.
Una alternativa es el {\bf desarrollo planificado}, en el que una comprensión
del problema en profundidad puede hacer la programación mucho más fácil. En
este caso, el enfoque es que un objeto {\tt Hora} es en realidad ¡un número de
tres dígitos en base 60! El componente {\tt segundo} es la "columna de
unidades", el componente {\tt minuto} es la "columna de las sesententas" y el
componente {\tt houra} es la "columna de las tresmilseiscentenas".
Cuando escribimos {\tt sumaHora} e {\tt incremento}, en realidad estábamos
haciendo una suma en base 60, que es por lo que debíamos acarrear de una
columna a la siguiente.
Esta observación sugiere otro enfoque para el problema. Podemos convertir un
objeto {\tt Hora} en un simple número y sacar provecho del hecho de que la
máquina sabe la aritmética necesaria. La siguiente función convierte un objeto
{\tt Hora} en un entero:
\beforeverb
\begin{verbatim}
def convierteASegundos(t):
minutos = t.horas * 60 + t.minutos
segundos = minutos * 60 + t.segundos
return segundos
\end{verbatim}
\afterverb
%
Ahora, sólo necesitamos una forma de convertir un entero en un objeto
{\tt Hora}:
\adjustpage{1}
\beforeverb
\begin{verbatim}
def haceHora(segundos):
hora = Hora()
hora.horas = segundos/3600
segundos = segundos - hora.horas * 3600
hora.minutos = segundos/60
segundos = segundos - hora.minutos * 60
hora.segundos = segundos
return hora
\end{verbatim}
\afterverb
%
Puede que tenga usted que pensar un poco para convencerse de que esta técnica
para convertir de una base a otra es correcta. Suponiendo que está usted
convencido, puede usar estas funciones para reescribir {\tt sumaHora}:
\beforeverb
\begin{verbatim}
def sumaHora(t1, t2):
segundos = convierteASegundos(t1) + convierteASegundos(t2)
return haceHora(segundos)
\end{verbatim}
\afterverb
%
Esta versión es mucho más corta que la original, y es mucho más fácil de
demostrar que es correcta (suponiendo, como es habitual, que las funciones a
las que llama son correctas).
\begin{quote}
{\em Como ejercicio, reescriba {\tt incremento} de la misma forma.}
\end{quote}
\section{Generalización}
\index{generalización}
De algún modo, convertir de base 60 a base 10 y de vuelta es más difícil que
simplemente manejarse con las horas. La conversión de base es más abstracta;
nuestra intuición para tratar con las horas es mejor.
Pero si tenemos la comprensión para tratar las horas como números en base 60, y
hacer la inversión de escribir las funciones de conversión ({\tt
convierteASegundos} y {\tt haceHora}), obtenemos un programa que es más corto,
más fácil de leer y depurar y más fiable.
También es más fácil añadir funcionalidades más tarde. Por ejemplo, imagine
restar dos {\tt Hora}s para hallar el intervalo entre ellas. La aproximación
ingenua sería implementar la resta con acarreo. Con el uso de las funciones de
conversión será más fácil y con mayor probabilidad, correcto.
Irónicamente, a veces hacer un poblema más complejo (o más general) lo hace más
fácil (porque ha menos casos especiales y menos oportunidades de error).
\section{Algoritmos}
\index{algoritmo}
Cuando escribe una solución general para una clase de problemas, en contraste
con una solución específica a un problema concreto, ha escrito un {\bf
algoritmo}. Mencionamos esta palabra antes pero no la definimos con precisión.
No es fácil de definir, así que probaremos un par de enfoques.
Primero, piense en algo que no es un algoritmo. Cuando usted aprendió a sumar
números de una cifra, probablemente memorizó la tabla de multiplicar. En
efecto, memorizó 100 soluciones específicas. Ese tipo de conocimiento no es
algorítmico.
Pero si usted era "haragán" probablemente hizo trampa aprendiendo algunos
trucos. Por ejemplo, para encontrar el producto de $n$ por 9, puede escribir
$n-1$ como el primer dígito y $10-n$ como el segundo dígito. Este truco es una
solución general para multiplicar cualquier número de una cifra por 9. ¡Eso es
un algoritmo!
De forma similar, las técnicas que aprendió para la suma y la resta con acarreo
y la división larga son todas algoritmos. Una de las características de los
algoritmos es que no requieren inteligencia para llevarse a cabo. Son procesos
mecánicos en los que cada paso sigue al anterior de acuerdo a un conjunto
simple de reglas.
En nuestra opinión, es un poco vergonzoso que los humanos pasen tanto tiempo en
la escuela aprendiendo a ejecutar algoritmos que, de forma bastante similar, no
exigen inteligencia.
Por otra parte, el proceso de diseñar algoritmos es interesante, un desafío
intelectual y una parte primordial de lo que llamamos programar.
Algunas de las cosas que la gente hace naturalmente, sin dificultad ni
pensamiento consciente, son las más difíciles de expresar algorítmicamente.
Entender el lenguaje natural es un buen ejemplo. Todos lo hacemos, pero hasta
el momento nadie ha sido capaz de explicar {\em cómo} lo hacemos, al menos no
en la forma de un algoritmo.
\section{Glosario}
\begin{description}
\item[función pura:] Una función que no modifica ninguno de los objetos que
recibe como parámetros. La mayoría de las funciones puras son rentables.
\item[modificador:] Una función que modifica uno o más de los objetos que
recibe como parámetros. La mayoría de los modificadores no entregan resultado.
\item[estilo funcional de programación:] Un estilo de programación en el que la
mayoría de las funciones son puras.
\item[desarrollo de prototipos:] Una forma de desarrollar programas empezando
con un prototipo y probándolo y mejorándolo gradualmente.
\item[desarrollo planificado:] Una forma de desarrollar programas que implica
una comprensión de alto nivel del problema y más planificación que desarrollo
incremental o desarrollo de prototipos.
\item[algoritmo:] Un conjunto de instrucciones para solucionar una clase de
problemas por medio de un proceso mecánico sin intervención de inteligencia.
\index{función pura}
\index{modifcador}
\index{estilo funcional de programación}
\index{desarrollo incremental}
\index{desarrollo!incremental}
\index{desarrollo planificado}
\index{desarrollo!planificado}
\index{algoritmo}
\end{description}