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.