使用也许和作家一起

问题描述:

这里是我的鸡蛋包装厂:使用也许和作家一起

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 =>添加2'允许您省略纯净。 – Gurkenglas

在第一示例中,在表达式中的第二>>=是用于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

您只要撰写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

我会改变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 

按预期工作。