I’d like to write a safe version of toEnum:
safeToEnum :: (Enum t, Bounded t) => Int -> Maybe t
A naive implementation:
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)
And it doesn’t work:
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))'
As well as I understand the message, the compiler does not recognize that minBound and maxBound should produce exactly the same type as in the result type of safeToEnum inspite of the explicit type declaration (:: t). Any idea how to fix it?
Solved
Both camccann’s and Dave’s solutions work (though Dave’s one needs to be adjusted). Thank you both (but I could accept only one). Working example with 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
Scoped type variables aren’t necessary here, you just need to make it clear to GHC that you expect all the
Enumstuff to be the same type. This is easy to do by passing them all to a function that explicitly takes variousEnums of the same type. Here’s one way:Trying it in GHCi:
A more general solution using the same principle is the standard library function
asTypeOf, which has the same behavior asconstbut requires both arguments to be the same type:This version works as well.
Keep in mind that
ScopedTypeVariablesis a language extension, and thus not necessarily portable between compilers. In practice almost everyone uses GHC, but it’s usually preferred to stick to the standard base language (i.e., Haskell 98) when possible. In this case,ScopedTypeVariablesreally is overkill; the Haskell wiki suggestsasTypeOfas a portable replacement for this kind of scenario.