簡體   English   中英

“錯誤”功能的存在如何影響Haskell的純度?

[英]How does the presence of the “error” function bear on the purity of Haskell?

我一直想知道Haskell異常系統如何適應整個“純函數式語言”的東西。 例如,請參閱下面的GHCi會話。

GHCi, version 8.0.1: http://www.haskell.org/ghc/  :? for help
Prelude> head []
*** Exception: Prelude.head: empty list
Prelude> :t head
head :: [a] -> a
Prelude> :t error
error :: [Char] -> a
Prelude> error "ranch"
*** Exception: ranch
CallStack (from HasCallStack):
  error, called at <interactive>:4:1 in interactive:Ghci1
Prelude>

頭的類型是[a] - > a。 但是當你在空列表的特殊情況下調用它時,你會得到一個例外。 但是類型簽名中沒有考慮此異常。

如果我沒記錯的話,在模式匹配過程中出現故障時,情況類似。 類型簽名所說的並不重要,如果你沒有考慮到每種可能的模式,你就有可能拋出異常。

我沒有一個簡明扼要的問題要問,但我的頭腦正在游泳。 將這個奇怪的異常系統添加到其他純粹優雅語言的動機是什么? 它仍然是純凈的,但我只是缺少一些東西? 如果我想利用這個異常功能,我將如何去做(比如我如何捕獲和處理異常?還有什么我可以用它們做的嗎?)例如,如果我編寫使用它的代碼“頭”功能,當然我應該采取預防措施,以一個空列表以某種方式走私自己的情況。

你混淆了兩個概念: 純度總體

  • Purity說功能沒有副作用。
  • Totality說每個函數都終止並產生一個值。

Haskell 純粹,但並不完全。

IO之外,nontermination(例如, let loop = loop in loop )和異常(例如, error "urk!" )是相同的 - 非強制和異常術語,當被強制時,不評估為值。 Haskell的設計者想要一種圖靈完備的語言 - 根據停止問題 - 意味着它們總體上是完整的。 一旦你有了無限制,我想你也可能也有異常 - 定義error msg = error msg並且error調用永遠沒有做任何事情在實踐中比在有限時間內實際看到你想要的錯誤消息更不令人滿意!

但是,一般情況下,你是對的 - 部分函數(那些沒有為每個輸入值定義的函數,比如head )都是丑陋的。 現代Haskell通常更喜歡通過返回MaybeEither值來編寫總函數,例如

safeHead :: [a] -> Maybe a
safeHead []    = Nothing
safeHead (x:_) = Just x

errHead :: [a] -> Either String a
errHead []    = Left "Prelude.head: empty list"
errHead (x:_) = Right x

在這種情況下, FunctorApplicativeMonadMonadErrorFoldableTraversable等機械結合了這些總功能並輕松地使用它們的結果。

您是否真的在代碼中遇到異常 - 例如,您可能會使用error來檢查您認為已執行的代碼中的復雜不變量,但是您有一個錯誤 - 您可以在IO捕獲它。 這回到了為什么可以與IO異常進行交互的問題 - 這是否會使語言不純? 答案與我們為什么可以在IO執行I / O或使用可變變量的問題相同 - 評估IO A類型的值不會產生它描述的副作用,它只是一個動作,描述程序可以做什么。 (互聯網上的其他地方有更好的描述;例外與其他效果沒有任何不同。)

