Buscar..


Observaciones

Estas extensiones de idioma suelen estar disponibles cuando se utiliza el Compilador de Haskell de Glasgow (GHC), ya que no forman parte del Informe de idiomas de Haskell 2010 aprobado. Para usar estas extensiones, uno debe informar al compilador usando una bandera o colocar un programa de LANGUAGE antes de la palabra clave del module en un archivo. La documentación oficial se puede encontrar en la sección 7 de la guía del usuario de GCH.

El formato del programa LANGUAGE es {-# LANGUAGE ExtensionOne, ExtensionTwo ... #-} . Ese es el literal {-# seguido de LANGUAGE seguido por una lista de extensiones separadas por comas, y finalmente el #-} cierre ». Se pueden colocar múltiples programas de LANGUAGE en un archivo.

MultiParamTypeClasses

Es una extensión muy común que permite clases de tipo con múltiples parámetros de tipo. Puedes pensar en MPTC como una relación entre tipos.

{-# LANGUAGE MultiParamTypeClasses #-}

class Convertable a b where
    convert :: a -> b

instance Convertable Int Float where
    convert i = fromIntegral i

El orden de los parámetros importa.

Los MPTC a veces se pueden reemplazar con familias de tipos.

FlexibleInstances

Las instancias regulares requieren:

All instance types must be of the form (T a1 ... an)
where a1 ... an are *distinct type variables*,
and each type variable appears at most once in the instance head.

Eso significa que, por ejemplo, mientras puede crear una instancia para [a] no puede crear una instancia específicamente para [Int] . FlexibleInstances relaja que:

class C a where

-- works out of the box
instance C [a] where

-- requires FlexibleInstances
instance C [Int] where

SobrecargadoStrings

Normalmente, los literales de cadena en Haskell tienen un tipo de String (que es un alias de tipo para [Char] ). Si bien esto no es un problema para programas educativos más pequeños, las aplicaciones del mundo real a menudo requieren un almacenamiento más eficiente, como Text o ByteString .

OverloadedStrings simplemente cambia el tipo de literales a

"test" :: Data.String.IsString a => a

Permitir que se pasen directamente a las funciones que esperan un tipo de este tipo. Muchas bibliotecas implementan esta interfaz para sus tipos de cadena, incluidos Data.Text y Data.ByteString, que proporcionan ciertas ventajas de tiempo y espacio sobre [Char] .

También hay algunos usos únicos de OverloadedStrings como los de la biblioteca Postgresql-simple que permite escribir consultas SQL entre comillas dobles como una cadena normal, pero brinda protección contra la concatenación incorrecta, una fuente notoria de ataques de inyección de SQL.

Para crear una instancia de la clase IsString , debe fromString función fromString . Ejemplo :

data Foo = A | B | Other String deriving Show

instance IsString Foo where
  fromString "A" = A
  fromString "B" = B
  fromString xs  = Other xs

tests :: [ Foo ]
tests = [ "A", "B", "Testing" ]

Este ejemplo es cortesía de Lyndon Maydwell ( sordina en GitHub) que se encuentra aquí .

TupleSections

Una extensión sintáctica que permite aplicar el constructor de tuplas (que es un operador) en una sección:

(a,b) == (,) a b

-- With TupleSections
(a,b) == (,) a b == (a,) b == (,b) a

N-tuplas

También funciona para tuplas con aridad mayor a dos.

(,2,) 1 3 == (1,2,3)

Cartografía

Esto puede ser útil en otros lugares donde se usan secciones:

map (,"tag") [1,2,3] == [(1,"tag"), (2, "tag"), (3, "tag")]

El ejemplo anterior sin esta extensión se vería así:

map (\a -> (a, "tag")) [1,2,3]

UnicodeSyntax

Una extensión que le permite usar caracteres Unicode en lugar de ciertos operadores y nombres integrados.

ASCII Unicode Usos)
:: tiene tipo
-> Tipos de funciones, lambdas, ramas de case , etc.
=> restricciones de clase
forall polimorfismo explícito
<- do notación
* el tipo (o tipo) de tipos (por ejemplo, Int :: ★ )
>- notación proc para Arrows
-< notación proc para Arrows
>>- notación proc para Arrows
-<< notación proc para Arrows

Por ejemplo:

runST :: (forall s. ST s a) -> a

se convertiría

runST ∷ (∀ s. ST s a) → a

Tenga en cuenta que el ejemplo * vs. es ligeramente diferente: ya que * no está reservado, también funciona de la misma manera que * para la multiplicación, o cualquier otra función llamada (*) , y viceversa. Por ejemplo:

ghci> 2 ★ 3
6
ghci> let (*) = (+) in 2 ★ 3
5
ghci> let (★) = (-) in 2 * 3
-1

Literales binarios

Haskell estándar le permite escribir literales enteros en decimal (sin ningún prefijo), hexadecimal (precedido por 0x o 0X ) y octal (precedido por 0o o 0O ). La extensión BinaryLiterals agrega la opción de binario (precedida por 0b o 0B ).

0b1111 == 15     -- evaluates to: True

Cuantificación existencial

Esta es una extensión de sistema de tipo que permite tipos que se cuantifican existencialmente, o, en otras palabras, tienen variables de tipo que solo se instancian en tiempo de ejecución .

Un valor de tipo existencial es similar a una referencia de clase base-abstracta en idiomas OO: no sabe el tipo exacto que contiene, pero puede restringir la clase de tipos.

data S = forall a. Show a => S a

o equivalentemente, con sintaxis GADT:

{-# LANGUAGE GADTs #-}
data S where
   S :: Show a => a -> S

Los tipos existenciales abren la puerta a cosas como contenedores casi heterogéneos: como se dijo anteriormente, en realidad puede haber varios tipos en un valor S , pero todos pueden show n, por lo tanto, también puede hacer

instance Show S where
    show (S a) = show a   -- we rely on (Show a) from the above

Ahora podemos crear una colección de tales objetos:

ss = [S 5, S "test", S 3.0]

Lo que también nos permite utilizar el comportamiento polimórfico:

mapM_ print ss

Los existenciales pueden ser muy poderosos, pero tenga en cuenta que en Haskell no son necesarios muy a menudo. En el ejemplo anterior, todo lo que puede hacer con la instancia de Show es mostrar (¡duh!) Los valores, es decir, crear una representación de cadena. Por lo tanto, todo el tipo S contiene exactamente tanta información como la cadena que se obtiene al mostrarla. Por lo tanto, generalmente es mejor simplemente almacenar esa cadena de inmediato, especialmente porque Haskell es perezoso y, por lo tanto, la cadena al principio solo será un thunk no evaluado de todos modos.

Por otro lado, los existenciales causan algunos problemas únicos. Por ejemplo, la forma en que la información de tipo está "oculta" en un existencial. Si coincide con un patrón en un valor de S , tendrá el tipo contenido en el alcance (más precisamente, su instancia de Show ), pero esta información nunca puede escapar de su alcance, que por lo tanto se convierte en una "sociedad secreta": el compilador no permite que nada se escape del ámbito, excepto los valores cuyo tipo ya se conoce desde el exterior. Esto puede provocar errores extraños, como Couldn't match type 'a0' with '()' 'a0' is untouchable .


Contraste esto con el polimorfismo paramétrico ordinario, que generalmente se resuelve en el momento de la compilación (lo que permite el borrado de todo tipo).


Los tipos existenciales son diferentes de los tipos Rank-N: estas extensiones son, en términos generales, duales entre sí: para usar valores de un tipo existencial, necesita una función polimórfica (posiblemente restringida), como se show en el ejemplo. Una función polimórfica se cuantifica universalmente , es decir, funciona para cualquier tipo en una clase dada, mientras que la cuantificación existencial significa que funciona para algún tipo particular que es a priori desconocido. Si tiene una función polimórfica, eso es suficiente, sin embargo, para pasar funciones polimórficas como argumentos, necesita {-# LANGUAGE Rank2Types #-} :

genShowSs :: (∀ x . Show x => x -> String) -> [S] -> [String]
genShowSs f = map (\(S a) -> f a)

LambdaCase

Una extensión sintáctica que te permite escribir \case en lugar de \arg -> case arg of .

Considere la siguiente definición de función:

dayOfTheWeek :: Int -> String
dayOfTheWeek 0 = "Sunday"
dayOfTheWeek 1 = "Monday"
dayOfTheWeek 2 = "Tuesday"
dayOfTheWeek 3 = "Wednesday"
dayOfTheWeek 4 = "Thursday"
dayOfTheWeek 5 = "Friday"
dayOfTheWeek 6 = "Saturday"

Si desea evitar repetir el nombre de la función, puede escribir algo como:

dayOfTheWeek :: Int -> String
dayOfTheWeek i = case i of
    0 -> "Sunday"
    1 -> "Monday"
    2 -> "Tuesday"
    3 -> "Wednesday"
    4 -> "Thursday"
    5 -> "Friday"
    6 -> "Saturday"

Usando la extensión LambdaCase, puedes escribir eso como una expresión de función, sin tener que nombrar el argumento:

{-# LANGUAGE LambdaCase #-}

dayOfTheWeek :: Int -> String
dayOfTheWeek = \case
    0 -> "Sunday"
    1 -> "Monday"
    2 -> "Tuesday"
    3 -> "Wednesday"
    4 -> "Thursday"
    5 -> "Friday"
    6 -> "Saturday"

RankNTypes

Imagina la siguiente situación:

foo :: Show a => (a -> String) -> String -> Int -> IO ()
foo show' string int = do
   putStrLn (show' string)
   putStrLn (show' int)

Aquí, queremos pasar una función que convierte un valor en una Cadena, aplicar esa función tanto a un parámetro de cadena como a un parámetro int e imprimirlos. En mi mente, no hay razón para que esto falle! Tenemos una función que funciona en ambos tipos de parámetros que estamos pasando.

Desafortunadamente, esto no va a escribir cheque! GHC infiere a tipo basado en su primera aparición en el cuerpo de la función. Es decir, en cuanto nos topamos:

putStrLn (show' string)

GHC inferirá que show' :: String -> String , ya que string es una String . Se procederá a estallar mientras se intenta show' int .

RankNTypes permite escribir la firma de tipo de la siguiente manera, cuantificando sobre todas las funciones que satisfacen el tipo show' :

foo :: (forall a. Show a => (a -> String)) -> String -> Int -> IO ()

Este es el rango 2 polimorfismo: estamos afirmando que el show' función debe funcionar para todos a s dentro de nuestra función y la implementación anterior ahora trabaja.

La extensión RankNTypes permite el anidamiento arbitrario de todos los bloques forall ... en firmas de tipo. En otras palabras, permite el polimorfismo de rango N.

Listas sobrecargadas

añadido en GHC 7.8 .

OverloadedLists, similar a OverloadedStrings , permite que los literales de la lista se analicen de la siguiente manera:

[]          -- fromListN 0 []
[x]         -- fromListN 1 (x : [])
[x .. ]     -- fromList (enumFrom x)

Esto es útil cuando se trata de tipos como Set , Vector y Map s.

['0' .. '9']             :: Set Char
[1 .. 10]                :: Vector Int
[("default",0), (k1,v1)] :: Map String Int
['a' .. 'z']             :: Text

IsList clase IsList en GHC.Exts está diseñada para usarse con esta extensión.

IsList está equipado con una función de tipo, Item y tres funciones, fromList :: [Item l] -> l , toList :: l -> [Item l] y fromListN :: Int -> [Item l] -> l donde fromListN es opcional. Las implementaciones típicas son:

instance IsList [a] where
  type Item [a] = a
  fromList = id
  toList   = id

instance (Ord a) => IsList (Set a) where
  type Item (Set a) = a
  fromList = Set.fromList
  toList   = Set.toList

Ejemplos tomados de OverloadedLists - GHC .

Dependencias funcionales

Si tiene una clase de tipo de múltiples parámetros con los argumentos a, b, c y x, esta extensión le permite expresar que el tipo x se puede identificar de forma única a partir de a, b y c:

class SomeClass a b c x | a b c -> x where ...

Al declarar una instancia de dicha clase, se verificará contra todas las demás instancias para asegurarse de que se mantiene la dependencia funcional, es decir, no existe ninguna otra instancia con el mismo abc pero diferente x .

Puede especificar múltiples dependencias en una lista separada por comas:

class OtherClass a b c d | a b -> c d, a d -> b where ...

Por ejemplo en MTL podemos ver:

class MonadReader r m| m -> r where ...
instance MonadReader r ((->) r) where ...

Ahora, si tiene un valor de tipo MonadReader a ((->) Foo) => a , el compilador puede inferir que a ~ Foo , ya que el segundo argumento determina completamente el primero, y simplificará el tipo en consecuencia.

La clase SomeClass se puede considerar como una función de los argumentos abc que dan como resultado x . Tales clases pueden usarse para hacer cálculos en el sistema de tipos.

GADTs

Los tipos de datos algebraicos convencionales son paramétricos en sus variables de tipo. Por ejemplo, si definimos un ADT como

data Expr a = IntLit Int 
            | BoolLit Bool 
            | If (Expr Bool) (Expr a) (Expr a)

con la esperanza de que esto descartará estáticamente condicionales no bien tipificados, esto no se comportará como se espera ya que el tipo de IntLit :: Int -> Expr a se cuantifica IntLit :: Int -> Expr a : para cualquier elección de a , produce un valor de tipo Expr a . En particular, para a ~ Bool , tenemos IntLit :: Int -> Expr Bool , que nos permite construir algo como If (IntLit 1) e1 e2 que es lo que el tipo del constructor If intentaba descartar.

Los tipos de datos algebraicos generalizados nos permiten controlar el tipo resultante de un constructor de datos para que no sean meramente paramétricos. Podemos reescribir nuestro tipo Expr como un GADT como este:

data Expr a where
  IntLit :: Int -> Expr Int
  BoolLit :: Bool -> Expr Bool
  If :: Expr Bool -> Expr a -> Expr a -> Expr a

Aquí, el tipo del constructor IntLit es Int -> Expr Int , y así IntLit 1 :: Expr Bool no verificará.

La coincidencia de patrones en un valor GADT provoca el refinamiento del tipo del término devuelto. Por ejemplo, es posible escribir un evaluador para Expr a como este:

crazyEval :: Expr a -> a
crazyEval (IntLit x) = 
   -- Here we can use `(+)` because x :: Int
   x + 1 
crazyEval (BoolLit b) = 
   -- Here we can use `not` because b :: Bool
   not b
crazyEval (If b thn els) = 
  -- Because b :: Expr Bool, we can use `crazyEval b :: Bool`.
  -- Also, because thn :: Expr a and els :: Expr a, we can pass either to 
  -- the recursive call to `crazyEval` and get an a back
  crazyEval $ if crazyEval b then thn else els 

Tenga en cuenta que podemos usar (+) en las definiciones anteriores porque cuando, por ejemplo, IntLit x coincide con el patrón, también aprendemos que a ~ Int (y lo mismo para not y if_then_else_ cuando a ~ Bool ).

ScopedTypeVariables

ScopedTypeVariables permite referirse a tipos cuantificados universalmente dentro de una declaración. Para ser más explícitos:

import Data.Monoid

foo :: forall a b c. (Monoid b, Monoid c) => (a, b, c) -> (b, c) -> (a, b, c)
foo (a, b, c) (b', c') = (a :: a, b'', c'')
    where (b'', c'') = (b <> b', c <> c') :: (b, c)

Lo importante es que podemos utilizar a , b y c para indicar al compilador en subexpresiones de la declaración (la tupla en el where cláusula y el primero a en el resultado final). En la práctica, ScopedTypeVariables ayuda a escribir funciones complejas como una suma de partes, lo que permite al programador agregar firmas de tipo a valores intermedios que no tienen tipos concretos.

Sinónimo de patrones

Los sinónimos de patrón son abstracciones de patrones similares a cómo las funciones son abstracciones de expresiones.

Para este ejemplo, veamos la interfaz que expone Data.Sequence y veamos cómo se puede mejorar con los sinónimos de patrón. El tipo Seq es un tipo de datos que, internamente, utiliza una representación complicada para lograr una buena complejidad asintótica para varias operaciones, especialmente para O (1) (des) y un (des) snocing.

Pero esta representación es difícil de manejar y algunas de sus invariantes no pueden expresarse en el sistema de tipos de Haskell. Debido a esto, el tipo Seq está expuesto a los usuarios como un tipo abstracto, junto con las funciones de constructor y de acceso que conservan invariantes, entre ellas:

empty :: Seq a

(<|) :: a -> Seq a -> Seq a
data ViewL a = EmptyL | a :< (Seq a)
viewl :: Seq a -> ViewL a

(|>) :: Seq a -> a -> Seq a 
data ViewR a = EmptyR | (Seq a) :> a 
viewr :: Seq a -> ViewR a

Pero usar esta interfaz puede ser un poco engorroso:

uncons :: Seq a -> Maybe (a, Seq a)
uncons xs = case viewl xs of
    x :< xs' -> Just (x, xs')
    EmptyL -> Nothing

Podemos usar patrones de vista para limpiarlo un poco:

{-# LANGUAGE ViewPatterns #-}

uncons :: Seq a -> Maybe (a, Seq a)
uncons (viewl -> x :< xs) = Just (x, xs)
uncons _ = Nothing

Usando la extensión de lenguaje PatternSynonyms , podemos proporcionar una interfaz aún más agradable al permitir que la coincidencia de patrones pretenda que tenemos una lista de contras o de snoc:

{-# LANGUAGE PatternSynonyms #-}
import Data.Sequence (Seq)
import qualified Data.Sequence as Seq

pattern Empty :: Seq a
pattern Empty <- (Seq.viewl -> Seq.EmptyL)

pattern (:<) :: a -> Seq a -> Seq a
pattern x :< xs <- (Seq.viewl -> x Seq.:< xs)

pattern (:>) :: Seq a -> a -> Seq a
pattern xs :> x <- (Seq.viewr -> xs Seq.:> x)

Esto nos permite escribir uncons en un estilo muy natural:

uncons :: Seq a -> Maybe (a, Seq a)
uncons (x :< xs) = Just (x, xs)
uncons _ = Nothing

RecordWildCards

Ver RecordWildCards



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