tuplas reservadas programar peta para palabras opciones hacer funciones español entornos descargar desarrollo como haskell

programar - palabras reservadas haskell



Funciones ocultas de Haskell (25)

¿Cuáles son las características menos conocidas pero útiles del lenguaje de programación Haskell? (Entiendo que el lenguaje en sí mismo es menos conocido, pero trabaje conmigo. Incluso las explicaciones de las cosas simples en Haskell, como definir la secuencia de Fibonacci con una línea de código, recibirán un voto favorable para mí).

  • Intenta limitar las respuestas al núcleo de Haskell
  • Una característica por respuesta
  • Dé un ejemplo y una breve descripción de la característica, no solo un enlace a la documentación
  • Etiquete la función usando el título en negrita como primera línea

Tipos de datos algebraicos generalizados. Aquí hay un intérprete de ejemplo donde el sistema de tipos le permite cubrir todos los casos:

{-# LANGUAGE GADTs #-} module Exp where data Exp a where Num :: (Num a) => a -> Exp a Bool :: Bool -> Exp Bool Plus :: (Num a) => Exp a -> Exp a -> Exp a If :: Exp Bool -> Exp a -> Exp a -> Exp a Lt :: (Num a, Ord a) => Exp a -> Exp a -> Exp Bool Lam :: (a -> Exp b) -> Exp (a -> b) -- higher order abstract syntax App :: Exp (a -> b) -> Exp a -> Exp b -- deriving (Show) -- failse eval :: Exp a -> a eval (Num n) = n eval (Bool b) = b eval (Plus e1 e2) = eval e1 + eval e2 eval (If p t f) = eval $ if eval p then t else f eval (Lt e1 e2) = eval e1 < eval e2 eval (Lam body) = /x -> eval $ body x eval (App f a) = eval f $ eval a instance Eq a => Eq (Exp a) where e1 == e2 = eval e1 == eval e2 instance Show (Exp a) where show e = "<exp>" -- very weak show instance instance (Num a) => Num (Exp a) where fromInteger = Num (+) = Plus


let 5 = 6 in ... es Haskell válido.


seq y ($!) solo evalúan lo suficiente como para comprobar que algo no está en el fondo.

El siguiente programa solo imprimirá "allí".

main = print "hi " `seq` print "there"

Para aquellos que no están familiarizados con Haskell, Haskell no es estricto en general, lo que significa que un argumento para una función solo se evalúa si es necesario.

Por ejemplo, lo siguiente imprime "ignorado" y termina con éxito.

main = foo (error "explode!") where foo _ = print "ignored"

Se sabe que seq cambia ese comportamiento evaluando hacia abajo si su primer argumento es inferior.

Por ejemplo:

main = error "first" `seq` print "impossible to print"

... o lo que es lo mismo, sin infijo ...

main = seq (error "first") (print "impossible to print")

... explotará con un error en "primero". Nunca se imprimirá "imposible de imprimir".

Por lo tanto, puede ser un poco sorprendente que aunque seq sea ​​estricto, no evalúe algo de la forma en que evalúan los lenguajes ansiosos. En particular, no intentará forzar todos los enteros positivos en el siguiente programa. En cambio, comprobará que [1..] no esté en el fondo (que se puede encontrar inmediatamente), imprima "listo" y salga.

main = [1..] `seq` print "done"


Bonitos guardias

Prelude define lo otherwise = True , haciendo que las condiciones de guardia completas se lean de manera muy natural.

fac n | n < 1 = 1 | otherwise = n * fac (n-1)


Coincidencia de patrones mejorada

  • Patrones perezosos
  • Patrones irrefutables

    let ~(Just x) = someExpression

Ver coincidencia de patrones


Comentarios multiline encajables .

{- inside a comment, {- inside another comment, -} still commented! -}


Composición de la función legible

Prelude define (.) Como composición de función matemática; es decir, g . f Primero aplica f , luego aplica g al resultado.

Si import Control.Arrow , los siguientes son equivalentes:

g . f f >>> g

Control.Arrow proporciona una instance Arrow (->) , y esto es bueno para las personas que no les gusta leer la aplicación de funciones al revés.


Comprensión de la lista paralela

(Característica GHC especial)

fibs = 0 : 1 : [ a + b | a <- fibs | b <- tail fibs ]


Diseño opcional

Puede usar llaves explícitas y puntos y comas en lugar de espacios en blanco (también conocido como diseño) para delimitar bloques.

let { x = 40; y = 2 } in x + y

... o equivalente...

let { x = 40; y = 2 } in x + y

... en lugar de ...

let x = 40 y = 2 in x + y

Debido a que no se requiere diseño, los programas de Haskell pueden ser producidos directamente por otros programas.


Enumeraciones de estilo C

La combinación de coincidencia de patrones de nivel superior y secuencias aritméticas nos da una forma práctica de definir valores consecutivos:

foo : bar : baz : _ = [100 ..] -- foo = 100, bar = 101, baz = 102


Enumeraciones

Cualquier tipo que sea una instancia de Enum puede usarse en una secuencia aritmética, no solo en números:

alphabet :: String alphabet = [''A'' .. ''Z'']

Incluyendo sus propios tipos de datos, solo se derivan de Enum para obtener una implementación predeterminada:

data MyEnum = A | B | C deriving(Eq, Show, Enum) main = do print $ [A ..] -- prints "[A,B,C]" print $ map fromEnum [A ..] -- prints "[0,1,2]"


Especificación flexible de las importaciones y exportaciones de módulos

Importar y exportar es bueno.

module Foo (module Bar, blah) -- this is module Foo, export everything that Bar expored, plus blah import qualified Some.Long.Name as Short import Some.Long.Name (name) -- can import multiple times, with different options import Baz hiding (blah) -- import everything from Baz, except something named ''blah''


Estructuras de control definidas por el usuario

Haskell no tiene un operador ternario taquigráfico. El if - then - else incorporado es siempre ternario, y es una expresión (los lenguajes imperativos tienden a tener ?: = Expresión, if = enunciado). Si quieres, sin embargo,

True ? x = const x False ? _ = id

definirá (?) para ser el operador ternario:

(a ? b $ c) == (if a then b else c)

Tendría que recurrir a macros en la mayoría de los demás lenguajes para definir sus propios operadores lógicos en cortocircuito, pero Haskell es un lenguaje totalmente vago, por lo que simplemente funciona.

-- prints "I''m alive! :)" main = True ? putStrLn "I''m alive! :)" $ error "I''m dead :("


Evitando paréntesis

Las funciones (.) Y ($) en Prelude tienen una solución muy conveniente, lo que le permite evitar paréntesis en muchos lugares. Los siguientes son equivalentes:

f (g (h x)) f $ g $ h x f . g $ h x f . g . h $ x

flip también ayuda, los siguientes son equivalentes:

map (/a -> {- some long expression -}) list flip map list $ /a -> {- some long expression -}


Fixity del operador

Puede usar las palabras clave infix, infixl o infixr para definir la asociatividad y la precedencia de los operadores. Ejemplo tomado de la reference :

main = print (1 +++ 2 *** 3) infixr 6 +++ infixr 7 ***,/// (+++) :: Int -> Int -> Int a +++ b = a + 2*b (***) :: Int -> Int -> Int a *** b = a - 4*b (///) :: Int -> Int -> Int a /// b = 2*a - 3*b Output: -19

El número (0 a 9) después del infijo le permite definir la precedencia del operador, siendo 9 el más fuerte. Infix no significa asociatividad, mientras que infixl asocia a la izquierda e infixr asocia a la derecha.

Esto le permite definir operadores complejos para realizar operaciones de alto nivel escritas como expresiones simples.

Tenga en cuenta que también puede usar funciones binarias como operadores si las coloca entre palos de retroceso:

main = print (a `foo` b) foo :: Int -> Int -> Int foo a b = a + b

Y como tal, también puede definir la precedencia para ellos:

infixr 4 `foo`


Hoogle

Hoogle es tu amigo. Lo admito, no es parte del "núcleo", así que cabal install hoogle

Ahora ya sabes cómo "si estás buscando una función de orden superior, ya está allí" ( comentario de ephemient ). ¿Pero cómo encuentras esa función? ¡Con hoogle!

$ hoogle "Num a => [a] -> a" Prelude product :: Num a => [a] -> a Prelude sum :: Num a => [a] -> a $ hoogle "[Maybe a] -> [a]" Data.Maybe catMaybes :: [Maybe a] -> [a] $ hoogle "Monad m => [m a] -> m [a]" Prelude sequence :: Monad m => [m a] -> m [a] $ hoogle "[a] -> [b] -> (a -> b -> c) -> [c]" Prelude zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]

