Pergunta

Quais são as características menos conhecidas, mas útil da linguagem de programação Haskell. (I compreender a linguagem em si é menos conhecido, mas o trabalho comigo. Explicações Mesmo das coisas simples em Haskell, como definir a seqüência de Fibonacci com uma linha de código, vai ficar upvoted por mim.)

  • Tente respostas limite para o núcleo Haskell
  • Uma característica por resposta
  • Dê um exemplo e uma breve descrição do recurso, não apenas um link para a documentação
  • Rotular o recurso usando título ousada como a primeira linha
Foi útil?

Solução

O meu cérebro simplesmente explodiu

Se você tentar compilar este código:

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

Você receberá esta mensagem de erro:

$ 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

Outras dicas

estruturas de controle definidas pelo usuário

Haskell tem nenhum operador taquigrafia ternário. A built-in if-then-else é sempre ternário, e é uma expressão (linguagens imperativas tendem a ter ?: = expressão, if = declaração). Se você quiser, porém,

True ? x = const x
False ? _ = id

definirá (?) ser o operador ternário:

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

Você teria que recorrer a macros na maioria das outras línguas para definir seus próprios operadores lógicos-circuito curto, mas Haskell é uma linguagem totalmente preguiçoso, então ele simplesmente funciona.

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

Hoogle

Hoogle é seu amigo. Eu admito, não é parte do "core", de modo cabal install hoogle

Agora você sabe como "se você está procurando uma função de ordem superior, ele já está lá" ( o ephemient comentário ). Mas como você encontra essa função? Com 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]

O programador Hoogle-google não é capaz de escrever seus programas em papel pelo próprio da mesma maneira que ele faz com a ajuda do computador. Mas ele e a máquina em conjunto, são um não forçado * a ser contada com.

Btw, se você gostou Hoogle ser deixe de conferir hlint!

Free Teoremas

Phil Wadler nos apresentou a noção de um livre teorema e estamos abusando-los em Haskell desde então.

Esses artefatos maravilhosas de sistemas do tipo-Milner estilo Hindley ajudar com raciocínio equational usando parametricity para informá-lo sobre o que é uma função não fazer.

Por exemplo, existem duas leis que cada instância de Functor deve satisfazer:

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

Mas, o teorema livre nos diz que não precisa se preocupar provando o primeiro, mas dado o segundo se trata de 'livre' apenas a partir da assinatura de tipo!

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

Você precisa ser um pouco cuidadoso com preguiça, mas isso é parcialmente coberto no artigo original, e em Janis Voigtlaender de artigo mais recente sobre teoremas livres na presença de seq.

Taquigrafia para uma operação de lista comum

A seguir são equivalentes:

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

Editar

Uma vez que mais detalhes foram solicitados ...

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

concat recebe uma lista de listas e concatena-los em uma única lista.

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

map mapeia uma função mais de uma lista.

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

concatMap é equivalente a (.) concat . map:. Mapear uma função mais de uma lista, e concatenar os resultados

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

Um Monad tem um ligamento operação, o que é chamado >>= em Haskell (ou sua adoçada do-equivalente). Lista, aka [], é um Monad. Se substituirmos [] para m no exemplo acima:

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

Qual é a coisa natural para as operações Monad fazer em uma lista? Temos de satisfazer as leis Mônada

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

Você pode verificar que estas leis segurar se usarmos a implementação

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)

Esta é, de fato, o comportamento de Monad []. Como demonstração,

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]

comentários várias linhas Nestable .

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

generalizadas tipos de dados algébricos. Aqui está um exemplo intérprete, onde o sistema de tipo permite a todos os 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

Patterns em ligações de nível superior

five :: Int
Just five = Just 5

a, b, c :: Char
[a,b,c] = "abc"

Como isso é legal! Poupa-lhe que chamada para fromJust e head de vez em quando.

Opcional layout

Você pode usar chaves explícitas e ponto e vírgula em vez de espaços em branco (aka disposição) para blocos delimitam.

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

... ou equivalentemente ...

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

... em vez de ...

let x = 40
    y = 2
 in x + y

Porque o layout não é necessário, programas Haskell pode ser diretamente produzida por outros programas.

seq e ($!) avaliam apenas o suficiente para verificação que algo não está baixo.

O programa seguinte só vai imprimir "lá".

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

Para aqueles não familiarizados com Haskell, Haskell não é rigorosa em geral, o que significa que um argumento para uma função é avaliada somente se for necessário.

Por exemplo, as seguintes impressões "ignorado" e termina com sucesso.

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

seq é conhecido por mudar esse comportamento através da avaliação para baixo se o seu primeiro argumento é inferior.

Por exemplo:

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

... ou equivalentemente, sem infix ...

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

... vai explodir com um erro em "primeira". Isso nunca vai imprimir "impossível imprimir".

Assim, pode ser um pouco surpreendente que, apesar de seq é rigoroso, não vai avaliar algo da maneira línguas ansiosas avaliar. Em particular, ele não vai tentar forçar todos os inteiros positivos no programa seguinte. Em vez disso, ele irá verificar que [1..] não é inferior (que pode ser encontrado imediatamente), imprima "feito", e sair.

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

Operador Fixidez

Você pode usar o infix, infixl ou infixr palavras-chave para definir operadores associatividade e precedência. Exemplo retirado do referência :

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

O número (0 a 9) após o infix permite definir a precedência do operador, sendo 9 o mais forte. Infix significa que não associatividade, enquanto associados infixl esquerda e associados infixr direita.

Isso permite que você definir operadores complexos para fazer operações de alto nível escrito como expressões simples.

Note que você também pode usar as funções binárias como operadores se você colocá-los entre acentos graves:

main = print (a `foo` b)

foo :: Int -> Int -> Int
foo a b = a + b

E, como tal, também é possível definir prioridade para eles:

infixr 4 `foo`

Evitar parênteses

As funções (.) e ($) em Prelude tem fixities muito conveniente, permitindo-lhe evitar parênteses em muitos lugares. A seguir são equivalentes:

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

flip ajuda também, a seguir são equivalentes:

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

guardas bonitas

Prelude define otherwise = True, fazendo condições de guarda completos ler muito naturalmente.

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

C-Style Enumerations

A combinação de sequências de nível superior que coincidirem e aritméticas nos dá uma maneira prática para definir valores consecutivos:

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

composição de função legível

define Prelude (.) ser composição função matemática; isto é, g . f aplica primeiro f, em seguida, aplica g ao resultado.

Se você import Control.Arrow, a seguir são equivalentes:

g . f
f >>> g

Control.Arrow fornece uma instance Arrow (->), e isso é bom para as pessoas que não gostam de ler função trás da aplicação.

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

listas infinitas

Uma vez que você mencionou Fibonacci, há uma maneira muito elegante de Fibonacci números a partir de uma lista infinita como este:

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

O operador @ permite que você use a correspondência de padrões no 1: estrutura tfib enquanto ainda referindo-se a todo o padrão como lorota.

Note que a compreensão da lista entra uma recursão infinita, gerando uma lista infinita. No entanto, você pode solicitar elementos ou operá-los, contanto que você solicitar uma quantidade finita:

take 10 fib

Você também pode aplicar uma operação a todos os elementos antes de solicitar-lhes:

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

Esta é graças a avaliação preguiçosa de Haskell de parâmetros e listas.

especificação flexível das importações do módulo e exportações

Importar e exportar é bom.

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'

Se você está procurando uma lista ou função de ordem superior, ele já está lá

Há sooo muitas funções de conveniência e de maior ordem na biblioteca padrão.

-- 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

equational Raciocínio

Haskell, sendo puramente funcional permite que você leia um sinal de igual como um verdadeiro sinal de igual (na ausência de padrões não sobrepostas).

Isso permite que você definições substitutos diretamente no código, e em termos de otimização dá muita margem de manobra para o compilador sobre quando as coisas acontecem.

Um bom exemplo deste tipo de raciocínio pode ser encontrada aqui:

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

Isso também se manifesta muito bem na forma de leis ou regras pragmas esperado para membros válidos de uma instância, por exemplo, as leis Mônada:

  1. returrn a >> = f == f a
  2. m >> = retorno == m
  3. (m >> = F) >> = g == m >> = (\ x -> f x = >> g)

muitas vezes pode ser usado para simplificar o código monádico.

Preguiça

meios preguiça ubíquos que você pode fazer coisas como definir

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

Mas também nos fornece uma série de benefícios mais sutis em termos de sintaxe e raciocínio.

Por exemplo, devido ao rigor ML tem de lidar com o restrição de valor, e é muito cuidadoso para rastrear ligações let circulares, mas em Haskell, podemos deixar cada vamos ser recursiva e não têm necessidade de distinguir entre val e fun. Isso remove uma grande verruga sintática da língua.

Esta indiretamente dá origem a nossa cláusula where adorável, porque podemos mover com segurança cálculos que podem ou não podem ser utilizados fora do fluxo de controle principal e deixe negócio preguiça com partilhando os resultados.

Podemos substituir (quase) todas essas funções estilo ML que precisam tomar () e retorna um valor, com apenas um cálculo preguiçoso do valor. Há razões para evitar fazê-lo de vez em quando para evitar vazamento de espaço com FAC , mas tais casos são raros.

Finalmente, permite redução de eta irrestrita (\x -> f x pode ser substituído por f). Isso faz com que Combinator Programação Orientada para coisas como combinadores analisador muito mais agradável do que trabalhar com construções semelhantes em uma linguagem rigorosa.

Isso ajuda você quando raciocínio sobre programas em estilo free-ponto, ou cerca de reescrevê-los em estilo free-ponto e reduz o ruído argumento.

lista paralela compreensão

(Special GHC-recurso)

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

Avançado correspondência padrão

  • padrões preguiçosos
  • padrões irrefutáveis ??

    let ~(Just x) = someExpression
    

padrão de correspondência

Enumerations

Qualquer tipo que é um exemplo de enum pode ser utilizado numa sequência de aritmética, não apenas os números:

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

Incluindo seus próprios tipos de dados, apenas derivar de Enum para obter uma implementação padrão:

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]"

Monads

Eles não são tão escondido, mas eles são simplesmente em toda parte, mesmo quando você não acha que eles (listas, talvez-Types) ...

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top