2010-04-30 11 views
8

Je voudrais écrire une version sûre de toEnum:toEnum sûr et polymorphes

safeToEnum :: (Enum t, Bounded t) => Int -> Maybe t 

Une implémentation naïve:

safeToEnum :: (Enum t, Bounded t) => Int -> Maybe t 
safeToEnum i = 
    if (i >= fromEnum (minBound :: t)) && (i <= fromEnum (maxBound :: t)) 
    then Just . toEnum $ i 
    else Nothing 

main = do 
    print $ (safeToEnum 1 :: Maybe Bool) 
    print $ (safeToEnum 2 :: Maybe Bool) 

Et cela ne fonctionne pas:

safeToEnum.hs:3:21: 
    Could not deduce (Bounded t1) from the context() 
     arising from a use of `minBound' at safeToEnum.hs:3:21-28 
    Possible fix: 
     add (Bounded t1) to the context of an expression type signature 
    In the first argument of `fromEnum', namely `(minBound :: t)' 
    In the second argument of `(>=)', namely `fromEnum (minBound :: t)' 
    In the first argument of `(&&)', namely 
     `(i >= fromEnum (minBound :: t))' 

safeToEnum.hs:3:56: 
    Could not deduce (Bounded t1) from the context() 
     arising from a use of `maxBound' at safeToEnum.hs:3:56-63 
    Possible fix: 
     add (Bounded t1) to the context of an expression type signature 
    In the first argument of `fromEnum', namely `(maxBound :: t)' 
    In the second argument of `(<=)', namely `fromEnum (maxBound :: t)' 
    In the second argument of `(&&)', namely 
     `(i <= fromEnum (maxBound :: t))' 

Aussi bien que je comprends le message, le compilateur ne reconnaît pas que minBound et maxBound doit produire exactement le même type que dans le type de résultat safeToEnum en dépit de la déclaration de type explicite (:: t). Toute idée de comment résoudre ce problème?


Résolu

Les deux années camccann et le travail des solutions de Dave (bien que celui de Dave doit être ajustée). Merci à vous deux (mais je ne pouvais en accepter qu'un). Exemple de travail avec ScopedTypeVariables:

{-# LANGUAGE ScopedTypeVariables #-} 

safeToEnum :: forall t . (Enum t, Bounded t) => Int -> Maybe t 
safeToEnum i = 
    if (i >= fromEnum (minBound :: t)) && (i <= fromEnum (maxBound :: t)) 
    then Just . toEnum $ i 
    else Nothing 

Répondre

13

Les variables de type Scoped ne sont pas nécessaires ici, vous devez juste indiquer clairement à GHC que toutes les données Enum doivent être du même type. C'est facile à faire en les passant tous à une fonction qui prend explicitement divers Enum s du même type. Voici une façon:

enumIfBetween :: (Enum a) => a -> a -> Int -> Maybe a 
enumIfBetween a z x = let a' = fromEnum a 
          z' = fromEnum z 
         in if a' <= x && x <= z' 
         then Just $ toEnum x 
         else Nothing 

safeToEnum i = enumIfBetween minBound maxBound i 

main = do 
    print $ (safeToEnum 1 :: Maybe Bool) 
    print $ (safeToEnum 2 :: Maybe Bool) 

Essayer dans GHCi:

> main 
Just True 
Nothing 

Une solution plus générale en utilisant le même principe est la fonction de la bibliothèque standard asTypeOf, qui a le même comportement que const mais exige que les deux arguments être le même type:

safeToEnum :: (Enum t, Bounded t) => Int -> Maybe t 
safeToEnum i = let r = toEnum i 
        max = maxBound `asTypeOf` r 
        min = minBound `asTypeOf` r 
       in if i >= fromEnum min && i <= fromEnum max 
       then Just r 
       else Nothing 

Cette version fonctionne également. Gardez à l'esprit que ScopedTypeVariables est une extension de langage, et donc pas nécessairement portable entre les compilateurs. En pratique, presque tout le monde utilise GHC, mais il est habituellement préférable de s'en tenir à la langue de base standard (à savoir, Haskell 98) lorsque cela est possible. Dans ce cas, ScopedTypeVariables est vraiment exagéré; le Haskell wiki suggests asTypeOf as a portable replacement pour ce genre de scénario.

+0

Oui. Cela fonctionne aussi. Bonne idée. – sastanin

+0

J'aime vos solutions. C'est très bien que 'r' ne soit pas réellement évalué dans' asTypeOf' (deuxième version). – sastanin

+0

@jetxee: Eh bien, c'est certainement évalué si vous l'utilisez réellement * ... ce qui bien sûr ne se produira pas si le résultat est 'Nothing'. N'est-ce pas génial d'être paresseux? –

3

Vous devez utiliser des variables de type scope

{-# LANGUAGE ScopedTypeVariables #-} 

safeToEnum :: (Enum t, Bounded t) => Int -> Maybe t 
safeToEnum i = 
    if (i >= fromEnum (minBound :: t)) && (i <= fromEnum (maxBound :: t)) 
    then Just . toEnum $ i 
    else Nothing 

main = do 
    print $ (safeToEnum 1 :: Maybe Bool) 
    print $ (safeToEnum 2 :: Maybe Bool) 

Sans elle, t signifie forall t. t.

+0

Toujours 'Impossible de déduire (borné t1) du contexte()'. GHC 6.12.1. – sastanin

+0

OK. Cela fonctionne: 'safeToEnum :: forall t. (Enum t, borné t) => Int -> Peut-être t'. Je vous remercie. – sastanin

+1

Vous pouvez contourner le besoin d'utiliser des variables de type scoped en ajoutant un combinateur d'aide comme: asArgTypeOf :: a -> f a -> a; asArgTypeOf = const et plomberie comme safeToEnum i = r où r = ... et en utilisant (minBound 'asArgTypeOf' r) et (maxBound' asArgTypeOf' r). –