import Prelude hiding (map,Nothing,Just,Maybe, (>>=),Functor,Applicative)
data Maybe a = Just a | Nothing
safeHead :: [a] -> Maybe a
safeHead [] = Nothing
safeHead (x:_) = Just x
Comme souvent en Haskell, il est intéressant de raisonner en terme de fonctions. Ainsi, il est utile de voir une fonction retournant Maybe a comme un calcul pouvant échouer. Essayons d’écrire une fonction ajoutant 1 au premier élément d’une liste d’entiers, s’il existe:
addOne :: [Int] -> Maybe Int
addOne l = case safeHead l of
Nothing -> Nothing
Just x -> Just (x + 1)
maybeMap :: (a -> b) -> Maybe a -> Maybe b
maybeMap f Nothing = Nothing
maybeMap f (Just x) = Just (f x)
addOne' l = maybeMap (+1) (safeHead l)
maybeMap permet d’appliquer une fonction réussissant toujours au résultat d’une fonction pouvant échouer. Considérons maintenant deux listes et supposons que l’on veuille faire la somme de leur têtes si elles sont non vide. Une première implémentation serait la suivante :
safeAdd :: [Int] -> [Int] -> Maybe Int
safeAdd lX lY = case (headX,headY) of
(Just x,Just y) -> Just (x + y)
otherwise -> Nothing
where headX = safeHead lX
headY = safeHead lY
{- Ou en utilisant maybeMap -}
safeAdd' [] lY = Nothing
safeadd' lX lY = case safeHead lX of
Just x -> maybeMap (+x) (safeHead lY)
otherwise -> Nothing
Nous sommes donc obligés d’établir un pattern matching sur les résultats des deux appels à safeHead pour récupérer les valeurs présentes dans les Just pour en faire la somme que l’on réencapsule dans un Just. Généralisons cela par la fonction maybeApply, identique à maybeMap à la diférence que son premier argument est encapsulé dans Maybe.
maybeApply :: Maybe (a -> b) -> Maybe a -> Maybe b
maybeApply Nothing _ = Nothing
maybeApply _ Nothing = Nothing
maybeApply (Just f) (Just x) = Just (f x)
{- Ou en utilisant maybeMap -}
maybeApply' Nothing _ = Nothing
maybeApply' (Just f) maybeX = maybeMap f maybeX
safeAdd'' :: [Int] -> [Int] -> Maybe Int
safeAdd'' lX lY = maybeApply f (safeHead lY)
where f :: Maybe (Int -> Int)
f = maybeMap (+) (safeHead lX)
negativeHead :: [Int] -> Maybe Int
negativeHead [] = Nothing
negativeHead (x:_)
| x < 0 = Just x
| otherwise = Nothing
(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
Nothing >>= _ = Nothing
(Just x) >>= f = f x
negativeVal :: Int -> Maybe Int
negativeVal x
| x < 0 = Just x
| otherwise = Nothing
negativeHead' :: [Int] -> Maybe Int
negativeHead' l = safeHead l >>= negativeVal
Sublime non ?
join :: Maybe (Maybe a) -> Maybe a
join Nothing = Nothing
join (Just x) = x
Fournir une implémentation de (>>=) en utilisant join.
Considérons un type f :: * -> * (ou type “conteneur”, comme Maybe). Il est intéressant de voir f comme un type de calcul. Nous pouvons, par exemple, imaginer les types de calcul suivant :
Il en existe beaucoup d’autres, et nous en étudierons certains plus en détails dans une autre section. Dans les classes de types suivantes, il est utile de remplacer f par Maybe, et ainsi voir le parallèle avec le reste de la section.
Le type f est un foncteur, s’il est possible d’appliquer une fonction pure sur son contenu.class Functor f where
fmap :: (a -> b) -> f a -> f b
La fonction fmap généralise la fonction map à des types autres que les listes.
Un foncteur est dit applicatif si est possible d’appliquer une fonction contenue cette fois dans un foncteur. Il est également nécessaire de pouvoir construire un foncteur à partir de n’importe quelle valeur.class (Functor f) => Applicative f where
(<*>) :: f (a -> b) -> f a -> f b
pure :: a -> f a
L’opérateur (<*>) généralise la fonction maybeApply à tous les foncteurs applicatifs.
Une monade permet de chaîner les calculs : il est possible de “sortir” le résultat d’un calcul pour le passer en argument à un autre.class (Applicative f) => Monad f where
(>>=) :: f a -> (a -> f b) -> f b
Les fonctions retournant une liste de valeurs peuvent être vues comme un calcul retournant plusieurs réponses possibles. Écrire les instances de Functor, Applicative et Monad pour ce type de calcul.
Indice : pour implémenter l >>= f, supposer que l’on a plusieurs valeurs possibles (l :: [a]) et une fonction qui, à chacune de ces valeurs possibles, retourne une liste de résultats possibles également (f :: a -> [b]).