簡體   English   中英

“未定義”如何在Haskell中起作用

[英]how does 'undefined' work in Haskell

我很好奇Haskell中的'undefined'值。 它很有趣,因為你可以把它放在任何地方,而Haskell會很開心。 以下都是好的

[1.0, 2.0, 3.0 , undefined]  ::[Float]
[1, 2 ,3 undefined, 102312] :: [Int]
("CATS!", undefined)  :: (String, String)
....and many more

未定義的工作如何在引擎蓋下工作? 什么使得擁有每種數據類型的數據成為可能? 我可以定義一個這樣的值,我可以放在任何地方,或者這是一個特例?

undefined沒什么特別之處。 它只是一個特殊的價值 - 您可以用無限循環,崩潰或段錯誤來表示它。 寫它的一種方法是崩潰:

undefined :: a
undefined | False = undefined

或循環:

undefined = undefined

它是一種特殊的價值,可以是任何類型的元素。

由於Haskell很懶,你仍然可以在計算中使用這些值。 例如

 > length [undefined, undefined]
 2

但除此之外,它只是多態性和非嚴格性的自然結果。

您正在檢查有趣的屬性是undefined的類型是a對任何類型的a選擇我們,即undefined :: a沒有約束。 正如其他人所指出的, undefined可能被認為是錯誤或無限循環。 我想說它最好被認為是“空洞的真實陳述”。 在與停止問題密切相關的任何類型系統中,這是一個幾乎不可避免的漏洞,但從邏輯的角度考慮它是很有趣的。


考慮使用類型進行編程的一種方法是,這是一個難題。 有人給你一個類型,並要求你實現一個這種類型的函數。 例如

Question:    fn  ::  a -> a
Answer:      fn  =  \x -> x

很容易。 我們需要為任何類型a生成一個a ,但是我們給了一個作為輸入,所以我們可以返回它。

undefined ,這個游戲總是很容易

Question:    fn  ::  Int -> m (f [a])
Answer:      fn  =  \i   -> undefined    -- backdoor!

所以讓我們擺脫它。 當你生活在沒有它的世界里時,理解undefined是最容易的。 現在我們的游戲越來越難了。 有時它是可能的

Question:    fn  :: (forall r. (a -> r) -> r) -> a
Answer:      fn  =  \f                        -> f id

但突然間,有時也不可能!

Question:    fn  ::  a -> b
Answer:      fn  =   ???                  -- this is `unsafeCoerce`, btw.
                                          -- if `undefined` isn't fair game,
                                          -- then `unsafeCoerce` isn't either

或者是嗎?

-- The fixed-point combinator, the genesis of any recursive program

Question:    fix  ::  (a -> a) -> a
Answer:      fix  =   \f       -> let a = f a in a

                                          -- Why does this work?
                                          -- One should be thinking of Russell's 
                                          -- Paradox right about now. This plays
                                          -- the same role as a non-wellfounded set.

這是合法的,因為Haskell的let綁定是懶惰的並且(通常)是遞歸的 現在我們是金色的。

Question:    fn   ::  a -> b
Answer:      fn   =  \a -> fix id         -- This seems unfair?

即使沒有undefined內置,我們也可以使用任何舊的無限循環在我們的游戲中重建它。 類型結帳。 為了真正防止我們在Haskell中使用undefined ,我們需要解決Halting問題。

Question:    undefined  ::  a
Answer:      undefined  =   fix id

現在,正如您所見, undefined對於調試很有用,因為它可以是任何值的占位符。 令人遺憾的是,操作非常糟糕,因為它會導致無限循環或立即崩潰。 這對我們的游戲來說也很糟糕,因為它讓我們作弊。 最后,我希望我已經證明,只要您的語言具有(可能是無限的)循環,就很難 undefined

存在像Agda和Coq這樣的語言來交換掉循環以便真正消除undefined 他們這樣做是因為我發明的這個游戲在某些情況下實際上可能非常有價值。 它可以編碼邏輯語句,因此可用於形成非常嚴格的數學證明。 您的類型代表定理,您的程序保證該定理得到證實。 undefined的存在意味着所有定理都是可證明的,因此使整個系統不可靠。

但是在Haskell中,我們對循環比對校對更感興趣,所以我們寧願fix而不是確定沒有undefined

如果我在GHCi中嘗試undefined ,我會得到一個例外:

Prelude> undefined
*** Exception: Prelude.undefined

因此,我認為它只是實現為拋出異常:
http://www.haskell.org/ghc/docs/latest/html/libraries/base/Control-Exception.html#g:2

undefined如何工作的? 嗯,最好的答案,恕我直言,是它不起作用 但要理解這個答案,我們必須解決其后果,這對新手來說並不明顯。

基本上,如果我們有undefined :: a ,那對於類型系統來說意味着undefined可以出現在任何地方。 為什么? 因為在Haskell中,每當您看到具有某種類型的表達式時,您可以通過將任何類型變量的所有實例替換為任何其他類型來專門化該類型。 熟悉的例子是這樣的:

map :: (a -> b) -> [a] -> [b]

-- Substitute b := b -> x
map :: (a -> b -> c) -> [a] -> [b -> c]

