viernes, 17 de febrero de 2017

1.3 Definición de funciones


En las funciones que hemos visto, los parámetros son números, valores booleano solistas. Pero un parámetro de una función puede también ser una función. Un ejemplo es la función map. Tiene dos parámetros: una función y una lista. La función map aplica la función parámetro a todos los elementos de la lista. Por ejemplo: 

? map fac [1,2,3,4,5]
 [1, 2, 6, 24, 120]
? map sqrt [1.0,2.0,3.0,4.0]
 [1.0, 1.41421, 1.73205, 2.0]
? map even [1...8]
 [False, True, False, True, False, True, False, True]

Existen muchas funciones en Gofer que tienen funciones como parámetro. En el capítulo 2 se describen pág. 23 más funciones de este tipo.

Definición por combinación

La manera más fácil de definir funciones es por combinación de otras funciones:
fac n = product [1...n]
 impar x = not (even x)
 cuadrado x = x*x

suma de cuadrados lista = sum (map cuadrado lista)
Las funciones pueden tener más de un parámetro:
comb n k = fac n / (fac k * fac (n-k))
formula ABC a b c = [ (-b+sqrt(b*b-4.0*a*c)) / (2.0*a), (-b-sqrt(b*b-4.0*a*c)) / (2.0*a) ]

Las funciones sin parámetros se llaman normalmente constantes:
pi = 3.1415926535
e = exp 1.0

Toda definición de función tiene por tanto la siguiente forma:

• el nombre de la función
• los nombres de los parámetros (si existen)

• el símbolo = 
• una expresión, que puede contener los parámetros, las funciones estándar y otras funciones definidas.

Una función que tiene un valor booleano como resultado, tiene a la derecha del símbolo = una expresión con un valor booleano:

negativo x = x < 0
 positivo x = x > 0
 es Cero x = x == 0

Note la diferencia en el anterior ejemplo entre = y ==. El símbolo = separa la parte izquierda de la parte derecha de la dentición. El símbolo == es un operador, como < y >.

En la definición de la función formula ABC se encuentran las expresiones sqrt (b*b-4.0*a*c) y (2.0*a) dos veces. Esto da mucho trabajo para teclear, pero calcular estas expresiones exige también demasiado tiempo: se calcula dos veces la misma expresión. Para evitar eso, se puede dar (en Gofer) un nombre a (sub)expresiones. La definición mejorada sería formula ABC’:

Formula ABC’ a b c = [ (-b+d)/n
                                   , (-b-d)/n
                                   ]
                                   where d = sqrt (b*b-4.0*a*c)  
                                                           n = 2.0*a

Con la opción de estadística del interprete (el comando: set +s) se puede ver la diferencia muy bien:

? formula ABC’ 1.0 1.0 0.0
 [0.0, -1.0]
(18 reductions, 66 cells)
? formula ABC 1.0 1.0 0.0
 [0.0, -1.0]
(24 reductions, 84 cells)

La palabra where no es el nombre de una función. Es una de las palabras reservadas que están en la lista de la sección 1.3.2. Detrás de ‘where’ hay algunas definiciones. En el caso del ejemplo, las definiciones de las constantes d y n. Estas constantes se pueden usar en la expresión anterior a la parte de where. Fuera de este lugar no se pueden usar: son definiciones locales. Parece tal vez extraño que se llamen a d y n ‘constantes’, porque el valor puede ser diferente para cada vez que se llama a la función formula ABC’. Pero durante la computación de una llamada a formula ABC’, con valores para a, b y c, aquellos valores son constantes.

Definición por distinción de casos

Algunas veces es necesario distinguir diferentes casos en la definición de una función. Un ejemplo es la función abs: con un parámetro negativo la definición es diferente que con un parámetro positivo. Esto se anota en Gofer de la siguiente manera:

abs x | x<0 = -x
         | x>=0 = x

También, se pueden distinguir más de dos casos. Por ejemplo, en la función signum:

signum x | x>0 = 1
                | x==0 = 0
                | x<0 = -1

Las definiciones de los diferentes casos son precedidas por expresiones booleanas, que se llaman guardas. Si se llama a una función definida de esta forma, se tratan las guardas una a una, hasta que se encuentre una guarda con el valor True. Entonces, se evalúa la expresión a la derecha del símbolo =. En ocasiones la última guarda es True o la constante otherwise. Por tanto, la descripción de la forma de la definición de una función es más amplia que la explicada en la sección anterior. Una descripción más completa de la definición de una función es:

 • el nombre de la función;
 • los nombres de los parámetros (si existen);
 • el símbolo = y una expresión, o una o más expresiones guardadas;
• si se desea, la palabra where seguida de definiciones locales.

En cada expresión guardada debe existir el símbolo |, una expresión booleana, el símbolo = yunaexpresi´on2. Esta descripción de funciones todavía no está completa...

