2010-04-30 4 views
8

Ich möchte eine sichere Version von toEnum schreiben:Sicher und polymorphen toEnum

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

Eine naive Implementierung:

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) 

Und es funktioniert nicht:

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

Sowie ich die Nachricht verstehe, erkennt der Compiler nicht, dass minBound und maxBound sollte trotz der expliziten Typdeklaration() genau den gleichen Typ wie im Ergebnistyp safeToEnum produzieren. Irgendeine Idee, wie man es repariert?


Gelöst

Beide camccann ist und Lösungen Arbeit Dave (obwohl Daves ein angepasst werden muss). Danke euch beiden (aber ich konnte nur einen akzeptieren). Arbeitsbeispiel mit 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 

Antwort

13

Scoped-Typ-Variablen sind hier nicht notwendig, Sie müssen nur GHC klar machen, dass Sie alle Enum Sachen vom gleichen Typ erwarten. Dies ist einfach zu tun, indem alle an eine Funktion übergeben werden, die verschiedene Typen des gleichen Typs explizit annimmt. Hier ist eine Möglichkeit:

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) 

es in GHCi Versuch:

> main 
Just True 
Nothing 

Eine allgemeine Lösung nach dem gleichen Prinzip ist die Standard-Library-Funktion asTypeOf, das das gleiche Verhalten wie const hat, erfordert aber beiden Argumente vom selben Typ sein:

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 

Diese Version funktioniert auch.

Beachten Sie, dass ScopedTypeVariables eine Sprachenerweiterung ist und daher nicht unbedingt zwischen Compilern portierbar ist. In der Praxis verwendet fast jeder GHC, aber es ist normalerweise bevorzugt, wenn möglich, sich an die Standardbasissprache (d. H. Haskell 98) zu halten. In diesem Fall ist ScopedTypeVariables wirklich übertrieben; das Haskell wiki suggests asTypeOf as a portable replacement für diese Art von Szenario.

+0

Ja. Das funktioniert auch. Gute Idee. – sastanin

+0

Ich mag Ihre Lösungen. Es ist sehr schön, dass 'r' nicht wirklich in' asTypeOf' (zweite Version) ausgewertet wird. – sastanin

+0

@jetxee: Nun, es ist sicherlich ausgewertet, wenn Sie es tatsächlich * benutzen * was natürlich nicht passieren wird, wenn das Ergebnis 'Nichts' ist. Ist es nicht toll, faul zu sein? –

3

Sie benötigen scoped Variablen Typ

{-# 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) 

Ohne es zu benutzen, bedeutet tforall t. t.

+0

Immer noch 'Konnte nicht abgeleitet werden (Bounded t1) aus dem Kontext()'. GHC 6.12.1. – sastanin

+0

OK. Das funktioniert: 'safeToEnum :: forall t. (Enum t, Bounded t) => Int -> Vielleicht t'. Vielen Dank. – sastanin

+1

Sie können die Notwendigkeit umgehen, Variablen vom Geltungsbereich zu verwenden, indem Sie einen Hilfskombinator wie folgt hinzufügen: asArgTypeOf :: a -> f a -> a; asArgTypeOf = const und und plumbing es durch wie safeToEnum i = r wo r = ... und mit (minBound 'asArgTypeOf' r) und (maxBound' asArgTypeOf' r). –

Verwandte Themen