2016-07-05 59 views
4

這裏是我的雞蛋包裝廠:使用也許和作家一起

type Eggs = Int 
data Carton = Carton Eggs deriving Show 

add :: Eggs -> Carton -> Maybe Carton 
add e (Carton c) 
    | c + e <= 12 = Just (Carton $ c + e) 
    | otherwise = Nothing 

main = do 
    print $ pure(Carton 2) >>= add 4 >>= add 4 >>= add 3 

似乎運作良好,我可以很好地連鎖add功能。

但我想記錄每一步添加多少個雞蛋的日誌。所以我這樣做:

import Control.Monad.Writer 

type Eggs = Int 
data Carton = Carton Eggs deriving Show 

add :: Eggs -> Carton -> Writer [String] (Maybe Carton) 
add e (Carton c) 
    | c + e <= 12 = do 
     tell ["adding " ++ show e] 
     return (Just (Carton $ c + e)) 
    | otherwise = do 
     tell ["cannot add " ++ show e] 
     return Nothing 

main = do 
    let c = add 4 $ Carton 2 
    print $ fst $ runWriter c 
    mapM_ putStrLn $ snd $ runWriter c 

這給了我,我想:我可以看到添加所產生的紙箱和4個雞蛋的記錄。

但我似乎已經失去了能力,鏈add功能再像以前那樣:

let c = pure(Carton 2) >>= add 4 -- works 
let c = pure(Carton 2) >>= add 4 >>= add 2 -- does not work 

我怎麼能連鎖我的新啓用作家add的功能呢?有沒有更好的方法來做到這一點?

+0

注意,寫它作爲'加4> =>添加2 $紙箱2'或添加2 <= <添加4 $紙箱2'或使用不常見的&,'紙箱2&添加4> =>添加2'允許您省略純淨。 – Gurkenglas

回答

1

在第一示例中,在表達式中的第二>>=是用於MaybeMonad實例,同時在第二個例子是從WriterMonad實例。具體而言,在第一個示例中,>>=需要類型爲Carton -> Maybe Carton的函數,如add 2,而在第二個示例中>>=需要類型爲Maybe Carton -> Writer [String] (Maybe Carton)的函數。在這兩個例子中,pure (Carton 2) >> = add 4因爲pure (Carton 2)的類型爲Maybe Cartonadd 4的類型爲Carton -> <something>,所以你沒有問題。將另一個>>=添加到表達式會觸發該錯誤,因爲在第一個示例中,>>=與第一個示例具有相同類型,而在第二個示例中它不相同>>=。一個解決方案可以是改變add使得其具有輸入Eggs -> Maybe Carton -> Writer [String] (Maybe Carton)

add :: Eggs -> Maybe Carton -> Writer [String] (Maybe Carton) 
add e Nothing = return Nothing 
add e (Just (Carton c)) 
    | c + e <= 12 = do 
     tell ["adding " ++ show e] 
     return (Just (Carton $ c + e)) 
    | otherwise = do 
     tell ["cannot add " ++ show e] 
     return Nothing 

注意,這意味着你不能再使用pure (Carton 2),但你需要pure (Just $ Carton 2)

> pure (Just $ Carton 2) >>= add 2 >>= add 5 
WriterT (Identity (Just (Carton 9),["adding 2","adding 5"])) 

說,我會建議你使用monad transformers來編寫MaybeWriter,因爲這在它們的通用用例中。您的例子可以改寫爲

import Control.Monad.Trans.Maybe 
import Control.Monad.Writer 

type Eggs = Int 
data Carton = Carton Eggs deriving Show 

add :: Eggs -> Carton -> MaybeT (Writer [String]) Carton 
add e (Carton c) 
    | c + e <= 12 = do 
     lift $ tell ["adding " ++ show e] 
     return (Carton $ c + e) 
    | otherwise = do 
     lift $ tell ["cannot add " ++ show e] 
     mzero 

main = do 
    let c = return (Carton 2) >>= add 4 >>= add 2 
    let result = runWriter $ runMaybeT c 
    print $ fst $ result 
    mapM_ putStrLn $ snd $ result 

有幾件事情從你的例子改爲:

  1. MaybeT m a是單子轉換。在這個例子中,mWriter [String]aCarton。爲了運行所有我們首先runMaybeT,它給你一個Writer [String] (Maybe Carton),然後我們打電話給runWriter就像你在你的例子中所做的那樣。
  2. 要使用Writer函數MaybeT (Writer [String])我們需要lift他們。例如lift $ tell ["something"]
  3. return carton用於返回Just Cartonmzero用於返回Nothing

最後一兩件事:在這個例子中,我們不能組成MaybeWriter周圍的其他方式,WriterT [String] Maybe Carton,因爲當有超過12個雞蛋runWriterT將返回Nothing和抑制歷史:

import Control.Monad 
import Control.Monad.Trans 
import Control.Applicative 
import Control.Monad.Trans.Maybe 
import Control.Monad.Trans.Writer 

type Eggs = Int 
data Carton = Carton Eggs deriving Show 

add :: Eggs -> Carton -> WriterT [String] Maybe Carton 
add e (Carton c) 
    | c + e <= 12 = do 
     tell ["adding " ++ show e] 
     lift $ Just $ Carton $ c + e 
    | otherwise = do 
     tell ["cannot add " ++ show e] 
     lift Nothing 

main = do 
    let c = return (Carton 2) >>= add 4 >>= add 20 
    case runWriterT c of 
     Nothing -> 
     print "nothing to print" 
     Just (carton, history) -> do 
     print carton 
     mapM_ putStrLn $ history 
+0

這是我期待的確切答案。你能用一個monad變壓器舉個例子嗎? – zoran119

+0

極好的例子。非常感謝你。我似乎可以在沒有'lift'的情況下運行'MaybeT(Writer [String])Carton'示例。 – zoran119

+0

'tell'適用於'Writer'的更通用版本,名爲['MonadWriter'](https://hackage.haskell.org/package/mtl-2.2.1/docs/Control-Monad-Writer-Class.html )。 'MaybeT'有一個'MonadWriter'的實例,這意味着你可以直接使用'tell'來代替'MaybeT',而不是'Writer'版本。在實例本身中,['tell'被定義爲'lift'。告訴'](https://hackage.haskell.org/package/mtl-2.2.1/docs/src/Control-Monad-Writer-Class.html#line-151),這是我在這裏使用的。我認爲明確的構圖有助於理解這個例子。 – mariop

5

您只要撰寫addMaybeT

import Control.Trans.Monad.Maybe 

test = pure (Carton 2) >>= MaybeT . add 3 
         >>= MaybeT . add 4 
         >>= MaybeT . add 5 

runTest = do 
    print $ fst $ runWriter (runMaybeT test) 

完整的示例在:http://lpaste.net/169070

5

我會改變add & C到使用MaybeT (Writer [String])

import Control.Monad.Writer 
import Control.Monad.Trans.Maybe 

type Eggs = Int 
data Carton = Carton Eggs deriving Show 

main = do 
    let c = add 4 $ Carton 2 
     (result, log) = runWriter $ runMaybeT c 
    print result 
    mapM_ putStrLn log 

add :: Eggs -> Carton -> MaybeT (Writer [String]) Carton 
add e (Carton c) 
    | c + e <= 12 = do 
      tell ["adding " ++ show e] 
      return $ Carton $ c + e 
    | otherwise = do 
      tell ["cannot add " ++ show e] 
      mzero 

這將使您的原始代碼

pure (Carton 2) >>= add 4 >>= add 2 

按預期工作。

相關問題