Le choix entre les alternatives dans un type de données algébrique Haskell
-
13-09-2019 - |
Question
Si le type est défini comme X
:
data X =
X { sVal :: String } |
I { iVal :: Int } |
B { bVal :: Bool }
et je veux la Int
l'intérieur d'une valeur X
, s'il y a un, sinon nul.
returnInt :: X -> Int
Comment puis-je déterminer quel type de X
l'argument est returnInt
?
La solution
Juste pour clarifier un point ici, permettez-moi de réécrire votre type de données pour éviter les ambiguïtés dans la signification de X:
data SomeType = X { myString :: String} | I {myInt :: Int} | B {myBool :: Bool}
Dans cette définition, il n'y a pas X, I et types B. X, I et B sont constructeurs qui créent une valeur de type Sometype
. Notez ce qui se passe lorsque vous demandez ghci quel est le type d'une valeur construite avec les constructeurs de type:
*Main> :t (I 5)
(I 5) :: Sometype
*Main> :t (B False)
(B False) :: Sometype
Ils appartiennent au même type !!
Comme vous pouvez utiliser X, I et B pour construire des types, comme fait dans les autres réponses ci-dessus, vous pouvez utiliser modèle correspondant à déconstruire le type,:
returnInt :: SomeType -> Int
returnInt (I x) = x -- if the pattern matches (I x) then return x
returnInt _ = error "I need an integer value, you moron" -- throw an error otherwise
Rappelez-vous simplement que la correspondance de modèle se produit dans l'ordre:. Si la valeur correspond au modèle dans une ligne, les modèles dans les lignes ci-dessous qui ne seront pas exécutées
Notez que lorsque vous définissez votre type comme vous l'avez fait, en utilisant ce qu'on appelle enregistrement de syntaxe (il suffit de regarder ici: http://en.wikibooks.org/wiki/Haskell/More_on_datatypes ), vous avez des fonctions comme cela gratuitement !!
Essayez de regarder le type de myInt, par exemple:
*Main> :t myInt
myInt :: SomeType -> Int
Et regardez ce que cette fonction ne:
*Main> myInt (I 5)
5
*Main> myInt (B False)
*** Exception: No match in record selector Main.myInt
Ceci est exactement le comportement de returnInt
défini ci-dessus. L'étrange message d'erreur vous indique simplement que la fonction ne sais pas comment faire face à un membre du type UnType qui ne correspond pas à (I x)
.
Si vous définissez votre type en utilisant la syntaxe plus commune:
data SomeType2 = X String | I Int | B Bool
vous perdez ces belles fonctions d'enregistrement.
Les messages d'erreur terminent l'exécution du programme. Cela est parfois gênant. Si vous avez besoin un comportement plus sûr pour vos fonctions de réponse de GBacon est juste la façon de le faire. En savoir plus sur le type de Maybe a
et l'utiliser pour faire face à ce genre de calcul qui ont besoin de retourner une valeur ou retourner rien (essayez ceci: http://en.wikibooks.org/wiki/Haskell/Hierarchical_libraries/Maybe ).
Autres conseils
Utilisation correspondance de motif.
returnInt :: X -> Int
returnInt (I x) = x
returnInt _ = 0
Utilisez une définition plus souple pour toutes les valeurs possibles X
:
returnInt :: X -> Maybe Int
returnInt (I i) = Just i
returnInt _ = Nothing
Ensuite, vous pouvez utiliser maybe
pour la vous voulez-0 défaut particulier pourrait être une valeur valide (ce qui est connu comme le semipredicate problème ):
*Main> maybe 0 id (returnInt $ X "")
0
*Main> maybe 0 id (returnInt $ I 123)
123
*Main> maybe (-1) id (returnInt $ X "yo")
-1
En revanche, les exceptions d'exécution de risque fonctions partielles:
*Main> let returnInt (I i) = i
*Main> :t returnInt
returnInt :: X -> Int
*Main> returnInt (B True)
*** Exception: <interactive>:1:4-22: Non-exhaustive patterns in function returnInt
Si vous vous sentez vraiment froggy, vous pouvez utiliser MonadPlus
returnInt :: (MonadPlus m) => X -> m Int
returnInt (I i) = return i
returnInt _ = mzero
pour gagner encore plus de flexibilité:
*Main> maybe 0 id (returnInt $ X "")
0
*Main> maybe 0 id (returnInt $ I 123)
123
*Main> returnInt (I 123) `mplus` returnInt (I 456) :: [Int]
[123,456]
Étant donné une fonction comme ceci:
returnInt :: X -> Int
returnInt x = {- some integer -}
... le type de x
est toujours X
. Qu'est-ce que vous vous souciez est de savoir si x
utilise le X
, I
ou constructeur de type B
.
Mode d'utilisation correspondant à la différence:
returnInt :: X -> Int
returnInt (X _) = error "needed an Int, got a String"
returnInt (I { iVal = n }) = n
returnInt (B _) = error "needed an Int, got a Bool"