2013-07-26 42 views
4

這是一個簡單的問題,我假設一個複雜的答案。Haskell中的前提條件檢查有哪些選項

一個非常常見的編程問題是一個函數返回的東西,或失敗的先決條件檢查。在Java中我會使用拋出IllegalArgumentException一些assert函數的方法開始,像這樣:

{ 
    //method body 
    Assert.isNotNull(foo); 
    Assert.hasText(bar) 
    return magic(foo, bar); 
} 

我喜歡這個,這是每一個前提一oneliner。我不喜歡這個是拋出異常(因爲異常〜goto)。

在Scala中,我和Either一起工作,這有點笨重,但比拋出異常要好。

有人向我建議:

putStone stone originalBoard = case attemptedSuicide of 
    True -> Nothing 
    False -> Just boardAfterMove 
    where { 
    attemptedSuicide = undefined 
    boardAfterMove = undefined 
    } 

我不喜歡的是,重點放在在真實與虛假,這本身沒什麼意思; attemptedSuicide的先決條件是隱藏在語法之間,所以沒有明確的關聯,Nothing和putStone(boardAfterMove)的實際實現並不明確是核心邏輯。啓動它不會編譯,但我相信這不會破壞我的問題的有效性。

什麼是前提條件檢查可以在Haskell乾淨地完成的方式?

+0

我敢肯定,我甚至沒有問這個問題,我需要改進我的例子的語法。請耐心等待,我昨天晚上開始學習Haskell,同時我也在做一個苛刻的工作。我會根據自己的進步和當然的反饋來改進這個問題。 – iwein

回答

5

你有兩個選擇:

  1. 編碼您的先決條件在你的類型,使他們在編譯時檢查。
  2. 在運行時檢查您的先決條件是否成立,以便在做出令人討厭和意外的事情之前停止您的程序。加布裏埃爾岡薩雷斯詳細地顯示了這一點his answer

選項1當然是首選,但它並不總是可能的。例如,你不能在Haskell的類型系統中說一個參數比另一個參數大,等等。但是你仍然可以表達很多,通常遠遠超過其他語言。也有使用所謂的dependent types的語言,它們允許您在其類型系統中表達任何條件。但他們大多是實驗或研究工作。如果您有興趣,我建議您閱讀Adam Chlipala的書Certified Programming with Dependent Types

否則運行時檢查更容易,這就是程序員更習慣。在Scala中,您可以在方法中使用require並從相應的異常中恢復。在Haskell中這很棘手。例外情況(由失敗的花樣守衛引起,或通過撥打errorundefined發出)基於其性質IO,因此只有IO代碼可以捕獲它們。

如果您懷疑代碼可能由於某些原因而失敗,最好使用MaybeEither向調用者發送失敗信號。缺點是這會使代碼更復雜,可讀性更差。

一種解決方案是將您的計算嵌入到錯誤處理/報告monad中,如MonadError。然後,您可以乾淨地報告錯誤並將它們捕捉到更高級別的某處。如果你已經在使用monad進行計算,你可以將你的monad包裝成EitherT變壓器。

5

你可以在開始處理在模式守衛的所有先決條件:

putStone stone originalBoard | attemptedSuicide = Nothing 
    where attemptedSuicide = ... 

putStone stone originalBoard = Just ... 
7

在Haskell,與MaybeEither工作比斯卡拉滑頭一點,那麼也許你會重新考慮這種做法。如果你不介意,我會用你的第一個例子來展示這一點。

首先,你通常不會測試null。相反,您只需計算您實際感興趣的屬性,然後使用Maybe來處理失敗。例如,如果你真正想要的是列表的頭,你可以只寫功能:

-- Or you can just import this function from the `safe` package 

headMay :: [a] -> Maybe a 
headMay as = case as of 
    [] -> Nothing 
    a:_ -> Just a 

的東西是純粹的驗證,如hasText,那麼你可以使用guard,它適用於任何MonadPlusMaybe

guard :: (MonadPlus m) => Bool -> m() 
guard precondition = if precondition then return() else mzero 

