web-dev-qa-db-fra.com

Comment obtenir la valeur d'un peut-être à Haskell

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

34
Moe

[é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.

23
Dan Burton

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.

44
ephemient

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

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.

2
Ben

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

1
Cristiano Paris