2017-02-15 58 views
5

結果哈斯克爾玩,現在我試圖創建一個像哈斯克爾:運行兩個單子,不停的第一個

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

功能具有以下語義:它應該單子值適用於一個功能,它的回報第二個單子,並保持結果第一個單子,但第二個

效果Maybe單子的情況下,有一個工作功能:

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

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

因此,如果第一個值是Nothing,功能不運行(所以沒有第二副作用),但如果第一個值是Just,然後,運行該功能(與副作用)。我保持效果的第二次計算(例如,Nothing使整個表達式Nothing),但原始值。

現在我想知道。它可以用於任何單子嗎?

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

+2

一般來說,你不能做任何事情來「扔掉」一個monadic動作的效果。如果你真的想保持兩種效果,那麼你的函數是'\ x f - > x >> = \ a - > f a >> return a'。 – user2407038

+1

在最一般的情況下,單數函數給出的*結果*取決於它的* efect *(或其祕密),所以我不認爲你可以避免某些選擇的一元函數的效應。 (我正在考慮IO ......如果'ma'從標準輸入中取出'a'會怎麼樣?) – Euge

回答

11

讓我們通過這個!

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類型的值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 

這是我如何完成編寫任何像這樣的一元函數的第一個草案行走。然後,我會清理它。首先,一些小事情:因爲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 =<< mama >>= pure . f,或者do a <- ma ; pure $ f a(所有這些都是等價物)可以通過f <$> ma更換。然而,Functor型類有另一個,-公知的方法少,(<$)

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

因此,我們必須對(<$)類似的替換規則:我們可以隨時更換b <$ mama >> pure bdo ma ; pure b。這給了我們

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

而且我認爲這是這個功能的最短合理版本!沒有任何不錯的免費技巧可以使這個更清潔。其中一個指標是在do區塊的第二行上多次使用a


順便說一句,一個術語記:你在運行兩個單子行動,或兩個單子值;你沒有運行*「兩個monad」。單子就像Maybe - 一種類型的構造函數,它支持(>>=)return。不要將這些值與類型混合在一起 - 這種術語區分有助於保持更清晰的事物!

+2

你的意思是'輸入Control.Arrow; keepValue ma f = ma >> = uncurry(>>)。 (f &&&返回)'不算是一個很好的伎倆? ; --P – ephemient

+2

@ephemient:我更喜歡'(。((<$) <*>))。(>> =)',個人:-)或'ma >> =(<$) <*> f',如果你真的喜歡變量名! –

+1

這些是甚至當perl編碼者變得嫉妒我們haskellers蒼白的時候,因爲當我們的貓走過鍵盤時,它被類型檢查 – epsilonhalbe

1

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

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

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

所以你的原始表達式可以簡化爲

ma >>= f >> ma 

,這適用於其他的單子。

但是,我不認爲這實際上是你想要的,因爲你可以看到ma發生兩次。取而代之,從第一個ma >>=綁定中取值,並將其貫穿到計算結束。

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

do -notation

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

爲什麼你不想要'ma >> = f >> ma'的例子: ''''''''''''''''''''''''''''''':-)或者更普通:'getLine >> = print >> getLine'讀入兩行,'keepValue getLine print'只讀入一行。 –