[英]Typeclass behavior in Haskell
作為Haskell的初學者,我很難理解為什么會失敗
-- this works fine of course
f :: Float
f = 1.0
f :: Num a => a
f = 1.0
-- Could not deduce (Fractional a) arising from the literal ‘1.0’
-- from the context: Num a
-- bound by the type signature for:
-- f :: forall a. Num a => a
我的困惑源於他們都有Num的例子。 因此,由於Int,Integers,Doubles等都有Num類型類的實例,為什么我不能在f
填充任何數值?
例如, negate
它有一個簽名negate :: Num a => a -> a
將與廉政局的浮動雙打等工作
任何見解將不勝感激。
這里的問題是,當你寫f :: Num a => a
,這意味着f
必須為所有可能的實例運行a
使得Num a
實例存在。 特別是,這意味着在其他地方寫入(f :: Int)
應該可以正常工作,因為instance Num Int
肯定存在。 但是,為f
返回的值是1.0
,它不是整數: 1.0 :: Fractional p => p
。 錯誤消息基本上是說“知道a
是一個Num
並沒有告訴我們a
是一個Fractional
,所以分數字面1.0沒有辦法讓類型為a
”。
想想它的一種方法是, 調用者就可以選擇什么a
應該是:這就是為什么這種形式的類型簽名被稱為普遍量化 。
您可能正在考慮存在量化: f
返回某種Num
,但“調用者”不知道Num
返回了什么。 這通常不如通用量化那么有用,並且在Haskell中有點笨拙,但可以像這樣完成:
{-# LANGUAGE ExistentialQuantification #-} -- This is a GHC extension
-- A SomeNum value contains some kind of Num. Can't see what kind from the "outside".
data SomeNum = forall a. Num a => SomeNum a
f :: SomeNum
f = SomeNum 5.0
在Haskell中,像f :: a
這樣的簽名是(概念上更清晰的) f :: forall a. a
語法f :: forall a. a
f :: forall a. a
,其中forall
很像“類型級lambda”(而不是f :: exists a. a
你可能一直在考慮)。 事實上,如果你看一下GHC Core,你會看到所有這些類型的應用程序都是顯式的:每當你使用一個通用量化的函數時,“調用者”顯式地傳入要用於每個類型變量的類型。
但是,我建議您不要在此階段嘗試使用存在量化:通常有更好/更容易的替代方案。 我只是想解釋它,以幫助顯示存在與普遍性之間的區別。
為什么我不能在
f
填充任何數值?例如,具有簽名
negate :: Num a => a -> a
將適用於Int
的Float
的Double
s等。
正因為如此!
你不能在f
的定義中填充任何數值,因為你不能在negate
的定義中填充任何數值。
假設我們試圖定義negate
如下
negate :: Num a => a -> a
negate x = 10.5 - 10.5 - x
這會對Int
工作嗎? 也就是說, negate 42 :: Int
? 不,因為10.5
不是Int
。
由於negate
的類型承諾它適用於任何數字類型,包括Int
,但它實際上不適用於Int
,因此承諾被破壞。 靜態類型檢查因此拒絕該代碼。
同樣,如果接受了類型檢查
f :: Num a => a
f = 10.5
然后所有這些都應該工作: f + 8 :: Int
, f / 2 :: Double
, f - 4 :: Integer
。 但是10.5
不適合Int
(也不適合Integer
)。
這里的問題是f :: Num a => a
允許調用者選擇任何數字類型a
。 由於類型允許調用者選擇, f
選擇,但必須適應調用者做出的任何選擇。 因此, f
不能使用僅適用於某些數字類型的代碼,而不能使用其他類型的代碼。
如果f
僅適用於Fractional
類型( Num
類型的子集),則f
的類型必須向調用者通告其選擇僅限於小數類型。 這是通過使用f :: Fractional a => a
來完成的。
如果你只是寫
f = 10.5
你回來了f :: Fractional a => a
沒有問題。
當你明確聲明它的類型為Num a => a
,Haskell必須統一聲明的和實際的類型,而它不能,因為Fractional
是Num
的子類 :
>> :i Fractional
class Num a => Fractional a where
-- ^^^ -- Fractional is Num's subclass
...........
fromRational :: Rational -> a
...........
每個Fractional
都是一個Num
,但不是每個Num
都是Fractional
。
fromRational
的類型來自fromRational :: Fractional a => Rational -> a
。
這表明像10.5
這樣的浮點文字實際上是從fromRational (10.5 :: Rational)
讀取的,就像整個數字文字(如10
被讀作fromInteger (10 :: Integer)
。
實際上, Haskell教程中的第10.3節內容如下:
整數(不帶小數點)實際上等同於
fromInteger
到數字值作為Integer
。 類似地,浮動數字(帶小數點)被視為fromRational
到數字值作為Rational
。 因此,7
具有類型(Num a) => a
,並且7.3
具有類型(Fractional a) => a
。
當你給編譯器類型聲明一樣f :: Num a => a
,你不只是說f
有類型類Num
但是這一切 ,你知道它在這里。 由於您輸入的是1.0
而不是1
,因此編譯器斷定您還需要Fractional
。 你的聲明說你沒有,所以這不能編譯。 如果你說它是Fractional a => a
,它會編譯,而Fractional
表示Num
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.