[英]Why `f x = x x` and `g x = x x x x x` have the same type
我正在玩 Rank-N-type 並嘗試輸入xx
。 但是我發現這兩個函數可以以相同的方式輸入是違反直覺的。
f :: (forall a b. a -> b) -> c
f x = x x
g :: (forall a b. a -> b) -> c
g x = x x x x x
我還注意到fx = xx ... xx
(many x
s) 之類的東西仍然具有相同的類型。 誰能解釋為什么會這樣?
關鍵是x :: a -> b
是一個可以提供任何類型值的函數,無論給出什么參數。 這意味着x
可以應用於自身,結果可以再次應用於x
,依此類推。
至少,這就是它承諾的類型檢查器可以做的事情。 類型檢查器不關心是否存在任何這樣的值,只關心類型是否對齊。 f
和g
實際上都不能被調用,因為不存在a -> b
類型a -> b
值(忽略 bottom 和unsafeCoerce
)。
這不應該比以下事實更令人驚訝
m :: (∀ a . a) -> (∀ a . a) -> (Int, Bool)
m p q = (p, q)
具有相同的類型
n :: (∀ a . a) -> (∀ a . a) -> (Int, Bool)
n p q = (q, p)
就像在您的示例中一樣,這是有效的,因為通用量化參數可以以多種不同的方式使用,編譯器在每種情況下都會選擇適當的類型並強制x
充當具有該類型的角色。
這實際上是一個相當人為的情況,因為像∀ a . a
這樣的類型∀ a . a
∀ a . a
或∀ ab . a->b
∀ ab . a->b
是無人居住的(模⊥),因此您實際上永遠無法使用帶有此類參數的 RankN 函數; 實際上,那時你甚至不會寫它!
實用的 RankN 函數通常會在其參數中強加一些額外的結構或類型類約束,例如
foo :: (∀ a . [a] -> [a]) -> ...
或者
qua :: (∀ n . Num n -> Int -> n -> n) -> ...
每當我們使用具有多態類型的變量(例如您的x
)時,都可以觀察到這種現象。 身份函數id
可能是最著名的例子。
id :: forall a . a -> a
在這里,所有這些表達式都進行類型檢查,並且類型為Int -> Int
:
id :: Int -> Int
id id :: Int -> Int
id id id :: Int -> Int
id id id id :: Int -> Int
...
這怎么可能? 好吧,關鍵是每次我們寫id
我們實際上是指“應該從上下文中推斷出的某個未知類型a
上的身份函數”。 至關重要的是,每次使用id
都有自己的a
。
讓我們寫id @T
來表示類型T
上的特定標識函數。
寫作
id :: Int -> Int
實際上是指
id @Int :: Int -> Int
這很簡單。 相反,寫
id id :: Int -> Int
實際上是指
id @(Int -> Int) (id @Int) :: Int -> Int
其中第一個id
現在指的是函數空間Int -> Int
! 而且當然,
id id id :: Int -> Int
方法
(id @((Int -> Int) -> (Int -> Int))) (id @(Int -> Int)) (id @Int) :: Int -> Int
等等。 我們沒有意識到類型會變得如此混亂,因為 Haskell 會為我們推斷這些類型。
在您的具體情況下,
g :: (forall a b. a -> b) -> c
g x = x x x x x
我們可以通過多種方式進行類型檢查。 一種可能的方法是定義A ~ Int
, B ~ Bool
, T ~ (A -> B)
然后推斷:
g x = x @T @(T -> T -> T -> c) (x @A @B) (x @A @B) (x @A @B) (x @A @B)
我建議花一些時間來了解所有類型的檢查。 (此外,我們對A
和B
選擇是完全任意的,我們可以在那里使用任何其他類型。我們甚至可以為每個x
使用不同的A
s 和B
s,只要第一個x
被適當地實例化!)
很明顯,即使當xxx ...
是一個更長的序列時,這種推斷也是可能的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.