[英]How does Haskell type-check infinite recursive values?
定義此數據類型:
data NaturalNumber = Zero | S NaturalNumber
deriving (Show)
在Haskell中(使用GHC編譯),此代碼將在沒有警告或錯誤的情況下運行:
infinity = S infinity
inf1 = S inf2
inf2 = S inf1
因此,遞歸和相互遞歸的無限深度值都會通過類型檢查。
但是,以下代碼給出了錯誤:
j = S 'h'
錯誤狀態Couldn't match expected type 'NaturalNumber' with actual type 'Char'
。 即使我設置,(相同)錯誤仍然存在
j = S (S (S (S ... (S 'h')...)))
有一百個左右的嵌套S
Haskell如何判斷infinity
是NaturalNumber
的有效成員,但j
不是?
有趣的是,它還允許:
bottom = bottom
k = S bottom
Haskell只是試圖證明一個程序的不正確性,如果它不能這樣做,那么允許它嗎? 或者Haskell的類型系統不是圖靈完整的,所以如果它允許程序那么程序可證明(在類型級別)正確嗎?
(如果類型系統(在Haskell的形式語義中,而不僅僅是類型檢查器)是Turing完成的,那么它將無法實現某些正確類型的程序是正確的,或者某些錯誤輸入的程序是不正確的,因為它的不可判定性停止問題。)
好
S :: NaturalNumber -> NaturalNumber
在
infinity = S infinity
我們首先假設什么:我們為infinity
分配一些未解決的類型_a
並試圖找出它是什么。 我們知道我們已經將S
應用於infinity
,所以_a
必須是構造函數類型中的箭頭左側的任何內容,即NaturalNumber
。 我們知道infinity
是應用S
的結果,所以infinity :: NaturalNumber
,再次(如果我們為這個定義得到兩個沖突的類型,我們必須發出一個類型錯誤)。
類似的推理適用於相互遞歸的定義。 inf1
必須是NaturalNumber
因為它在inf2
顯示為S
的參數; inf2
必須是NaturalNumber
因為它是S
的結果; 等等
通用算法是分配定義未知類型(值得注意的例外是文字和構造函數),然后通過查看每個定義的使用方式來創建對這些類型的約束。 例如,這必須是某種形式的列表,因為它是reverse
,並且這必須是一個Int
因為它用於從IntMap
查找值等。
如果是
oops = S 'a'
'a' :: Char
因為它是一個文字,但是,我們也必須有'a' :: NaturalNumber
因為它被用作S
的參數。 我們得到明顯的偽造約束,即文字的類型必須都是Char
和NaturalNumber
,這會導致類型錯誤。
並在
bottom = bottom
我們從bottom :: _a
開始。 唯一的約束是_a ~ _a
,因為正在使用類型_a
( bottom
)的值,其中期望類型為_a
的值(在bottom
定義的RHS上)。 由於沒有什么可以進一步約束類型,因此未解決的類型變量是通用的 :它被通用量詞綁定以生成bottom :: forall a. a
bottom :: forall a. a
。
注意在推斷bottom
類型時,上面bottom
兩種用法如何具有相同的類型( _a
)。 這打破了多態遞歸:其定義中每次出現的值都與定義本身的類型相同。 例如
-- perfectly balanced binary trees
data Binary a = Leaf a | Branch (Binary (a, a))
-- headB :: _a -> _r
headB (Leaf x) = x -- _a ~ Binary _r; headB :: Binary _r -> _r
headB (Branch bin) = fst (headB bin)
-- recursive call has type headB :: Binary _r -> _r
-- but bin :: Binary (_r, _r); mismatch
所以你需要一個類型簽名:
headB :: {-forall a.-} Binary a -> a
headB (Leaf x) = x
headB (Branch bin) = fst (headB {-@(a, a)-} bin)
-- knowing exactly what headB's signature is allows for polymorphic recursion
所以:當某些東西沒有類型簽名時,類型檢查器會嘗試為它分配一個類型,如果它遇到偽造的約束,它會拒絕該程序。 當某些東西有一個類型簽名時,類型檢查器會進入它,以確保它是正確的(如果你更願意這樣想的話,試圖證明它是錯誤的)。
Haskell的類型系統不是Turing完整的,因為有很多語法限制來防止例如類型lambdas(沒有語言擴展),但它不足以確保所有程序運行完成而沒有錯誤,因為它仍然允許底部(更不用說所有不安全的功能)。 它提供了較弱的保證,如果程序在不使用不安全功能的情況下運行完成,它將保持類型正確。 在GHC下,通過足夠的語言擴展,類型系統確實成為圖靈完整的。 我不認為它允許通過不良類型的程序; 我認為你能做的最多就是將編譯器拋入無限循環。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.