Question

Quelles sont les fonctionnalités moins connues mais utiles du langage de programmation Haskell. (Je comprends que le langage lui-même est moins connu, mais travaillez avec moi. Même les explications sur les choses simples en Haskell, telles que la définition de la séquence de Fibonacci avec une ligne de code, seront votées par moi.)

  • Essayez de limiter les réponses au noyau Haskell
  • Une fonctionnalité par réponse
  • Donnez un exemple et une brève description de la fonctionnalité, pas seulement un lien vers la documentation
  • Étiquetez l'entité en utilisant le titre en gras comme première ligne
Était-ce utile?

La solution

Mon cerveau vient d'exploser

Si vous essayez de compiler ce code:

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

Vous obtiendrez ce message d'erreur:

$ 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

Autres conseils

Structures de contrôle définies par l'utilisateur

Haskell n’a pas d’opérateur ternaire abrégé. Le intégré si - , puis - sinon est toujours ternaire et constitue une expression (les langages impératifs ont tendance à avoir ?: = expression, si = instruction). Si vous voulez, cependant,

True ? x = const x
False ? _ = id

définira (?) comme étant l'opérateur ternaire:

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

Vous devrez recourir à des macros dans la plupart des autres langues pour définir vos propres opérateurs logiques de court-circuit, mais Haskell est un langage totalement paresseux, donc cela fonctionne.

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

Hoogle

Hoogle est votre ami. J'admets que cela ne fait pas partie du "noyau", donc installer la cabale hoogle

Maintenant, vous savez comment "si vous recherchez une fonction d'ordre supérieur, elle est déjà là" ( commentaire de l'éphémient ). Mais comment trouvez-vous cette fonction? Avec 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]

Le programmeur hoogle-google n’est pas en mesure d’écrire ses programmes sur papier tout comme il le fait avec l’aide de l’ordinateur. Mais lui et la machine ensemble sont des éléments incontournables.

Btw, si vous aimez Hoogle, assurez-vous de vérifier hlint!

Théorèmes libres

Phil Wadler nous a présenté la notion de théorème libre et depuis nous en abusons à Haskell.

Ces merveilleux artefacts de systèmes de type Hindley-Milner aident au raisonnement équationnel en utilisant la paramétrie pour vous dire ce qu'une fonction ne fera pas .

Par exemple, chaque instance de Functor doit respecter deux lois:

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

Mais, le théorème libre nous dit que nous n’avons pas besoin de prouver le premier, mais compte tenu du second, il vient pour «gratuit», juste à partir de la signature de type!

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

Vous devez être un peu prudent avec la paresse, mais cela est partiellement couvert dans le document d'origine et dans le document article plus récent sur les théorèmes libres en présence de seq .

Raccourci pour une opération de liste commune

Les éléments suivants sont équivalents:

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

Modifier

Depuis plus de détails ont été demandés ...

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

concat prend une liste de listes et les concatène en une seule liste.

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

map mappe une fonction sur une liste.

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

concatMap est équivalent à (.) concat. map : mappe une fonction sur une liste et concatène les résultats.

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

Un Monad a une opération bind , appelée > > = dans Haskell (ou son sucré / code> -équivalent). List, autrement dit [] , est un Monad . Si nous substituons [] à m ci-dessus:

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

Qu'est-ce qui est naturel pour les opérations Monad à faire sur une liste? Nous devons satisfaire les lois de la monade,

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

Vous pouvez vérifier que ces lois sont respectées si nous utilisons la mise en œuvre

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)

C’est en fait le comportement de Monad [] . À titre de démonstration,

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]

Commentaires multilignes imbriquables .

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

Types de données algébriques généralisées. Voici un exemple d'interprète où le système de types vous permet de couvrir tous les cas:

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

Modèles dans les liaisons de niveau supérieur

five :: Int
Just five = Just 5

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

C'est cool ça! Vous enregistre cet appel à fromJust et à head de temps en temps.

Mise en forme facultative

Vous pouvez utiliser des accolades et des points-virgules explicites à la place des espaces (disposition) pour délimiter les blocs.

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

... ou de manière équivalente ...

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

... au lieu de ...

let x = 40
    y = 2
 in x + y

La mise en page n'étant pas obligatoire, les programmes Haskell peuvent être directement produits par d'autres programmes.

seq et ($!) évalue seulement suffisamment pour vérifier que quelque chose ne va pas au fond.

Le programme suivant n’imprimera que "là-bas".

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

Pour ceux qui ne connaissent pas Haskell, Haskell est généralement non strict, ce qui signifie qu'un argument pour une fonction n'est évalué que s'il est nécessaire.

Par exemple, les impressions suivantes "ignorées" et se termine avec succès.

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

seq est connu pour changer ce comportement en évaluant en bas si son premier argument est en bas.

Par exemple:

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

... ou de façon équivalente, sans infixe ...

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

... va exploser avec une erreur sur "premier". Il ne sera jamais imprimé "impossible à imprimer".

Il peut donc être un peu étonnant que même si seq est strict, il n'évaluera pas quelque chose de la même manière que les langages avides. En particulier, il ne tentera pas de forcer tous les entiers positifs dans le programme suivant. Au lieu de cela, il vérifiera que [1 ..] n'est pas en bas (ce qui peut être trouvé immédiatement), affiche "terminé" et quitte.

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

Résistance des opérateurs

Vous pouvez utiliser les mots-clés infix, infixl ou infixr pour définir des opérateurs. associativité et priorité. Exemple tiré de la référence :

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

Le nombre (0 à 9) après l'infixe vous permet de définir la priorité de l'opérateur, 9 étant le plus fort. Infix signifie pas d’associativité, alors que infixl associe gauche et infixr associe droite.

Ceci vous permet de définir des opérateurs complexes pour effectuer des opérations de haut niveau écrites sous forme d'expressions simples.

Notez que vous pouvez également utiliser des fonctions binaires en tant qu'opérateurs si vous les placez entre les jeux de cartes:

main = print (a `foo` b)

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

Et en tant que tel, vous pouvez également définir une priorité pour eux:

infixr 4 `foo`

Éviter les parenthèses

Les fonctions (.) et ($) de Prelude ont des fixités très pratiques, vous permettant d’éviter les parenthèses dans de nombreux endroits. Les éléments suivants sont équivalents:

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

flip aide aussi, les éléments suivants sont équivalents:

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

Jolis gardes

Prelude définit sinon = True , ce qui rend les conditions de protection complètes lues très naturellement.

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

Énumérations de style C

La combinaison de correspondance de modèles de niveau supérieur et de séquences arithmétiques nous offre un moyen pratique de définir des valeurs consécutives:

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

Composition de fonction lisible

Prelude définit (.) comme étant une composition de fonction mathématique; c'est-à-dire g. f applique d'abord f , puis applique g au résultat.

Si vous importez Control.Arrow , les éléments suivants sont équivalents:

g . f
f >>> g

Control.Arrow fournit une instance de flèche (- >) , ce qui est agréable pour les personnes qui n'aiment pas lire les applications de fonctions à l'envers.

laissez 5 = 6 dans ... est valide Haskell.

Listes infinies

Depuis que vous avez mentionné fibonacci, il existe un moyen très élégant de générer numéros de fibonacci d'une liste infinie comme ceci:

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

L'opérateur @ vous permet d'utiliser le filtrage par motif sur la structure 1: tfib tout en faisant référence au motif entier en tant que fib.

Notez que la liste de compréhension entre une récursion infinie, générant une liste infinie. Cependant, vous pouvez lui demander des éléments ou les exploiter, à condition de demander une quantité finie:

take 10 fib

Vous pouvez également appliquer une opération à tous les éléments avant de les demander:

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

C’est grâce à l’évaluation paresseuse des paramètres et des listes de Haskell.

Spécification flexible des importations et des exportations de modules

Importer et exporter, c'est bien.

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'

Si vous recherchez une liste ou une fonction d'ordre supérieur, c'est déjà là

Il y a tellement de fonctions pratiques et d'ordre supérieur dans la bibliothèque standard.

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

Raisonnement d'équation

Haskell, être purement fonctionnel vous permet de lire un signe égal en tant que signe réel égal (en l’absence de motifs ne se chevauchant pas).

Cela vous permet de substituer les définitions directement dans le code et, en termes d’optimisation, donne une marge de manœuvre importante au compilateur en ce qui concerne le moment où cela se produit.

Vous trouverez un bon exemple de cette forme de raisonnement ici:

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

Cela se manifeste aussi bien sous la forme de lois ou de pragmas RULES attendus pour les membres valides d'une instance, par exemple les lois Monad:

  1. revenir a > > = f == f a
  2. m > > = = return == m
  3. (m > > = f) > > = g == m > > = (\ x - > f x > > = g)

peut souvent être utilisé pour simplifier le code monadique.

Paresse

La paresse omniprésente signifie que vous pouvez faire des choses comme définir

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

Mais cela nous apporte également de nombreux avantages plus subtils en termes de syntaxe et de raisonnement.

Par exemple, pour des raisons de rigueur, ML doit faire face à restriction de valeur , et fait très attention au suivi des liaisons de let circulaires, mais dans Haskell, nous pouvons laisser chaque let être récursif et ne pas avoir besoin de distinguer val et fun . Cela supprime une verrue syntaxique majeure du langage.

Cela donne indirectement lieu à notre belle clause where , car nous pouvons déplacer en toute sécurité des calculs qui peuvent ou non être utilisés en dehors du flux de contrôle principal et laisser la paresse gérer le partage des résultats.

Nous pouvons remplacer (presque) toutes les fonctions de style ML devant prendre () et renvoyer une valeur, par un simple calcul paresseux de la valeur. Il y a des raisons de ne pas le faire de temps en temps pour éviter de perdre de l'espace avec les CAF , mais les cas sont rares.

Enfin, cela permet une eta-réduction sans restriction ( \ x - > f x peut être remplacé par f). Cela rend la programmation orientée combinateur pour des choses comme les combinateurs d’analyseur beaucoup plus agréable que de travailler avec des constructions similaires dans un langage strict.

Cela vous aide à raisonner sur des programmes dans un style sans points ou à les réécrire dans un style sans points et à réduire le bruit d'argument.

Compréhension des listes parallèles

(fonction spéciale GHC)

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

Correspondance améliorée entre les modèles

  • Modèles paresseux
  • Modèles irréfutables

    let ~(Just x) = someExpression
    

Voir correspondance de modèle

Énumérations

Tout type qui est une instance de Enum peut être utilisé dans une séquence arithmétique, pas seulement des nombres:

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

En incluant vos propres types de données, dérivez d’Enum pour obtenir une implémentation par défaut:

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

Monades

Ils ne sont pas si cachés que ça, mais ils sont simplement partout, même quand on ne pense pas à eux (Listes, Maybe-Types) ...

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top