-- Substitute a := Int
map :: (Int -> b -> c) -> [Int] -> [b -> c]

-- etc.

map的情況下,這是如何工作的? 嗯,這歸結於map的參數提供了產生答案所必需的一切,無論我們為其類型變量做了什么替換和特化。 如果你有一個列表和一個消耗與列表元素相同類型的值的函數,你可以做map map,period。

但在情況undefined :: a ,這是什么簽名會的意思是,不管是什么類型的a可能會專門到, undefined是能夠生產該類型的值。 怎么辦呢? 嗯,實際上,它不能,所以如果一個程序實際上達到了需要undefined值的步驟,就沒有辦法繼續。 程序在這一點上唯一能做的就是失敗

這個案例背后的故事是相似但不同的:

loop :: a
loop = loop

在這里,我們可以通過這個瘋狂的論證來證明loop具有類型a :假設loop具有類型a 它需要生成類型a的值。 怎么辦呢? 簡單,它只是調用loop 普雷斯托!

聽起來很瘋狂吧? 嗯,問題在於它與map定義的第二個等式中的情況沒有什么不同:

map :: (a -> b) -> [a] -> [b]
map f [] = []
map f (x:xs) = f x : map f xs

在第二個等式中, fx具有類型b ,並且(fx:)具有類型[b] -> [b] ; 現在總結我們的證據,即map確實具有我們的簽名聲明的類型,我們需要產生[b] 那我們怎么做呢? 通過假設map具有我們試圖證明它的類型!

Haskell的類型推斷算法的工作方式是它首先猜測表達式的類型是a ,然后它只在找到與該假設相矛盾的東西時改變其猜測。 undefined a因為它是一個平坦的謊言。 loop typechecks到a因為允許遞歸,所有loop都是遞歸。


編輯:到底是什么,我不妨拼出一個例子。 以下是如何根據此定義推斷map類型的非正式演示:

map f [] = []
map f (x:xs) = f x : map f xs

它是這樣的:

  1. 我們首先暫時假設map :: a
  2. 但是map有兩個參數,所以a不能是類型。 我們修改了我們的假設: map :: a -> b -> c; f :: a map :: a -> b -> c; f :: a
  3. 但正如我們在第一個等式中所看到的,第二個參數是一個列表: map :: a -> [b] -> c; f :: a map :: a -> [b] -> c; f :: a
  4. 但正如我們在第一個等式中所看到的,結果也是一個列表: map :: a -> [b] -> [c]; f :: a map :: a -> [b] -> [c]; f :: a
  5. 在第二個等式中,我們將第二個參數與構造函數(:) :: b -> [b] -> [b]模式匹配。 這意味着在該等式中, x :: bxs :: [b]
  6. 考慮第二個等式的右側。 由於map f (x:xs)必須是[c]類型,這意味着fx : map f xs也必須是[c]類型。
  7. 給定構造函數的類型(:) :: c -> [c] -> [c] ,這意味着fx :: cmap f xs :: [c]
  8. 在(7)中我們得出結論, map f xs :: [c] 我們假設在(6)中,如果我們在(7)中另有結論,這將是一個類型錯誤。 我們現在也可以深入研究這個表達式,看看這需要fxs有哪些類型,但為了使故事更長,所有內容都要檢查。
  9. 由於fx :: cx :: b ,我們必須得出結論f :: b -> c 所以現在我們得到map :: (b -> c) -> [b] -> [c]
  10. 我們完成了。

相同的過程,但對於loop = loop

  1. 我們暫時假設loop :: a
  2. loop不帶任何參數,那么它的類型很符合a至今。
  3. loop的右側是loop ,我們暫時分配了類型a ,以便檢出。
  4. 沒有什么可以考慮的; 我們完成了。 loop有類型a

好吧,基本上undefined = undefined - 如果你嘗試評估它,你會得到一個無限循環。 但是Haskell是一種非嚴格的語言,因此head [1.0, 2.0, undefined]不會評估列表中的所有元素,因此它會打印1.0並終止。 但是如果你打印show [1.0, 2.0, undefined] [1.0,2.0,*** Exception: Prelude.undefined show [1.0, 2.0, undefined] ,你會看到[1.0,2.0,*** Exception: Prelude.undefined

至於它是如何可以是所有類型的......好吧,如果表達式是A類型,則意味着評估它將產生類型A值,或者評估將發散,根本不產生任何值。 現在, undefined總是分歧,所以它適合每個可以想象的類型A定義。

此外,關於相關主題的一篇好博文: http//james-iry.blogspot.ru/2009/08/getting-to-bottom-of-nothing-at-all.html

關於undefined,有兩點需要注意:

  • 你幾乎可以在任何地方放置undefined,typechecker會很高興。 這是因為undefined有類型(forall a.a)。
  • 您幾乎可以在任何地方放置undefined,並且在運行時它將具有一些明確定義的表示。

第二, GHC評論清楚地說:

⊥的表示必須是一個指針:它是一個在評估時拋出異常或進入無限循環的對象。

有關詳細信息,您可能需要閱讀Paper Spinless Tagless G-Machine

暫無
暫無

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

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