haskell - 安全且多态的 toEnum

标签 haskell polymorphism

我想编写一个安全版本的 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))'

据我了解该消息,编译器无法识别 minBoundmaxBound 应该生成与 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

最佳答案

这里不需要作用域类型变量,您只需要向 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对于这种场景。

关于haskell - 安全且多态的 toEnum,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2743858/

相关文章:

perl - 在 Perl 中实现函数式编程

haskell - 我可以让 Stack 使用我打过补丁的 Cabal 库吗?

haskell - 如何在 Haskell 中实现部分注入(inject)类型族?

Haskell: TVar: orElse

c++ - 虚拟构造函数

java - Java中动态多态和静态多态有什么区别?

haskell - Haskell 有没有一种简单的方法来实现快速优先级队列?

c++ - 具有多态模板参数的多态模板类

java - hibernate 。无法保存使用 classForName 生成并声明为其父类(super class)的对象(多态性)

java - Java 中具有两个泛型的方法