簡體   English   中英

為什么`succ i`在`i :: Num a => a`(而不是'Enum 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不出現在ix的類型簽名中我認為這意味着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中的約束。 如果是的話,默認為aty

  • 單元類型()和列表類型[]被添加到執行類型默認時嘗試的標准類型列表的開頭。

默認的默認類型列表(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可以不是別的 (即沒有任何不適合這種類型的東西),但它可以用於較少的一般(更具體)類型: IntegerInt 即使他們中的許多人同時出現在表達中:

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不會出現在ix的類型簽名中。

那是一只紅鯡魚。 我不知道為什么它在一個機箱上的顯示,而不是其他,但他們有一個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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM