安全和多态toEnum

sas*_*nin 8 polymorphism haskell

我想写一个安全的版本toEnum:

 safeToEnum :: (Enum t, Bounded t) => Int -> Maybe t
Run Code Online (Sandbox Code Playgroud)

一个天真的实现:

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)
Run Code Online (Sandbox Code Playgroud)

它不起作用:

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))'
Run Code Online (Sandbox Code Playgroud)

除了我理解这个消息之外,编译器不会识别它,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
Run Code Online (Sandbox Code Playgroud)

C. *_*ann 13

这里不需要范围类型变量,您只需要向GHC明确表示您希望所有的Enum东西都是相同的类型.通过将它们全部传递给显式采用Enum相同类型的各种s的函数,可以很容易地做到这一点.这是一种方式:

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)
Run Code Online (Sandbox Code Playgroud)

在GHCi中尝试:

> main
Just True
Nothing
Run Code Online (Sandbox Code Playgroud)

使用相同原理的更通用的解决方案是标准库函数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
Run Code Online (Sandbox Code Playgroud)

这个版本也适用.

请记住,这ScopedTypeVariables是一种语言扩展,因此不一定能在编译器之间移植.在实践中,几乎每个人都使用GHC,但通常最好在可能的情况下坚持使用标准基本语言(即Haskell 98).在这种情况下,ScopedTypeVariables真的是矫枉过正; 在哈斯克尔维基建议asTypeOf便携式更换为这种情况的.