许多介绍性文本会告诉您,在Haskell类型中,签名“几乎总是”可选的。任何人都可以量化“差不多”的部分吗?
据我所知,仅时间需要显式签名是消除类型类的歧义。 (规范的例子是read . show
。)还有其他我没有想过的情况,或者是这样吗?
(我知道如果你超越Haskell 2010就有很多例外。例如,GHC永远不会推断排名N类型。但是排名N类型是语言扩展,不是官方标准的一部分[然而]。)
答案 0 :(得分:25)
一般来说,多态递归需要类型注释。
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
次。当然,有一种更简单的方法来编写一个等效的程序。
答案 1 :(得分:20)
如果您启用了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
并确定是否约束实际上是满足的。为了使此表达式起作用,需要注释snd
或myTuple
的类型:
myString = snd (myTuple :: ((), String))
答案 2 :(得分:4)
在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)
由于random
会在Random
类中返回某些内容,因此我们必须使用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%肯定,它是唯一需要类型的时候。