2015-02-23 53 views
5

{-# LANGUAGE LambdaCase #-}作爲類型類實例的函數?

我有一堆以各種方式編碼失敗的函數。例如:

  • f :: A -> Bool回報False失敗
  • g :: B -> Maybe B'回報Nothing失敗
  • h :: C -> Either Error C'回報Left ...失敗

我想鏈以同樣的方式,這些操作爲Maybe單子,所以鏈接函數需要知道每個函數是否失敗,然後才能繼續下一個函數。爲此,我寫了這個類:

class Fail a where 
    isFail :: a -> Bool 
instance Fail() where 
    isFail() = False 
instance Fail Bool where -- a 
    isFail = not 
instance Fail (Maybe a) where -- b 
    isFail = not . isJust 
instance Fail (Either a b) where -- c 
    isFail (Left _) = True 
    isFail _ = False 

但是,它可能是不符合功能存在:

  • f' :: A -> Bool回報True失敗
  • g' :: B -> Maybe Error回報Just Error失敗(Nothing成功)
  • h' :: C -> Either C' Error返回Right ...失敗

這些可以通過簡單地與改造他們,例如功能包它們來補救:

  • f'' = not . f'
  • g'' = (\case Nothing -> Right(); Just e -> Left e) . g'
  • h'' = (\case Left c -> Right c; Right e -> Left e) . h'

然而,鏈接功能的用戶希望能夠結合fghf'g'h'並讓他們只是工作。他不會知道函數的返回類型需要被轉換,除非他查看了他所組合的每個函數的語義,並檢查它們是否與範圍內的任何其他實例匹配。對於普通用戶來說,這是單調乏味而且太微妙的,尤其是對於繞過用戶不得不選擇正確實例的類型推斷而言。

這些函數不是在知道如何使用它們的情況下創建的。所以我可以創建一個類型data Result a b = Fail a | Success b並在每個函數週圍創建包裝。例如:

  • fR = (\case True -> Sucess(); False -> Fail()) . f
  • f'R = (\case False -> Sucess(); True -> Fail()) . f'
  • gR = (\case Just a -> Sucess a; Nothing -> Fail()) . g
  • g'R = (\case Nothing -> Sucess(); Just e -> Fail e) . g'
  • hR = (\case Left e -> Fail e; Right a -> Sucess a) . h
  • h'R = (\case Right e -> Fail e; Left a -> Sucess a) . h'

然而,這種感覺很髒。我們正在做的僅僅是證明/解釋如何在組合函數的上下文中使用f,g,h,f',g'h'中的每一個。是否有更直接的方式來做到這一點?我要的到底是一個方式說,應使用其Fail類型類的實例爲每個功能,即,(使用提供給上述類型類實例的名稱),fagbhc,並f'a'g'b'h'c'爲「無效」的功能,其中a'b'c'被定義爲以下情況(重疊於以前的,所以你需要能夠通過名稱來接他們不知何故):

instance Fail Bool where -- a' 
    isFail = id 
instance Fail (Maybe a) where -- b' 
    isFail = isJust 
instance Fail (Either a b) where -- c' 
    isFail (Right _) = True 
    isFail _ = False 

雖然不一定要通過類特性來完成。也許有一些方法可以做到這一點,而不是類型類?

+1

如果你想鏈接他們像一個monad(與'do'符號),那麼你會需要將它們全部轉換爲單個類型,然後您可以創建一個Monad實例。您聲明您希望類型系統只是在沒有提示或上下文的情況下通過失敗找出函數意味着什麼。理論上,我可以有無數個函數,每個函數都返回一個不同的Integer表示失敗,編譯器應該如何知道特定的Integer何時失敗?它僅作爲上下文具有價值。你不能指望編譯器爲你編寫程序,否則我們都會使用Agda。 – bheklilr 2015-02-23 22:22:12

+0

是的,我不一定要把它變成一個monad,但它會是類似的。我不希望編譯器知道哪些整數是失敗的,我想以某種方式指出哪些整數是'f'的失敗,併爲'f2'指示一個不同的集合,依此類推。例如,在Python中,我可以將一個全局變量映射函數作爲類型實例的一些等價物。那麼如果沒有用戶想要使用的某個函數的映射,它會在運行時崩潰而不是編譯時。但它會更安全。 – 2015-02-23 22:26:00

回答

10

不要這樣做。 Haskell的靜態類型系統和參考透明性爲您提供了非常有用的保證:您可以確定某些特定的值意味着相同的事情,而不管它是如何生成的。對於這種干擾既沒有可變性,也沒有動態風格的表達式的運行時重新解釋」,正如你所期望的那樣。

如果你那裏的那些功能沒有相應地遵守這樣的規範,那麼這很糟糕。更好地擺脫它們(至少,隱藏他們,並只導出重新定義的版本與統一的行爲)。或者告訴用戶他們將不得不一起查找每個用戶的規格。但不要試圖繞開這個破碎定義的特定症狀。

你可以適用於只是「標誌」功能的一個簡單變化,其中失敗意味着它,否則確實是讓他們迴歸這樣的包裹的結果正好相反:

newtype Anti a = Anti { profail :: a } 

instance (Anti a) => Fail (Anti a) where 
    isFail (Anti a) = not $ isFail a 

注意:「同樣的事情」可能非常抽象。有沒有必要爲Left是普遍一個「失敗構造」,這是足夠了很明顯,這是相關聯的第一個類型參數的變型的構造,這是什麼仿/單子實例從它 –工作自動跟隨,這將「意味着」在monadic應用程序中失敗。
即,當你選擇了正確的類型時,東西應該是非常自動的;當你僅僅是tossing around booleans,顯然是相反的,所以也許你應該完全擺脫那些...

+0

我將不得不重新閱讀你的答案,但是,輸出布爾值在我的實際用例中很有用,因爲我不是使用函數來實現輸出是否成功的一元操作。 – 2015-02-24 00:08:17

+1

請注意,base 4.8爲'Data.Monoid'添加了一個'Alt'類型(將'Alternative'變成'Monoid'),所以這可能不是最好的選擇名稱。 – dfeuer 2015-02-24 00:27:42

+0

@og_loc:monadic操作不應該輸出布爾值來表示失敗,只不過是普通函數。對於後者,您使用合適的'Maybe'或'Either'包裝;前者應該用相應的monad變壓器來完成。 – leftaroundabout 2015-02-24 14:03:14