我想写一个toEnum
的安全版本:
safeToEnum :: (Enum t, Bounded t) => Int -> Maybe t
天真的实施:
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)
它不起作用:
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))'
除了我理解该消息之外,编译器不会识别minBound
和maxBound
应该生成与safeToEnum
的结果类型完全相同的类型,尽管显式类型声明(:: t
)。知道怎么解决吗?
解决
camccann和Dave的解决方案都有效(尽管Dave需要调整)。谢谢你们(但我只能接受一个)。使用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
答案 0 :(得分:13)
此处不需要范围类型变量,您只需要向GHC明确表示您希望所有Enum
内容都是相同的类型。通过将它们全部传递给显式采用相同类型的各种Enum
的函数,这很容易实现。这是一种方式:
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)
在GHCi中尝试:
> main
Just True
Nothing
使用相同原理的更通用的解决方案是标准库函数asTypeOf
,它具有与const
相同的行为,但要求两个参数都是相同的类型:
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
此版本也适用。
请记住,ScopedTypeVariables
是一种语言扩展,因此不一定能在编译器之间移植。在实践中,几乎每个人都使用GHC,但通常最好在可能的情况下坚持使用标准基本语言(即Haskell 98)。在这种情况下,ScopedTypeVariables
确实有点过分;这种情况的Haskell wiki suggests asTypeOf
as a portable replacement。
答案 1 :(得分:3)
您需要使用范围类型变量
{-# 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)
没有它,t
表示forall t. t
。