【问题标题】:When are type signatures necessary in Haskell?Haskell 什么时候需要类型签名?
【发布时间】:2015-01-19 23:38:43
【问题描述】:

许多介绍性文本会告诉您,在 Haskell 中,类型签名“几乎总是”是可选的。任何人都可以量化“几乎”部分吗?

据我所知,唯一您需要显式签名的时间是消除类型类的歧义。 (典型的例子是read . show。)还有其他我没有想到的情况,或者是这样吗?

(我知道,如果您超越 Haskell 2010,则有很多例外。例如,GHC 永远不会推断 rank-N 类型。但是 rank-N 类型是语言扩展,而不是官方标准的一部分 [然而]。)

【问题讨论】:

  • 您是要一个大列表,还是只是示例?
  • this instance 怎么样?由于使用方式不同,编译器在元组的每一侧为相同的表达式推断出不同的类型。原因并不是很明显,但是突然之间有了类型签名,一切都变得有意义了。当然,这仍然是为了消除类型类的歧义。

标签: haskell types typeclass


【解决方案1】:

一般来说,多态递归需要类型注解。

f :: (a -> a) -> (a -> b) -> Int -> a -> b
f f1 g n x = 
    if n == (0 :: Int)
    then g x
    else f f1 (\z h -> g (h z)) (n-1) x f1

(图片来源:Patrick Cousot)

请注意递归调用的类型错误(!):尽管f 只有四个,但它使用五个 参数调用自身!然后记住b 可以用c -> d 实例化,这会导致出现一个额外的参数。

上面设计的例子计算

f f1 g n x = g (f1 (f1 (f1 ... (f1 x))))

其中f1 被应用n 次。当然,还有一种更简单的方法来编写等效程序。

【讨论】:

    【解决方案2】:

    单态限制

    如果您启用了MonomorphismRestriction,那么有时您需要添加类型签名以获得最通用的类​​型:

    {-# LANGUAGE MonomorphismRestriction #-}
    -- myPrint :: Show a => a -> IO ()
    myPrint = print
    main = do
      myPrint ()
      myPrint "hello"
    

    这将失败,因为myPrint 是单态的。您需要取消注释类型签名以使其工作,或禁用 MonomorphismRestriction

    虚拟约束

    当你把一个带约束的多态值放入一个元组时,元组本身就变成了多态并且具有相同的约束:

    myValue :: Read a => a
    myValue = read "0"
    
    myTuple :: Read a => (a, String)
    myTuple = (myValue, "hello")
    

    我们知道约束影响元组的第一部分,但影响第二部分。不幸的是,类型系统不知道这一点,如果你尝试这样做,它会抱怨:

    myString = snd myTuple
    

    尽管直觉上人们会认为 myString 只是一个 String,但类型检查器需要专门化类型变量 a 并确定是否确实满足约束。为了使这个表达式起作用,需要注释sndmyTuple 的类型:

    myString = snd (myTuple :: ((), String))
    

    【讨论】:

    • 第二个让我吃惊。但我想这有点像规范的 show [] 需要一个具体的类型,即使它(可能)并不重要。
    • 类似但不完全相同:在show [] 的情况下,编译器无法确定地判断列表是否为空:想象类似:show resultFromAComplicatedFunction。对于元组,编译器(理论上)可以判断。
    • myTuple' :: (Read a, Show a) => (a, String); myTuple' = let x = read "0" in (x, show x)) 怎么样?它的类型类似于您原来的myTuple 类型,但它的snd 显然仍然取决于a 的选择。
    • 你说得对,我想编译器并不像看起来那么明显。
    【解决方案3】:

    我相信您知道,在 Haskell 中,类型是推断的。换句话说,编译器会计算出你想要的类型。

    但是,在 Haskell 中,也有多态类型类,其函数根据返回类型以不同的方式起作用。这是 Monad 类的示例,虽然我还没有定义所有内容:

    class Monad m where
        return :: a -> m a
        (>>=) :: m a -> (a -> m b) -> m b
        fail :: String -> m a
    

    我们得到了很多只有类型签名的函数。我们的工作是为可以被视为 Monad 的不同类型进行实例声明,例如 Maybe t[t]

    看看这段代码 - 它不会像我们预期的那样工作:

    return 7
    

    这是一个来自 Monad 类的函数,但是因为有多个 Monad,我们必须指定我们想要的返回值/类型,否则它会自动变成一个 IO Monad。所以:

    return 7 :: Maybe Int
    -- Will return...
    Just 7
    
    return 6 :: [Int]
    -- Will return...
    [6]
    

    这是因为 [t]Maybe 都已在 Monad 类型类中定义。

    这是另一个例子,这次来自随机类型类。此代码抛出错误:

    random (mkStdGen 100)
    

    因为randomRandom 类中返回了一些东西,所以我们必须用StdGen 对象tupelo 来定义我们想要返回的类型:

    random (mkStdGen 100) :: (Int, StdGen)
    -- Returns...
    (-3650871090684229393,693699796 2103410263)
    
    random (mkStdGen 100) :: (Bool, StdGen)
    -- Returns...
    (True,4041414 40692)
    

    这一切都可以在learn you a Haskell 在线找到,不过您必须阅读一些长篇。这一点,我几乎 100% 确定,这是唯一需要类型的时候。

    【讨论】:

    • 这是正确的,但“消除类型类歧义”正是问题要求的。
    • 这是真的,但我需要为其他访问此问题的人解释唯一的实例。
    猜你喜欢
    • 2017-12-15
    • 2018-12-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-01
    • 2012-09-16
    • 2019-08-13
    • 1970-01-01
    相关资源
    最近更新 更多