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.

Relaciones entre las clases de tipos estándar de Haskell, Figura 1 tal como se publicó en Typeclassopedia.

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 de compare 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 monomorfo Integer con la conversión general a su alrededor, por lo que puede usar el literal 5 tanto en un contexto Int como en un ajuste Complex 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 para negate 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 magnitud

      abs (-a) ≡ abs a
      abs (abs a) ≡ abs a
    

    abs a ≡ 0 solo debería suceder si a ≡ 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 los abs 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 o 1 , dependiendo del signo del argumento. En realidad, eso solo es cierto para los números reales distintos de cero; en general signum 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.



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow