簡體   English   中英

Haskell:運行兩個monad,保留第一個monad的結果

[英]Haskell: Run two monads, keep the result of the first one

和Haskell一起玩,現在我嘗試創建一個類似的函數

keepValue :: (Monad m) => m a -> (a -> m b) -> m a

具有以下語義:它應該將monad值應用於函數,該函數返回第二個monad,並保留第一個monad的結果 ,但是第二個monad的效果

對於Maybe monad,我有一個工作函數:

keepValueMaybe :: Maybe a -> (a -> Maybe b) -> Maybe a

keepValue ma f = case ma >>= f of
    Nothing -> Nothing
    Just _ -> ma

因此,如果第一個值為Nothing ,則不運行該函數(因此沒有第二個副作用),但如果第一個值為Just ,則運行該函數(帶副作用)。 我保持第二次計算的效果 (例如, Nothing使整個表達式為Nothing ),但是原始值。

現在我好奇。 它適用於任何monad嗎?

它看起來有點內置>> ,但我在標准庫中找不到任何東西。

讓我們來看看吧!

keepValue :: Monad m => m a -> (a -> m b) -> m a
keepValue ma f = _

那么我們想要keepValue做什么呢? 好吧,我們應該做的第一件事是使用ma ,所以我們可以將它連接到f

keepValue :: Monad m => m a -> (a -> m b) -> m a
keepValue ma f = do
  a <- ma
  _

現在我們有一個類型a a的值va ,所以我們可以將它傳遞給f

keepValue :: Monad m => m a -> (a -> m b) -> m a
keepValue ma f = do
  va <- ma
  vb <- f va
  _

最后,我們想要生成va ,所以我們可以這樣做:

keepValue :: Monad m => m a -> (a -> m b) -> m a
keepValue ma f = do
  va <- ma
  vb <- f va
  return va

這就是我如何編寫像這樣的任何monadic函數的初稿。 然后,我會把它清理干凈。 首先,一些小事:因為ApplicativeMonad的超類,我更喜歡purereturn ; 我們沒有使用vb ; 我會刪除名字中的v 因此,對於一個do此功能的-notation基礎版本,我認為最好的辦法是

keepValue :: Monad m => m a -> (a -> m b) -> m a
keepValue ma f = do
  a <- ma
  _ <- f a
  pure a

但是,現在我們可以開始更好地實施。 首先,我們可以用顯式調用(>>)替換_ <- f va

keepValue :: Monad m => m a -> (a -> m b) -> m a
keepValue ma f = do
  a <- ma
  f a >> pure a

現在,我們可以應用簡化。 您可能知道我們總是可以用fmap / (<$>)替換(>>=)加上pure / return :任何pure . f =<< ma pure . f =<< mama >>= pure . f ma >>= pure . f ,或do a <- ma ; pure $ fa do a <- ma ; pure $ fa (所有這些都是等價的)可以用f <$> ma替換。 但是, Functor類型類有另一個不太知名的方法(<$)

(<$) :: a -> fb -> fa
用相同的值替換輸入中的所有位置。 默認定義是fmap . const fmap . const ,但這可能會被更高效的版本覆蓋。

所以我們對(<$)有一個類似的替換規則:我們總是可以替換ma >> pure bdo ma ; pure b do ma ; pure bb <$ ma 這給了我們

keepValue :: Monad m => m a -> (a -> m b) -> m a
keepValue ma f = do
  a <- ma
  a <$ f a

而且我認為這是此功能的最短合理版本! 沒有任何好的無點技巧可以讓它變得更干凈; 一個指標是在do塊的第二行上多次使用a


順便提一下,術語說明:你正在運行兩個monadic動作 ,或兩個monadic值 ; 你沒有運行*“兩個單子”。 monad就像Maybe一種支持(>>=)return的類型構造函數。 不要將這些值與類型混合在一起 - 這種術語上的區別有助於使事情更清晰!

這個結構看起來很像Monad Maybe的定義>>= / >>

case foo of
    Nothing -> Nothing
    Just _ -> bar

foo >>= \_ -> bar
foo >> bar

所以你的原始表達可以簡化為

ma >>= f >> ma

這適用於其他monad。

但是,我不認為這實際上是你想要的,因為你可以看到ma出現兩次。 而是從第一個ma >>= bind獲取值,並將其傳遞到計算結束。

keepValue ma f =
    ma >>= \a ->
    f a >>
    return a

do -notation

keepValue ma f = do
    a <- ma
    f a
    return a

你可以定義:

passThrough f = (>>) <$> f <*> pure

然后取而代之

keepValue ma f

ma >>= passThrough f

然后讀取一行並打印兩次(比如說)

getLine >>= passThrough putStrLn >>= putStrLn

暫無
暫無

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

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