簡體   English   中英

Haskell 中的存在與通用量化類型

[英]Existential vs. Universally quantified types in Haskell

這些之間究竟有什么區別? 我想我理解存在類型是如何工作的,它們就像在面向對象中擁有一個基類而沒有向下轉換的方法。 通用類型有何不同?

這里的術語“普遍的”和“存在的”來自謂詞邏輯中同名的量詞。

全稱量化通常寫為 ∀,您可以將其讀作“for all”,大致意思是它的發音:在類似於“∀x. ...”的邏輯語句中,任何代替“...”的東西對於所有可能的“x”都是正確的,您可以從任何正在量化的事物集中進行選擇。

存在量化通常寫為 ∃,您可以將其讀作“存在”,這意味着在類似於“∃x....”的邏輯語句中,任何代替“...”的東西對於某些未指定的都是正確的“x”取自被量化的一組事物。

在 Haskell 中,被量化的東西是類型(至少忽略某些語言擴展),我們的邏輯語句也是類型,而不是“真實”,我們考慮的是“可以實現”。

因此,像forall a. a -> a這樣的通用量化類型forall a. a -> a forall a. a -> a意味着,對於任何可能的類型“a”,我們可以實現一個類型為a -> a的函數。 事實上,我們可以:

id :: forall a. a -> a
id x = x

由於a是普遍量化的,我們對此一無所知,因此無法以任何方式檢查論證。 所以id是該類型(1)唯一可能的函數。

在 Haskell 中,通用量化是“默認”——簽名中的任何類型變量都隱式地通用量化,這就是為什么id的類型通常只寫成a -> a 這也稱為參數多態性,在 Haskell 中通常簡稱為“多態性”,在其他一些語言(例如 C#)中稱為“泛型”。

存在量化類型,如exists a. a -> a exists a. a -> a意味着,對於某些特定類型“a”,我們可以實現一個類型為a -> a的函數。 任何功能都可以,所以我會選擇一個:

func :: exists a. a -> a
func True = False
func False = True

...這當然是布爾值的“非”函數。 但問題是我們不能這樣使用它,因為我們對“a”類型的了解只是它存在。 任何關於它可能是哪種類型的信息都被丟棄了,這意味着我們不能將func應用於任何值。

這不是很有用。

所以,我們可以做什么用func 好吧,我們知道它是一個輸入和輸出類型相同的函數,因此我們可以將它與自身組合,例如。 從本質上講,您可以對具有存在類型的事物做的唯一事情是您可以基於該類型的不存在部分做的事情。 類似地,給定某種類型的東西exists a. [a] exists a. [a]我們可以找到它的長度,或者將它連接到它本身,或者刪除一些元素,或者我們可以對任何列表做的任何其他事情。

最后一點讓我們回到全稱量詞,以及 Haskell (2)沒有直接存在存在類型的原因(我上面的exists完全是虛構的,唉):因為具有存在性量化類型的事物只能與以下操作一起使用有普遍量化的類型,我們可以寫出exists a. a的類型exists a. a exists a. a作為forall r. (forall a. a -> r) -> r forall r. (forall a. a -> r) -> r --in換句話說,所有的結果類型r給出一個函數,用於所有類型的a需要類型的參數a和返回類型的值r我們可以得到一個r類型的結果。

如果您不清楚為什么它們幾乎相等,請注意,對於a而言,整體類型並沒有普遍量化,而是需要一個參數,該參數本身對a進行a普遍量化,然后它可以與它選擇的任何特定類型一起使用.


順便說一句,雖然 Haskell 並沒有通常意義上的子類型的概念,但我們可以將量詞視為表達子類型的一種形式,層次結構從普遍到具體再到存在。 forall a. a類型的東西forall a. a forall a. a可以轉換為任何其他類型,因此可以將其視為所有內容的子類型; 另一方面,任何類型都可以轉換為exists a. a的類型exists a. a exists a. a ,使其成為所有內容的父類型。 當然,前者是不可能的(除了錯誤之外,沒有類型forall a. a值),而后者是無用的(你不能對exists a. a的類型exists a. a做任何事情),但這個類比至少在紙面上有效. :]

請注意,存在類型和通用量化參數之間的等價性與函數輸入的方差翻轉的原因相同。


因此,基本思想大致是,通用量化類型描述對任何類型都有效的事物,而存在類型描述對特定但未知類型有效的事物。


1:嗯,不完全是——僅當我們忽略導致錯誤的函數時,例如notId x = undefined ,包括永不終止的函數,例如loopForever x = loopForever x

2:嗯,GHC。 如果沒有擴展,Haskell 就只有隱含的全稱量詞,根本沒有真正的討論存在類型的方式。

Bartosz Milewski 在他的書中提供了一些關於 Haskell 不需要存在量詞的很好的見解:

在偽 Haskell 中:

 (exists x. pxx) -> c ≅ forall x. pxx -> c

它告訴我們采用存在類型的函數等價於多態函數。 這是完全有道理的,因為這樣的函數必須准備好處理可能以存在類型編碼的任何一種類型。 同樣的原則告訴我們,接受 sum 類型的函數必須作為 case 語句實現,並帶有一個處理程序元組,一個用於 sum 中存在的每個類型。 在這里, sum 類型被一個 coend 替換,並且一系列處理程序變成了一個 end,或者一個多態函數。

因此,Haskell 中存在量化類型的一個例子是

data Sum = forall a. Constructor a    (i.e. forall a. (Constructor_a:: a -> Sum) ≅ Constructor:: (exists a. a) -> Sum)

可以將其視為總和data Sum = int | char | bool | ... data Sum = int | char | bool | ... data Sum = int | char | bool | ... 相比之下,Haskell 中通用量化類型的一個例子是

data Product = Constructor (forall a. a)

可以將其視為產品data Product = int char bool ...

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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