(另請注意, IO中有一個單獨但相關的異常系統例如在嘗試讀取不存在的文件時使用。人們通常可以使用此異常系統,但是因為你'在IO你已經使用了不純的代碼。)

例如,如果我編寫使用“head”函數的代碼,我肯定應該采取預防措施來處理空列表以某種方式走私自己的情況。

一個更簡單的解決方案:不要使用head 有很多替代的: listToMaybeData.Maybe ,在各種不同的備選實施方式中的安全包等部分功能[1]在基庫-特別如那些易於更換為head -比歷史更小應該忽略或替換為安全的變體,例如上述安全包裝中的安全變體。 對於進一步的論證, 這是一個關於部分函數的完全合理的咆哮

如果我想利用這個異常功能,我將如何去做(即如何捕獲和處理異常?還有什么我可以用它們做的嗎?)

error引發的排序異常只能在IO monad中捕獲。 如果您正在編寫純函數,則不希望強制用戶僅在IO monad中運行它們以捕獲異常。 因此,如果您在純函數中使用error ,則假定錯誤不會被捕獲[2]。 理想情況下,您根本不應該在純代碼中使用error ,但如果您不知何故必須這樣做,請至少確保編寫一條信息性錯誤消息(即不是 “Prelude.head:empty list”),以便您的用戶知道程序崩潰時發生了什么。

如果我沒記錯的話,在模式匹配過程中出現故障時,情況類似。 類型簽名所說的並不重要,如果你沒有考慮到每種可能的模式,你就有可能拋出異常。

確實。 唯一的區別就是使用head來自己編寫不完整的模式匹配(\\(x:_) -> x) ,在后一種情況下編譯器至少會警告你,如果你使用-Wall ,而head甚至是在地毯下掃過。

我一直想知道Haskell異常系統如何適應整個“純函數式語言”的東西。

從技術上講,部分功能不會影響純度(當然,這並不會使它們變得不那么令人討厭)。 從理論的角度來看, head []foo = let x = x in x類的東西一樣未定義。 (進一步閱讀這些微妙之處的關鍵詞是“底部” 。)


[1]:部分函數是函數,就像head一樣,沒有為它們應該采用的參數類型的某些值定義。

[2]:值得一提的是IO中的異常是一個完全不同的問題,因為你不能僅僅通過使用更好的函數來避免例如文件讀取失敗。 以合理的方式處理這些場景有很多方法。 如果你對這個問題感到好奇, 這里有一篇關於它的“高度自以為是”的文章說明了相關的工具和權衡。

Haskell不要求你的函數是完整的,也不要追蹤它們何時不是。 (總函數是那些為其輸入類型的每個可能值都有明確定義的輸出的函數)

即使沒有異常或模式匹配失敗,您也可以擁有一個函數,通過永遠繼續,不會為某些輸入定義輸出。 一個例子是length (repeat 1) 這將繼續永遠計算,但從未實際拋出錯誤。

Haskell語義“應對”的方式是聲明每個類型都有一個“額外”值; 所謂的“ 底值 ”,並聲明任何未正確完成並產生其類型的正常值的計算實際上產生底值。 它是由數學符號⊥(僅當談到 Haskell的代表;有沒有真正在Haskell任何方式直接引用此值,但是undefined也經常使用,因為這綁定到一個錯誤認識一個Haskell名計算,從而在語義上產生底值)。

是系統中的理論瑕疵,因為它使您能夠創建任何類型的“值”(雖然不是非常有用的),並且基於類型實際依賴的代碼位數的許多推理假設你不能做到這一點(如果你進入純函數程序和形式邏輯之間的Curry-Howard同構,⊥的存在使你有能力“證明”邏輯矛盾,從而絕對證明什么都沒有)。

但是在實踐中似乎可以解決所有通過假裝⊥在Haskell中不存在所做的推理通常仍然能夠很好地工作,當你編寫“表現良好”的代碼時,它不會非常使用⊥ 。

在Haskell中容忍這種情況的主要原因是易於使用作為編程語言而不是形式邏輯或數學系統。 不可能使編譯器能夠實際告訴任何類似Haskell的代碼,無論每個函數是全部還是部分(參見暫停問題)。 因此,一種想要強制執行整體性的語言必須要么刪除許多你可以做的事情,要么要求你跳過很多箍來證明你的代碼總是終止,或者兩者兼而有之。 Haskell設計師不想這樣做。

因此,考慮到Haskell作為一種語言被置於偏袒和⊥,它也可能會給你帶來像error這樣的東西作為一種便利。 畢竟,你總是可以通過不終止來寫一個error :: String -> a函數; 立即打印出錯誤消息而不是讓程序永遠旋轉對於練習程序員來說更有用,即使這些在Haskell語義理論中都是等價的!

類似地,Haskell的原始設計者決定隱式地為每個模式匹配添加一個包含所有模式匹配的情況,只是錯誤輸出比強制程序員每次期望他們的代碼的一部分只看到它們時明確地添加錯誤情況更方便某些情況。 (盡管包括我在內的很多Haskell程序員都使用了不完整模式匹配警告,並且幾乎總是將其視為錯誤並修復他們的代碼,因此可能更喜歡原始的Haskell設計者在這一方面采取另一種方式) 。

TLDR ; error和模式匹配失敗的例外是為了方便起見,因為它們不會使系統破碎而不是已經存在,而不是與Haskell完全不同的系統。


可以通過使用來自Control.Exception的工具,通過拋出和捕獲異常進行編程,包括從error或模式匹配失敗中捕獲異常。

為了不破壞系統的純度,你可以從任何地方引發異常(因為系統總是必須處理函數不能正確終止和產生值的可能性;“引發異常”只是另一種方式可能發生),但異常只能通過IO的構造捕獲。 因為IO的形式語義基本上允許發生任何事情(因為它必須與現實世界接口,並且我們可以從Haskell的定義中實施任何硬限制),我們也可以放寬大多數規則我們通常需要Haskell中的純函數,並且仍然具有技術上適合Haskell代碼純模型的東西。

我根本沒有使用過這個(通常我更喜歡使用Haskell的語義模型中定義比IO操作模型更明確的東西來保持我的錯誤處理,這可以像Maybe或者簡單一樣簡單。 Either ),但如果你願意,你可以閱讀它。

暫無
暫無

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

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