Definición por análisis de patrones

Se llaman parámetros formales a los parámetros de una función en la definición de la misma, por ejemplo x e y en

f x y = x * y

En una llamada a la función se dan parámetros actuales a la función. Por ejemplo, en la llamada

f 17 (1+g 6)

17 es el parámetro actual que corresponde a x, (1+g 6) es el parámetro actual que corresponde a y. Los parámetros actuales se sustituyen en los lugares de los parámetros formales cuando se llama a una función. El resultado del anterior ejemplo es por tanto 17*(1+g 6). Por tanto, los parámetros actuales son expresiones. Los parámetros formales eran hasta ahora solamente nombres. En la mayoría de los lenguajes de programación, el parámetro formal tiene que ser un nombre. Existen otras posibilidades en Gofer: un parámetro formal puede ser también un patrón. Un ejemplo de la definición de una función en que se usa un patrón como parámetro formal es:

f [1,x,y] = x+y

Esta función es correcta solamente si el parámetro es una lista de exactamente tres elementos, con el primer elemento 1. La función suma el segundo y el tercer elemento. La función entonces, no está definida para listas más pequeñas o más grandes, o para listas que tienen otro elemento que 1 como primer elemento. (Es normal que una función no esté definida para todos los parámetros actuales que son posibles. Por ejemplo, la función sqrt no está definida para parámetros negativos y el operador / no está definido con 0 como segundo parámetro.) Se pueden definir funciones con diferentes patrones como parámetro formal:

suma [] = 0
suma [x] = x
suma [x,y] = x+y
suma [x,y,z] = x+y+z

Esta función se puede aplicar a listas con cero, uno, dos o tres elementos (en la siguiente sección se define esta función para listas con cualquier tamaño). En todos los casos la función suma los elementos. Llamando a la función, el intérprete ve si el parámetro cumple con una de las definiciones. La llamada suma [3,4] cumple con la tercera definición. 3 corresponde al parámetro x en la definición y 4 a y. Las siguientes construcciones se pueden utilizar como patrón:

• Números (por ejemplo 3);
• Las constantes True y False;
• Nombres (por ejemplo, x);
• Listas cuyos elementos también son patrones (por ejemplo [1,x,y]);
• El operador: con patrones a la izquierda y a la derecha (por ejemplo a:b);
• El operador + con un patrón a la izquierda y un entero a la derecha (por ejemplo n+1);
• El operador * con un patrón a la derecha y un entero a la izquierda (por ejemplo 2*x).

Con la ayuda de los patrones se pueden definir varias funciones importantes. Por ejemplo, el operador && del preludio:

False && False = False
False && True = False
True && False = False
 True && True = True

Con el operador: se pueden construir listas. La expresión x:y significa: coloca el elemento x delante de la lista y. Al incluir el operador: se separa el primer elemento del resto de la lista. Con esto podemos escribir dos funciones estándar que son bastante útiles:

head (x:y) = x
tail (x:y) = y

La función head (cabeza) devuelve el primer elemento de una lista; la función tail (cola) devuelve todo excepto el primer elemento. Usos de estas funciones en expresiones son, por ejemplo:

? head [3,4,5]
3
? tail [3,4,5]
 [4, 5]

Estas funciones se pueden aplicar a casi todas las listas, solamente están indefinidas para la lista vacía (la lista sin elementos). En los patrones en que ocurren los operadores + o *, las variables tienen que ser de enteros. Por ejemplo, se puede definir una función par que devuelve True si el número es par:

par (2*n) = True
par (2*n+1) = False

Con la llamada par 5, solamente se cumple el segundo patrón (en este caso n representa 2). No se cumple el primer patrón puesto que en ese caso n tendría que ser 2.5, que no es un entero.

Definición por recursión o inducción

En la definición de una función se pueden usar las funciones estándar y las funciones definidas por el usuario. Pero también se puede usar la propia función que se define en su definición. A tal definición se la llama definición recursiva. La siguiente función es una función recursiva:

f x = f x

El nombre de la función que se define (f), está en la expresión que define la función, a la derecha del símbolo =. Sin embargo, esta definición no tiene sentido. Para calcular el valor de f 3, se tiene que calcular el valor de f 3 primero y para eso, se tiene que calcular el valor de f 3, etc... Las funciones recursivas tienen sentido bajo las siguientes dos condiciones:

• el parámetro de la llamada recursiva es más simple que el parámetro de la función que se quiere definir (por ejemplo, numéricamente menor o una lista más corta);

• existe una definición no recursiva para un caso base. Una definición recursiva de la función factorial es:
fac n | n==0 = 1

 | n>0 = n * fac (n-1)

