代码之家  ›  专栏  ›  技术社区  ›  sastanin

安全多形趾

  •  8
  • sastanin  · 技术社区  · 14 年前

    我想写一个安全的版本 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 显式类型声明的inspite( :: t )知道怎么修吗?


    解决了的

    Camccann和Dave的解决方案都有效(尽管Dave的解决方案需要调整)。谢谢你们两位(但我只能接受一位)。作用域类型变量的工作示例:

    {-# 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
    
    2 回复  |  直到 14 年前
        1
  •  13
  •   C. A. McCann Ravikant Cherukuri    14 年前

    这里不需要作用域类型变量,您只需要向ghc说明您希望所有 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)。在这种情况下, 作用域类型变量 真是太过分了 Haskell wiki suggests asTypeOf as a portable replacement 在这种情况下。

        2
  •  3
  •   dave4420    14 年前

    您需要使用范围类型变量

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