Prelude> let myprint = putStrLn . show
Prelude> :t myprint
myprint :: () -> IO ()
好的,这里没什么不寻常的。只是GHCi类型的默认规则,我猜......
Prelude> let myprint = (putStrLn . show) :: Show x => x -> IO ()
Prelude> :t myprint
myprint :: () -> IO ()
这是什么巫术?你是直言忽略我的类型声明?! O_O
有什么方法可以说服GHCi做我真正想要的事情吗?
答案 0 :(得分:21)
在表达式中添加类型注释,如
e :: type
使编译器检查e
是否具有type
,以及使用type
来驱动类型变量实例化和实例选择。 然而,如果type
是多态的,它仍然可以在以后实例化。考虑例如。
(id :: a -> a) "hello"
上面,a
将被实例化为String
,尽管我的注释。此外,
foo :: Int -> Int
foo = (id :: a -> a)
稍后会将a
实例化为Int
。上面的id
注释没有向GHC提供任何信息:它已经知道id
具有该类型。
我们可以删除它而不会影响类型检查。也就是说,表达式id
和id :: a->a
不仅是动态等效的,而且也是静态等效的。
同样,表达式
putStrLn . show
和
(putStrLn . show) :: Show x => x -> IO ()
是静态等价的:我们只是用GHC可以推断的类型来注释代码。换句话说,我们没有向GHC提供任何尚未了解的信息。
在注释类型检查后,GHC可以进一步实例化x
。在您的示例中,单态限制就是这样。为防止这种情况,请使用您正在介绍的绑定的注释,而不是表达式:
myprint :: Show x => x -> IO ()
myprint = (putStrLn . show)
答案 1 :(得分:17)
我们可以执行以下操作,单一性限制:
>let myprint :: Show x => x -> IO (); myprint = putStrLn . show
>:t myprint
myprint :: Show x => x -> IO ()
这与let myprint = putStrLn . show :: Show x => x -> IO ()
不同。在前一种情况下,我们有一个带有类型签名的绑定,在后一种情况下,我们在右侧有一个带有类型注释的let
绑定。单态检查顶级类型签名,但不检查本地注释。