Je suis relativement nouveau à Haskell et j'ai commencé à lire "Real World Haskell".
Je suis juste tombé sur le type Peut-être et j'ai une question sur la façon de recevoir la valeur réelle d'un Just 1
par exemple.
J'ai écrit le code suivant:
combine a b c = (eliminate a, eliminate b, eliminate c)
where eliminate (Just a) = a
eliminate Nothing = 0
Cela fonctionne bien si j'utilise:
combine (Just 1) Nothing (Just 2)
Mais si je change, par exemple, 1 en String, cela ne fonctionne pas.
Je pense que je sais pourquoi: parce que eliminate
doit rendre un type, qui est, dans ce cas, un Int
. Mais comment puis-je changer eliminate
pour traiter au moins les chaînes (ou peut-être avec toutes sortes de types)?
[éditer de l'auteur, 6 ans plus tard] Ceci est une réponse inutilement longue, et je ne sais pas pourquoi elle a été acceptée. Utilisez maybe
ou Data.Maybe.fromMaybe
comme suggéré dans la réponse la plus élevée. Ce qui suit est plus une expérience de pensée que des conseils pratiques.
Vous essayez donc de créer une fonction qui fonctionne pour un tas de types différents. C'est le bon moment pour faire un cours. Si vous avez programmé en Java ou C++, une classe dans Haskell est un peu comme une interface dans ces langages.
class Nothingish a where
nada :: a
Cette classe définit une valeur nada
, qui est censée être l'équivalent de la classe de Nothing
. Maintenant, la partie amusante: créer des instances de cette classe!
instance Nothingish (Maybe a) where
nada = Nothing
Pour une valeur de type Maybe a
, la valeur Nothing-like est, eh bien, Nothing
! Ce sera un exemple étrange dans une minute. Mais avant cela, faisons également des listes une instance de cette classe.
instance Nothingish [a] where
nada = []
Une liste vide est un peu comme rien, non? Ainsi, pour une chaîne (qui est une liste de caractères), elle renverra la chaîne vide, ""
.
Les nombres sont également une mise en œuvre facile. Vous avez déjà indiqué que 0 représente évidemment le "néant" pour les nombres.
instance (Num a) => Nothingish a where
nada = 0
Celui-ci ne fonctionnera pas sauf si vous mettez une ligne spéciale en haut de votre fichier
{-# LANGUAGE FlexibleInstances, UndecidableInstances, OverlappingInstances #-}
Ou lorsque vous le compilez, vous pouvez définir les indicateurs pour ces pragmas de langue. Ne vous inquiétez pas pour eux, ils sont juste magiques qui font fonctionner plus de choses.
Alors maintenant, vous avez cette classe et ses instances ... maintenant, réécrivons simplement votre fonction pour les utiliser!
eliminate :: (Nothingish a) => Maybe a -> a
eliminate (Just a) = a
eliminate Nothing = nada
Remarquez que je n'ai changé que 0
à nada
, et le reste est le même. Essayons-le!
ghci> eliminate (Just 2)
2
ghci> eliminate (Just "foo")
"foo"
ghci> eliminate (Just (Just 3))
Just 3
ghci> eliminate (Just Nothing)
Nothing
ghci> :t eliminate
eliminate :: (Nothingish t) => Maybe t -> t
ghci> eliminate Nothing
error! blah blah blah...**Ambiguous type variable**
A l'air bien pour les valeurs et les trucs. Remarquez que (Just Nothing) se transforme en Nothing, voyez-vous? C'était un exemple étrange, un Peut-être dans un Peut-être. Quoi qu'il en soit ... qu'en est-il de eliminate Nothing
? Eh bien, le type résultant est ambigu. Il ne sait pas à quoi nous nous attendons. Nous devons donc lui dire quel type nous voulons.
ghci> eliminate Nothing :: Int
0
Allez-y et essayez-le pour d'autres types; vous verrez qu'il obtient nada
pour chacun. Alors maintenant, lorsque vous utilisez cette fonction avec votre fonction combine
, vous obtenez ceci:
ghci> let combine a b c = (eliminate a, eliminate b, eliminate c)
ghci> combine (Just 2) (Just "foo") (Just (Just 3))
(2,"foo",Just 3)
ghci> combine (Just 2) Nothing (Just 4)
error! blah blah Ambiguous Type blah blah
Notez que vous devez toujours indiquer le type de votre "Nothing" ou indiquer le type de retour que vous attendez.
ghci> combine (Just 2) (Nothing :: Maybe Int) (Just 4)
(2,0,4)
ghci> combine (Just 2) Nothing (Just 4) :: (Int, Int, Int)
(2,0,4)
Vous pouvez également restreindre les types autorisés par votre fonction en plaçant explicitement sa signature de type dans la source. Cela a du sens si l'utilisation logique de la fonction est qu'elle n'est utilisée qu'avec des paramètres du même type.
combine :: (Nothingish a) => Maybe a -> Maybe a -> Maybe a -> (a,a,a)
combine a b c = (eliminate a, eliminate b, eliminate c)
Maintenant, cela ne fonctionne que si les trois choses Peut-être sont du même type. De cette façon, il en déduira que le Nothing
est du même type que les autres.
ghci> combine (Just 2) Nothing (Just 4)
(2,0,4)
Aucune ambiguïté, yay! Mais maintenant, c'est une erreur de mélanger et assortir, comme nous l'avons fait auparavant.
ghci> combine (Just 2) (Just "foo") (Just (Just 3))
error! blah blah Couldn't match expected type blah blah
blah blah blah against inferred type blah blah
Eh bien, je pense que c'était une réponse suffisamment longue et exagérée. Prendre plaisir.
De la norme Prelude
,
maybe :: b -> (a -> b) -> Maybe a -> b
maybe n _ Nothing = n
maybe _ f (Just x) = f x
Étant donné une valeur par défaut et une fonction, appliquez la fonction à la valeur dans Maybe
ou renvoyez la valeur par défaut.
Votre eliminate
pourrait s'écrire maybe 0 id
, par exemple. appliquer la fonction d'identité ou retourner 0.
De la norme Data.Maybe
,
fromJust :: Maybe a -> a
fromJust Nothing = error "Maybe.fromJust: Nothing"
fromJust (Just x) = x
Il s'agit d'une fonction partielle (ne renvoie pas de valeur pour chaque entrée, par opposition à un total , qui fait), mais extrait la valeur lorsque cela est possible.
Je suis nouveau pour Haskell aussi, donc je ne sais pas encore si cela existe dans la plate-forme (je suis sûr que c'est le cas), mais que diriez-vous d'une fonction "get or else" pour obtenir une valeur si elle existe, sinon return un défaut?
getOrElse::Maybe a -> a -> a
getOrElse (Just v) d = v
getOrElse Nothing d = d
Voici la réponse que je cherchais lorsque je suis arrivée à cette question:
https://hackage.haskell.org/package/base-4.9.0.0/docs/Data-Maybe.html#v:fromJust
... et de même, pour Either
:
https://hackage.haskell.org/package/either-unwrap-1.1/docs/Data-Either-Unwrap.html
Ils fournissent des fonctions que j'aurais écrites moi-même qui déballent la valeur de son contexte.
La signature de type de la fonction eliminate
est:
eliminate :: Maybe Int -> Int
C'est parce qu'il retourne 0 sur Nothing, forçant le compilateur à supposer que a :: Int
dans votre fonction eliminate
. Par conséquent, le compilateur déduit que la signature de type de la fonction combine
est:
combine :: Maybe Int -> Maybe Int -> Maybe Int -> (Int, Int, Int)
et c'est précisément pourquoi cela ne fonctionne pas lorsque vous lui passez une chaîne.
Si vous l'avez écrit comme:
combine a b c = (eliminate a, eliminate b, eliminate c)
where eliminate (Just a) = a
eliminate Nothing = undefined
alors cela aurait fonctionné avec String ou avec tout autre type. La raison repose sur le fait que undefined :: a
, ce qui rend eliminate
polymorphe et applicable à des types autres que Int.
Bien sûr, ce n'est pas le but de votre code, c'est-à-dire de rendre la fonction de combinaison totale.
En effet, même si une application de combine
à certains arguments Nothing réussissait (c'est parce que Haskell est paresseux par défaut), dès que vous essayez d'évaluer les résultats, vous obtiendrez une erreur d'exécution comme undefined
ne peut pas être évalué en quelque chose d'utile (pour le dire simplement).