El programador de hoogle-google no puede escribir sus programas en papel por sí mismo de la misma manera que lo hace con la ayuda de la computadora. Pero él y la máquina juntos son un forzado no * a tener en cuenta.

Por cierto, si te gustaba hoogle, asegúrate de echar un vistazo a Hlint!


Listas infinitas

Como mencionó fibonacci, hay una manera muy elegante de generar números de fibonacci de una lista infinita como esta:

fib@(1:tfib) = 1 : 1 : [ a+b | (a,b) <- zip fib tfib ]

El operador @ le permite usar la coincidencia de patrones en la estructura 1: tfib mientras sigue refiriéndose al patrón completo como fib.

Tenga en cuenta que la lista de comprensión entra en una recursión infinita, generando una lista infinita. Sin embargo, puede solicitar elementos o utilizarlos, siempre que solicite una cantidad limitada:

take 10 fib

También puede aplicar una operación a todos los elementos antes de solicitarlos:

take 10 (map (/x -> x+1) fib)

Esto es gracias a la evaluación perezosa de parámetros y listas de Haskell.


Mónadas

No están tan escondidos, pero están simplemente en todas partes, incluso donde no pienses en ellos (Lists, Maybe-Types) ...


Mi cerebro acaba de explotar

Si intenta compilar este código:

{-# LANGUAGE ExistentialQuantification #-} data Foo = forall a. Foo a ignorefoo f = 1 where Foo a = f

Obtendrá este mensaje de error:

$ ghc Foo.hs Foo.hs:3:22: My brain just exploded. I can''t handle pattern bindings for existentially-quantified constructors. Instead, use a case-expression, or do-notation, to unpack the constructor. In the binding group for Foo a In a pattern binding: Foo a = f In the definition of `ignorefoo'': ignorefoo f = 1 where Foo a = f


Patrones en enlaces de nivel superior

five :: Int Just five = Just 5 a, b, c :: Char [a,b,c] = "abc"

¡Cuan genial es eso! Le ahorra esa llamada de fromJust y head vez en cuando.


Razonamiento ecuacional

Haskell, al ser puramente funcional, le permite leer un signo igual como un signo real igual (en ausencia de patrones no superpuestos).

Esto le permite sustituir definiciones directamente en el código, y en términos de optimización le da mucho margen de maniobra al compilador sobre cuándo sucede algo.

Un buen ejemplo de esta forma de razonamiento se puede encontrar aquí:

http://www.haskell.org/pipermail/haskell-cafe/2009-March/058603.html

Esto también se manifiesta muy bien en forma de leyes o REGLAS pragmas esperados para los miembros válidos de una instancia, por ejemplo, las leyes de Mónada:

  1. retornar a >> = f == fa
  2. m >> = return == m
  3. (m >> = f) >> = g == m >> = (/ x -> fx >> = g)

a menudo se puede utilizar para simplificar el código monádico.


Si está buscando una lista o una función de orden superior, ya está allí

Hay muchas funciones de conveniencia y de orden superior en la biblioteca estándar.

-- factorial can be written, using the strict HOF foldl'': fac n = Data.List.foldl'' (*) 1 [1..n] -- there''s a shortcut for that: fac n = product [1..n] -- and it can even be written pointfree: fac = product . enumFromTo 1


Taquigrafía para una operación de lista común

Los siguientes son equivalentes:

concat $ map f list concatMap f list list >>= f

Editar

Dado que se solicitaron más detalles ...

concat :: [[a]] -> [a]

concat toma una lista de listas y las concatena en una sola lista.

map :: (a -> b) -> [a] -> [b]

map mapea una función sobre una lista.

concatMap :: (a -> [b]) -> [a] -> [b]

concatMap es equivalente a (.) concat . map (.) concat . map : asigna una función sobre una lista y concatenan los resultados.

class Monad m where (>>=) :: m a -> (a -> m b) -> m b return :: a -> m a

Una Monad tiene una operación de enlace , que se llama >>= en Haskell (o su equivalente en azucar). List, aka [] , es una Monad . Si sustituimos [] por m en lo anterior:

instance Monad [] where (>>=) :: [a] -> (a -> [b]) -> [b] return :: a -> [a]

¿Qué es lo natural para las operaciones de Monad en una lista? Tenemos que satisfacer las leyes de la mónada,

return a >>= f == f a ma >>= (/a -> return a) == ma (ma >>= f) >>= g == ma >>= (/a -> f a >>= g)

Puede verificar si estas leyes son válidas si usamos la implementación

instance Monad [] where (>>=) = concatMap return = (:[]) return a >>= f == [a] >>= f == concatMap f [a] == f a ma >>= (/a -> return a) == concatMap (/a -> [a]) ma == ma (ma >>= f) >>= g == concatMap g (concatMap f ma) == concatMap (concatMap g . f) ma == ma >>= (/a -> f a >>= g)

Este es, de hecho, el comportamiento de Monad [] . Como una demostración,

double x = [x,x] main = do print $ map double [1,2,3] -- [[1,1],[2,2],[3,3]] print . concat $ map double [1,2,3] -- [1,1,2,2,3,3] print $ concatMap double [1,2,3] -- [1,1,2,2,3,3] print $ [1,2,3] >>= double -- [1,1,2,2,3,3]


Teoremas libres

Phil Wadler nos presentó la noción de un teorema libre y desde entonces hemos estado abusando de ellos en Haskell.

Estos maravillosos artefactos de los sistemas tipo Hindley-Milner ayudan con el razonamiento ecuacional al usar parametricidad para informarle sobre lo que una función no hará.

Por ejemplo, hay dos leyes que cada instancia de Functor debería cumplir:

  1. forall f g. fmap f. fmap g = fmap (f. g)
  2. fmap id = id

Pero, el teorema libre nos dice que no tenemos que molestarnos en probar el primero, ¡pero dado el segundo, es ''gratis'' solo por la firma del tipo!

fmap :: Functor f => (a -> b) -> f a -> f b

Debe ser un poco cuidadoso con la pereza, pero esto está parcialmente cubierto en el documento original, y en el documento más reciente de Janis Voigtlaender sobre teoremas libres en presencia de las seq .


pereza

La holgazanería omnipresente significa que puedes hacer cosas como definir

fibs = 1 : 1 : zipWith (+) fibs (tail fibs)

Pero también nos proporciona muchos más beneficios sutiles en términos de sintaxis y razonamiento.

Por ejemplo, debido a la rigurosidad, ML tiene que lidiar con la restricción de valores , y tiene mucho cuidado de rastrear los enlaces circulares, pero en Haskell, podemos permitir que cada let sea recursivo y no tengamos necesidad de distinguir entre val y fun . Esto elimina una gran verruga sintáctica del lenguaje.

Esto da lugar indirectamente a nuestra encantadora cláusula where , porque podemos mover con seguridad los cálculos que pueden o no ser utilizados fuera del flujo de control principal y permitir que la pereza se ocupe de compartir los resultados.

Podemos reemplazar (casi) todas las funciones de estilo ML que necesitan tomar () y devolver un valor, con solo un cálculo lento del valor. Hay razones para evitar hacerlo de vez en cuando para evitar pérdidas de espacio con los CAFs , pero estos casos son raros.

Finalmente, permite una reducción de eta ilimitada ( /x -> fx puede reemplazarse por f). Esto hace que la programación orientada a combinador para cosas como los combinadores de analizador sea mucho más agradable que trabajar con construcciones similares en un lenguaje estricto.

Esto lo ayuda a razonar acerca de los programas en un estilo sin puntos, o al reescribirlos en un estilo sin puntos y reduce el ruido de los argumentos.