[英]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函數的初稿。 然后,我會把它清理干凈。 首先,一些小事:因為Applicative
是Monad
的超類,我更喜歡pure
的return
; 我們沒有使用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 =<< ma
, ma >>= 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 b
或do ma ; pure b
do ma ; pure b
, b <$ 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.