當你專注guardMaybe單子然後return成爲Justmzero成爲Nothing

guard precondition = if precondition then Just() else Nothing 

現在,假設我們有以下幾種類型:

foo :: [A] 
bar :: SomeForm 

hasText :: SomeForm -> Bool 

magic :: A -> SomeForm -> B 

我們可以處理兩個foobar錯誤和使用do符號爲Maybe單子的magic功能安全地提取值:

example :: Maybe B 
example = do 
    a <- headMay foo 
    guard (hasText bar) 
    return (magic a bar) 

如果您對斯卡拉熟悉,do表示法就像斯卡拉的理解一樣。上面的代碼desugars到:

example = 
    headMay foo >>= \a -> 
     guard (hasText bar) >>= \_ -> 
     return (magic a bar) 

Maybe單子,(>>=)return有以下定義:

m >>= f = case m of 
    Nothing -> Nothing 
    Just a -> f a 

return = Just 

...所以上面的代碼只是短手:

example = case (headMay foo) of 
    Nothing -> Nothing 
    Just a -> case (if (hasText bar) then Just() else Nothing) of 
     Nothing -> Nothing 
     Just() -> Just (magic a bar) 

...你可以簡化到:

example = case (headMay foo) of 
    Nothing -> Nothing 
    Just a -> if (hasText bar) then Just (magic a bar) else Nothing 

......這是你可能用手寫的沒有doguard

+0

感謝您花時間詳細解釋此問題。高度讚賞。 – iwein

+0

不客氣! –

1

我將對此採取更廣泛的視角。

在Haskell我們一般區分三種功能之間:

  • 總計功能保證給正確的結果對所有參數。用你的術語來說,前提條件是用類型編碼的。這是最好的一種功能。其他語言使得編寫這種類型的函數變得困難,例如,因爲您無法消除類型系統中的空引用。

  • 部分功能可以保證給出正確的結果或拋出異常。 「頭」和「尾」是部分功能。在這種情況下,你要記錄Haddock評論的先決條件。您不必擔心測試前提條件,因爲如果違反了前提條件,反正會拋出異常(儘管有時候您會進行冗餘測試以便爲開發人員提供有用的異常消息)。

  • 不安全的功能可能會產生損壞的結果。例如Data.Set模塊包含一個函數「fromAscList」,它假定它的參數已經按升序排序。如果你違反了這個先決條件,那麼你會得到一個損壞的Set而不是一個例外。在Haddock的評論中,不安全的功能應該清楚地標記出來。顯然,通過測試前提條件,你總是可以把一個不安全的函數變成一個局部函數,但是在很多情況下,這個不安全函數的全部意義在於這對於某些客戶來說太貴了,所以你給他們提供了不安全的函數並給出了適當的警告。

因爲Haskell值是不可變的,所以在執行不變量時通常不會有困難。假設在Java中,我有一個擁有一個Bar的Foo類,而Foo有一些額外的數據必須與Bar的內容保持一致。如果代碼的某些其他部分在不更新Foo的情況下修改了Bar,那麼這些不變量就會被Foo的作者無法阻止的方式所侵犯。 Haskell沒有這個問題。因此,您可以創建具有由其創建者函數強制執行的內部不變量的複雜結構,而無需擔心其他代碼違反這些不變量。 Data.Set再一次提供了這種代碼的例子; Data.Set中的全部函數無需擔心檢查Set對象的有效性,因爲可以創建Set的唯一函數位於同一個模塊中,因此可以信任它以使其正確。

部分和不安全之間的一個折衷辦法是使用「Control.Exception.assert」,GHC將其視爲一種特殊情況,爲斷言失敗提供有用的錯誤消息,但在打開優化時禁用檢查。詳情請參閱the GHC docs

+0

由於結構,背景和清晰度,我真的很想將此標記爲正確的答案。從這裏其餘的自然而然。我正在尋找一些代碼示例,因爲在這裏有很多左手一樣的練習。如果我找到時間,我會自己添加它們,然後將答案標記爲正確。 – iwein