Aquí el caso base es n==0; en este caso se puede calcular el resultado directamente (sin recursión). En el caso n>0 existe una llamada recursiva, es decir fac (n-1). El parámetro de esta llamada (n-1) es menor que n. Otra manera de distinguir estos dos casos (caso base y caso recursivo) es usando patrones:

fac 0 = 1
fac (n+1) = (n+1) * fac n

También en este caso el parámetro de la llamada recursiva (n) es menor que el parámetro de la función que se quiere definir (n+1). El uso de patrones tiene mucha relación con la tradición matemática del usar inducción. Por ejemplo, la definición matemática de la función potencia puede ser usada casi directamente como función en Gofer:

x ^ 0 = 1
x ^ (n+1) = x * x^n

Una definición recursiva en que se usan patrones para distinguir diferentes casos (en lugar de expresiones booleanas) se llama también definición inductiva. Las funciones sobre listas también pueden ser recursivas. Una lista es ‘menor’ que otra si tiene menos elementos. La función suma que hemos visto en la sección anterior, puede ser definida de diferentes maneras. Una definición recursiva con expresiones booleanas es:

suma lista | lista==[] = 0
                 | otherwise = head lista + suma (tail lista)

Pero también es posible obtener una versión inductiva (usando patrones):

suma [] = 0
suma (cabeza: cola) = cabeza + suma cola

En la mayoría de los casos, es más clara una definición con patrones, porque las diferentes partes en el patrón pueden conseguir un nombre directamente (como cabeza y cola en la función suma). En la versión recursiva de suma se necesitan las funciones estándar head y tail para distinguir las diferentes partes en lista. En estas funciones también se usan patrones. La función estándar length, que calcula el número de elementos de una lista, puede ser definida inductivamente:

length [] = 0
 length (cabeza: cola) = 1 + length cola

En este caso, el valor del primer elemento de la lista no importa, solamente importa que exista un primer elemento. En patrones se permite usar el símbolo ‘_’ en este tipo de casos en vez de un nombre:

length [] = 0
length (_:cola) = 1 + length cola

Indentacion y comentarios

En la mayoría de los lugares de un programa, se pueden poner más blancos para hacer el programa más claro. En los anteriores ejemplos, hemos añadido más espacio, para ordenar las definiciones de funciones. Está claro que no se pueden poner blancos en un nombre de una función o en un número. len gth es algo diferente de length, y 1 7 es algo diferente de 17. También se puede añadir una nueva línea para hacer el resultado claro. Hemos hecho esto en la definición de formula ABC, porque sin ello la línea sería muy larga. Pero, a diferencia de otros lenguajes de programación, una nueva línea tiene un significado. Véanse por ejemplo las siguientes construcciones:

where                           where
a = f x y                        a = f x
b = g z                         b = g z y

Entre estas dos construcciones existe bastante diferencia. En una serie de definiciones, Gofer usa la siguiente manera para decidir que es parte de que:

• una línea inventada exactamente tanto como la línea anterior se considera una nueva definición.
• una línea inventada más que la anterior, es parte de la línea anterior.
 • una línea inventada menos que la anterior, no es parte de las definiciones anteriores.

El último caso es importante si la construcción de where está en otra definición de where. Por ejemplo, en
f x y = g (x+w)
           where g u = u + v
                            where v = u * u
                                            w = 2 + y

w es una declaración local de f, y no de g. La definición de w está a la izquierda de la definición de v; por tanto, no forma parte de la construcción del where de g. Deja tanto espacio libre como la definición de g, por tanto, es parte de la construcción del where de f. Si dejara menos espacio libre, no sería parte de tal construcción. En este caso, Gofer daría un mensaje de error. Tal vez estas reglas parezcan un poco complicadas, pero no es dificil si se usa la siguiente regla global:

definiciones equivalentes están igualmente inventadas

Por tanto, todas las definiciones de funciones tienen que empezar en la misma columna (por ejemplo, todas en la posición cero).

Comentarios En cualquier lugar donde se puedan poner blancos, se puede poner un comentario. El intérprete ignora todo comentario. ´Este solamente es u´til para los lectores del programa. Existen dos tipos de comentarios en Gofer:

• con los símbolos -- empieza un comentario, que termina al finalizar la línea.
• con los símbolos {- empieza el comentario, que termina con los símbolos -}.

Una excepción a la primera regla es el caso que -- sea parte de un operador, por ejemplo, en <-->. La combinación -- fue reservada (en el párrafo 1.3.2).

Los comentarios con {- y -} se pueden anidar. Cada comentario termina con el correspondiente símbolo de terminación. Por ejemplo, en

{- {- hola -} f x = 3 -}

no se define una función f, todo es comentario.


Fokker, J. (1996). Programación Funcional (primera edicion septiembre 1992 ed.). (H. R. J., Ed., & p. M. simón, Trad.) madrid: Universidad Carlos III de Madrid.

No hay comentarios:

Publicar un comentario