Haskell Language
Clases de tipo
Buscar..
Introducción
Las clases de tipos en Haskell son un medio para definir el comportamiento asociado con un tipo separado de la definición de ese tipo. Mientras que, digamos, en Java, definiría el comportamiento como parte de la definición del tipo, es decir, en una interfaz, clase abstracta o clase concreta, Haskell mantiene estas dos cosas separadas.
Hay una serie de clases de tipos ya definidas en el paquete base
de Haskell. La relación entre estos se ilustra en la sección de Comentarios a continuación.
Observaciones
El siguiente diagrama tomado del artículo de Typeclassopedia muestra la relación entre las diferentes clases de tipos en Haskell.
Tal vez y la clase Functor
En Haskell, los tipos de datos pueden tener argumentos como funciones. Tome el tipo Maybe
por ejemplo.
Maybe
es un tipo muy útil que nos permite representar la idea de fracaso, o la posibilidad de que sea así. En otras palabras, si existe la posibilidad de que un cálculo falle, usaremos el tipo Maybe
allí. Maybe
actúa como una especie de envoltorio para otros tipos, dándoles funcionalidad adicional.
Su declaración actual es bastante simple.
Maybe a = Just a | Nothing
Lo que esto dice es que un Maybe
viene en dos formas, un Just
, que representa el éxito, y una Nothing
, que representa el fracaso. Just
toma un argumento que determina el tipo de Maybe
, y Nothing
toma ninguno. Por ejemplo, el valor Just "foo"
tendrá el tipo Maybe String
, que es un tipo de cadena envuelto con la funcionalidad Maybe
. El valor Nothing
tiene tipo Maybe a
en a
puede ser de cualquier tipo.
Esta idea de envolver tipos para darles funcionalidad adicional es muy útil y se puede aplicar a más que a Maybe
. Otros ejemplos incluyen los tipos Either
, IO
y lista, cada uno con una funcionalidad diferente. Sin embargo, hay algunas acciones y habilidades que son comunes a todos estos tipos de envoltorios. El más notable de ellos es la capacidad de modificar el valor encapsulado.
Es común pensar en este tipo de tipos como cuadros que pueden tener valores colocados en ellos. Las diferentes cajas tienen diferentes valores y hacen diferentes cosas, pero ninguna es útil sin poder acceder a los contenidos.
Para encapsular esta idea, Haskell viene con una clase de tipos estándar, llamada Functor
. Se define de la siguiente manera.
class Functor f where
fmap :: (a -> b) -> f a -> f b
Como puede verse, la clase tiene una sola función, fmap
, de dos argumentos. El primer argumento es una función de un tipo, a
, a otro, b
. El segundo argumento es un functor (tipo de envoltura) que contiene un valor de tipo a
. Devuelve un functor (tipo de envoltorio) que contiene un valor de tipo b
.
En términos simples, fmap
toma una función y se aplica al valor dentro de un functor. Es la única función necesaria para que un tipo sea miembro de la clase Functor
, pero es extremadamente útil. Las funciones que operan en los funtores que tienen aplicaciones más específicas se pueden encontrar en las clases de tipos Applicative
y Monad
.
Herencia de clase de tipo: Ord clase de tipo
Haskell soporta una noción de extensión de clase. Por ejemplo, la clase Ord
hereda todas las operaciones en la Eq
, pero además tiene una función de compare
que devuelve un Ordering
entre valores. Ord
también puede contener los operadores de comparación de órdenes comunes, así como un método min
y un método max
.
La notación =>
tiene el mismo significado que tiene en la firma de una función y requiere el tipo a
para implementar la Eq
, para implementar Ord
.
data Ordering = EQ | LT | GT
class Eq a => Ord a where
compare :: Ord a => a -> a -> Ordering
(<) :: Ord a => a -> a -> Bool
(<=) :: Ord a => a -> a -> Bool
(>) :: Ord a => a -> a -> Bool
(>=) :: Ord a => a -> a -> Bool
min :: Ord a => a -> a -> a
max :: Ord a => a -> a -> a
Todos los métodos que siguen a la compare
se pueden derivar de varias formas:
x < y = compare x y == LT
x <= y = x < y || x == y -- Note the use of (==) inherited from Eq
x > y = not (x <= y)
x >= y = not (x < y)
min x y = case compare x y of
EQ -> x
LT -> x
GT -> y
max x y = case compare x y of
EQ -> x
LT -> y
GT -> x
Las clases de tipo que a su vez se extienden Ord
deben implementar al menos el método de compare
o el método (<=)
sí mismo, que construye la red de herencia dirigida.
Ecuación
Todos los tipos de datos básicos (como Int
, String
, Eq a => [a]
) de Prelude, excepto las funciones y IO
tienen instancias de Eq
. Si un tipo crea una instancia de Eq
, significa que sabemos cómo comparar dos valores para valor o igualdad estructural .
> 3 == 2
False
> 3 == 3
True
Metodos requeridos
-
(==) :: Eq a => a -> a -> Boolean
o(/=) :: Eq a => a -> a -> Boolean
(si solo se implementa uno, el otro por defecto es la negación del definido uno)
Define
-
(==) :: Eq a => a -> a -> Boolean
-
(/=) :: Eq a => a -> a -> Boolean
Superclases directas
Ninguna
Subclases notables
Ord
Los tipos que crean instancias de Ord
incluyen, por ejemplo, Int
, String
y [a]
(para los tipos a
donde hay Ord a
instancia de Ord a
). Si un tipo crea una instancia de Ord
, significa que conocemos un ordenamiento "natural" de valores de ese tipo. Tenga en cuenta que a menudo hay muchas opciones posibles para el ordenamiento "natural" de un tipo y Ord
nos obliga a favorecerlo.
Ord
proporciona los operadores estándar (<=)
, (<)
, (>)
, (>=)
pero los define a todos con un tipo de datos algebraico personalizado
data Ordering = LT | EQ | GT
compare :: Ord a => a -> a -> Ordering
Metodos requeridos
-
compare :: Ord a => a -> a -> Ordering
o(<=) :: Ord a => a -> a -> Boolean
(el método decompare
predeterminado del estándar utiliza(<=)
en su implementación)
Define
-
compare :: Ord a => a -> a -> Ordering
-
(<=) :: Ord a => a -> a -> Boolean
-
(<) :: Ord a => a -> a -> Boolean
-
(>=) :: Ord a => a -> a -> Boolean
-
(>) :: Ord a => a -> a -> Boolean
-
min :: Ord a => a -> a -> a
-
max :: Ord a => a -> a -> a
Superclases directas
Monoide
Los tipos que Monoid
instancia de Monoid
incluyen listas, números y funciones con valores de retorno de Monoid
, entre otros. Para crear una instancia de Monoid
un tipo debe admitir una operación binaria asociativa ( mappend
o (<>)
) que combina sus valores, y tiene un valor especial de "cero" ( mempty
) tal que la combinación de un valor con él no cambie ese valor:
mempty <> x == x
x <> mempty == x
x <> (y <> z) == (x <> y) <> z
De manera intuitiva, los tipos Monoid
son "similares a una lista" en que admiten valores agregados juntos. Alternativamente, los tipos de Monoid
se pueden considerar como secuencias de valores para los que nos preocupamos por el orden pero no por la agrupación. Por ejemplo, un árbol binario es un Monoid
, pero al usar las operaciones Monoid
no podemos presenciar su estructura de bifurcación, solo un recorrido de sus valores (consulte Foldable
y Traversable
).
Metodos requeridos
-
mempty :: Monoid m => m
-
mappend :: Monoid m => m -> m -> m
Superclases directas
Ninguna
Num
La clase más general para los tipos de números, más precisamente para los anillos , es decir, los números que pueden sumarse y restarse y multiplicarse en el sentido habitual, pero no necesariamente divididos.
Esta clase contiene tanto tipos integrales ( Int
, Integer
, Word32
etc.) como tipos fraccionales ( Double
, Rational
, también números complejos, etc.). En el caso de los tipos finitos, la semántica se entiende generalmente como aritmética modular , es decir, con overflow y underflow † .
Tenga en cuenta que las reglas para las clases numéricas están mucho menos obedecidas estrictamente que las leyes de la mónada o monoides, o aquellas para la comparación de la igualdad . En particular, los números de punto flotante generalmente obedecen las leyes solo en un sentido aproximado.
Los métodos
fromInteger :: Num a => Integer -> a
. convierta un número entero al tipo de número general (ajuste alrededor del rango, si es necesario). Los literales numéricos de Haskell se pueden entender como un literal monomorfoInteger
con la conversión general a su alrededor, por lo que puede usar el literal5
tanto en un contextoInt
como en un ajusteComplex Double
.(+) :: Num a => a -> a -> a
. Adición estándar, generalmente entendida como asociativa y conmutativa, es decir,a + (b + c) ≡ (a + b) + c a + b ≡ b + a
(-) :: Num a => a -> a -> a
. Resta, que es la inversa de la suma:(a - b) + b ≡ (a + b) - b ≡ a
(*) :: Num a => a -> a -> a
. Multiplicación, una operación asociativa que es distributiva sobre la suma:a * (b * c) ≡ (a * b) * c a * (b + c) ≡ a * b + a * c
para las instancias más comunes, la multiplicación también es conmutativa, pero esto definitivamente no es un requisito.
negate :: Num a => a -> a
. El nombre completo del operador de negación unario.-1
es azúcar sintáctico paranegate 1
.-a ≡ negate a ≡ 0 - a
abs :: Num a => a -> a
. La función de valor absoluto siempre da un resultado no negativo de la misma magnitudabs (-a) ≡ abs a abs (abs a) ≡ abs a
abs a ≡ 0
solo debería suceder sia ≡ 0
.Para los tipos reales , está claro lo que significa no negativo: siempre tienes
abs a >= 0
. Los tipos complejos, etc. no tienen un ordenamiento bien definido, sin embargo, el resultado de losabs
siempre debe estar en el subconjunto real ‡ (es decir, dar un número que también podría escribirse como un literal de un solo número sin negación).signum :: Num a => a -> a
. La función de signo, de acuerdo con el nombre, produce solo-1
o1
, dependiendo del signo del argumento. En realidad, eso solo es cierto para los números reales distintos de cero; en generalsignum
se entiende mejor como la función normalizadora :abs (signum a) ≡ 1 -- unless a≡0 signum a * abs a ≡ a -- This is required to be true for all Num instances
Tenga en cuenta que la sección 6.4.4 del Informe Haskell 2010 requiere explícitamente que esta última igualdad se mantenga para cualquier instancia de
Num
válida.
Algunas bibliotecas, notablemente lineales y hmatrix , tienen una comprensión mucho más laxa de para qué es la clase Num
: lo tratan como una forma de sobrecargar a los operadores aritméticos . Si bien esto es bastante sencillo para +
y -
, ya se vuelve problemático con *
y más con los otros métodos. Por ejemplo, debería *
significa multiplicación de matrices o multiplicación elemento a elemento?
Podría decirse que es una mala idea definir tales instancias no numéricas; Por favor considere clases dedicadas como VectorSpace
.
† En particular, los "negativos" de los tipos no firmados se envuelven en grandes positivos, por ejemplo, (-4 :: Word32) == 4294967292
.
‡ Esto no se cumple ampliamente: los tipos de vectores no tienen un subconjunto real. El controvertido Num
-instances para tales tipos generalmente definen abs
y signum
elemento a elemento, que matemáticamente hablando realmente no tiene sentido.