Haskell Hero

Haskell Hero es un manual interactivo del lenguaje Haskell para principiantes.

Tipos III

¿Por qué clases de tipos?

Ya sabemos de las lecciones anteriores sobre tipos que un tipo es una caja de valores relacionados. Por ejemplo Integer es una caja que contiene todos números enteros.

¿Qué es el tipo de la función (==)? Puede comparar números enteros, así que podríamos decir que es de tipo Integer -> Integer -> Bool. Puede también comparar caracteres, así que podríamos decir que es de tipo Char -> Char -> Bool. En este momento nos acordamos de tipos polimórficos y decimos que la función es de tipo a -> a -> Bool. Sin embargo, esto significaría que esta función puede comparar también árboles binarios, lo que no es verdad.

¿Cómo hacer entonces la declaración de tipo de (==)? En este momento nos sirven clases de tipos.

Cajas de cajas


La representación de la clase de tipo Eq

Esta es la caja Eg (de la palabra inglés equal) que contiene todos los tipos de valores que se pueden comparar por igualdad. El tipo de la función (==) será entonces el siguiente:

(==)  ::  Eq a  =>  a -> a -> Bool

Es decir, la función (==) puede tomar dos parámetros de cualquier tipo que está en la clase de tipos Eq.

¿Qué es el tipo de la función (+)? Puede sumar números enteros, puede sumar números decimales pero no puede sumar por ejemplo valores booleanos. Nos gustaría entonces expresar que puede sumar cualesquier valores numéricos. La caja de todos números se llama Num.


La representación de la clase de tipo Num

El tipo de la función (+) es entonces el siguiente:

(+)  ::  Num a  =>  a -> a -> a

La clase Num contiene entre otras cosas también subclases Integral, lo que son todos números enteros, y Floating, lo que son todos números decimales. Para nosotros es suficiente conocer la caja Num.

Nos queda mencionar la caja Ord que contiene todo lo que se puede ordenar. En otras palabras, contiene todos los valores que se pueden comparar entre sí con operadores < y >.

Operadores < y > son entonces de tipo

(<)  ::  Ord a  =>  a -> a -> Bool

Insertamos cajas en cajas

Ahora ya sabemos que hay funciones que toman como parámetro "cualquier cosa que se puede comparar por igualdad". Por ejemplo la función

zipWith (==)  ::  Eq a => [a] -> [a] -> [Bool]

toma dos listas de cualquier cosa comparable y devuelve una lista de valores booleanos. Nos gustaría aplicar esta función por ejemplo a dos listas de tipo [Nat], lo que son números naturales que hemos definido antes. Están definidos de manera siguiente:
data Nat  =  Zero  |  Succ Nat

Nos gustaría poder evaluar esta expresión:
zipWith  (==)  [Zero, Succ Zero]  [Zero, Succ (Succ Zero)]

~>*  [True, False]

Sin embargo, esta evaluación no se puede realizar ya que Nat no pertenece a la clase Eq. ¿Qué va ahora? Insertamos el tipo Nat en la clase Eq. Lo hacemos definiendo la igualdad de dos valores de tipo Nat.


¿Cuándo son iguales dos valores de tipo Nat?

  • cuando son ambos Zero
  • cuando el primer valor es Succ x, el segundo valor es Succ y y x es igual a y, lo que se comprueba de manera recursiva
  • otros casos (Zero, Succ x o Succ x, Zero) no son iguales

Lo vamos a definir de esta manera. Primero hay que mencionar que hacemos. Insertamos el tipo Nat en la caja Eq, es decir, definimos una nueva instancia de la clase Eq.

instance Eq Nat

Completamos la definición de igualdad de dos valores de tipo Nat según la descripción mencionada arriba.
instance Eq Nat

  where Zero   == Zero    =  True
        Succ x == Succ y  =  x == y
        _      == _       =  False

En la ecuación Succ x == Succ y = x == y en la parte derecha la misma función se llama por medio de == de manera recursiva ya que x y y son también de tipo Nat, es decir, pueden solo estar en forma Zero o Succ x.

Y ya está. Ahora podemos usar valores de tipo Nat siempre cuando se necesita un tipo de la clase Eq.

Si el tipo está en la clase de tipo Ord, la operación <= tiene que estar definida. La clase Num requiere más. Estas operaciones tienen que ser definidas para que un tipo pueda estar en la clase Num:

  • (+) (la adición)
  • (-) (la resta)
  • (*) (la multiplicación)
  • abs (el valor absoluto)
  • fromInteger (la función que convierte un número entero en un número del tipo requerido)
  • signum (la función en la que la expresión signum x se evalúa a 1 si x es un número positivo, a -1 si x es nu número negativo, y a 0 si x es cero)