[英]Why I get the “class Num a where” instead of the “class (Eq a, Show a) => Num a”?
[英]Why is `succ i` valid where `i :: Num a => a` (and not an `Enum a`)?
這似乎適用於GHCi和GHC。 我將首先展示一個GHCi的例子。
給定i
類型已被推斷如下:
Prelude> i = 1
Prelude> :t i
i :: Num p => p
鑒於succ
是在Enum
定義的函數:
Prelude> :i Enum
class Enum a where
succ :: a -> a
pred :: a -> a
-- …OMITTED…
並且Num
不是Enum
的'子類'(如果我可以使用該術語):
class Num a where
(+) :: a -> a -> a
(-) :: a -> a -> a
-- …OMITTED…
為什么succ i
沒有返回錯誤?
Prelude> succ i
2 -- works, no error
我希望:type i
可以推斷為:
Prelude> i = 1
Prelude> :type i
i :: (Enum p, Num p) => p
(我正在使用'GHC v.8.6.3')
加成:
在閱讀@RobinZigmond評論和@AlexeyRomanov回答后,我注意到1
可以被解釋為許多類型之一和許多類之一。 感謝@AlexeyRomanov的回答,我對用於確定用於模糊表達的類型的默認規則了解得更多。
但是我不覺得Alexey的回答完全解決了我的問題。 我的問題是關於i
的類型。 這不是succ i
的類型。
這是關於succ
參數類型( Enum a
)和i
的表觀類型( Num a
)之間的不匹配。
我現在開始意識到我的問題必須來自一個錯誤的假設:'一旦i
被推斷為i :: Num a => a
,那么i
就沒有其他了 。 因此,我很困惑地看到succ i
被評估沒有錯誤。
除了明確宣布的內容之外,GHC似乎也在推斷Enum a
。
x :: Num a => a
x = 1
y = succ x -- works
但是,當類型變量顯示為函數時,它不會添加Enum a
:
my_succ :: Num a => a -> a
my_succ z = succ z -- fails compilation
對我而言,附加到函數的類型約束似乎比應用於變量的類型更嚴格。
GHC說my_succ :: forall a. Num a => a -> a
my_succ :: forall a. Num a => a -> a
並且給定forall a
不出現在i
和x
的類型簽名中我認為這意味着GHC不會為my_succ
類型推斷更多類。
但這似乎又錯了:我用以下內容檢查了這個想法(我第一次輸入RankNTypes),顯然GHC仍然推斷Enum a
:
{-# LANGUAGE RankNTypes #-}
x :: forall a. Num a => a
x = 1
y = succ x
因此,似乎函數的推理規則比變量的推理規則更嚴格?
是的, succ i
的類型是按照你的期望推斷出來的:
Prelude> :t succ i
succ i :: (Enum a, Num a) => a
這種類型是模糊的,但它滿足GHCi 的默認規則中的條件:
找到所有未解決的約束。 然后:
- 找到那些形式為
(C a)
,其中a
是一個類型變量,並將這些約束划分為共享公共類型變量a
。
在這種情況下,只有一個組:( (Enum a, Num a)
。
- 僅保留至少一個類是交互式類的組(在下面定義)。
這個小組是保留的,因為Num
是一個互動課程。
現在,對於每個剩余的組G,依次嘗試默認類型列表中的每個類型
ty
; 如果設置a = ty
將允許完全解決G中的約束。 如果是的話,默認為a
以ty
。單元類型
()
和列表類型[]
被添加到執行類型默認時嘗試的標准類型列表的開頭。
默認的默認類型列表(sic)是(帶有最后一個子句的添加) default ((), [], Integer, Double)
。
所以當你做Prelude> succ i
來實際評估這個表達式時(注意:t
不計算它得到的表達式), a
設置為Integer
(滿足約束條件的第一個列表),結果打印為2
。
您可以通過更改默認值來查看原因:
Prelude> default (Double)
Prelude> succ 1
2.0
對於更新的問題:
我現在開始意識到我的問題必須來自一個錯誤的假設:'一旦
i
被推斷為i :: Num a => a
,那么i
就沒有其他了。 因此,我很困惑地看到succ i
被評估沒有錯誤。
i
可以不是別的 (即沒有任何不適合這種類型的東西),但它可以用於較少的一般(更具體)類型: Integer
, Int
。 即使他們中的許多人同時出現在表達中:
Prelude> (i :: Double) ^ (i :: Integer)
1.0
並且這些用途不會影響i
本身的類型:它已經定義並且其類型已修復。 好的到目前為止?
好吧,添加約束也會使類型更具體,因此(Num a, Enum a) => a
比(Num a) => a
更具體:
Prelude> i :: (Num a, Enum a) => a
1
因為當然,任何類型的a
滿足在兩個約束(Num a, Enum a)
滿足只是Num a
。
但是,當類型變量顯示為函數時,它不會添加
Enum a
:
那是因為你指定了一個不允許它的簽名。 如果您不提供簽名,則沒有理由推斷Num
約束。 但是,例如
Prelude> f x = succ x + 1
將推斷具有兩個約束的類型:
Prelude> :t f
f :: (Num a, Enum a) => a -> a
因此,似乎函數的推理規則比變量的推理規則更嚴格?
實際上它是由於單態限制而導致的另一種方式(默認情況下不在GHCi中)。 你實際上有點幸運,不要在這里遇到它,但答案已經足夠長了。 搜索該術語應該給你解釋。
GHC說
my_succ :: forall a. Num a => a -> a
my_succ :: forall a. Num a => a -> a
並且給定forall a
不會出現在i
和x
的類型簽名中。
那是一只紅鯡魚。 我不知道為什么它在一個機箱上的顯示,而不是其他,但他們有一個forall a
幕后:
Haskell類型簽名是隱式量化的。 當使用語言選項
ExplicitForAll
,關鍵字forall
允許我們確切地說出這意味着什么。 例如:g :: b -> b
意思是:
g :: forall b. (b -> b)
(另外,你只需要使用ExplicitForAll
而不是RankNTypes
來記下forall a. Num a => a
RankNTypes
forall a. Num a => a
。)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.