2017-03-08 41 views
1

考慮以下功能:在Haskell monad/left-lifting中使用純函數?

foo = 
    [1,2,3] >>= 
    return . (*2) . (+1) 

爲了更好的可讀性和邏輯,我想(*2)(+1)移動我的純函數的返回的左側。我可以這樣實現這一點:

infixr 9 <. 
(<.) :: (a -> b) -> (b -> c) -> (a -> c) 
(<.) f g = g . f 

bar = 
    [1,2,3] >>= 
    (+1) <. 
    (*2) <. 
    return 

不過,我不喜歡的(<.)右關聯性。

讓我們介紹一個功能leftLift

leftLift :: Monad m => (a -> b) -> a -> m b 
leftLift f = return . f 

baz = 
    [1,2,3] >>= 
    leftLift (+1) >>= 
    leftLift (*2) >>= 
    return 

我很喜歡這一點。另一種可能性是定義的bind變體:

infixl 1 >>$ 
(>>$) :: Monad m => m a -> (a -> b) -> m b 
(>>$) m f = m >>= return . f 

qux = 
    [1,2,3] >>$ 
    (+1) >>$ 
    (*2) >>= 
    return 

我不知道這是否是一個好主意,因爲它不會允許我使用do符號我應該希望如此。 leftLift我可以do使用:

bazDo = do 
    x <- [1,2,3] 
    y <- leftLift (+1) x 
    z <- leftLift (*2) y 
    return z 

我沒有找到的leftLift上的簽名Hoogle功能。這樣的功能是否存在?如果它叫什麼?如果不是,我應該怎麼稱呼它?什麼是我正在嘗試做的最習慣的方式?


編輯:這是一個被@鄧祿普的回答以下的啓發版本:

infixl 4 <&> 
(<&>) :: Functor f => f a -> (a -> b) -> f b 
(<&>) = flip fmap 

blah = 
    [1,2,3] <&> 
    (+1) <&> 
    (*2) >>= 
    return 

我還要補充一點,我是一個bind後-variant,因爲我想寫我的代碼點免費樣式。對於do -notation,我想我不需要「假裝」我做了任何單調的事情,所以我可以使用let s。

+1

我發現自己使用'= <比'>> ='更經常地與組成和普通應用保持一致。這很好地避免了第一個例子中的方向反轉。 – Ben

+0

這很好,我沒有意識到這一點。然後我可以寫回報。 (* 2)。 (+1)= << [1,2,3]'。而且,是的,正如@ user2297560所說,可讀性是非常主觀的。 –

回答

4

每個MonadFunctor(和Applicative)。你的(>>$)是(翻轉)fmap

GHCi> :t fmap 
fmap :: Functor f => (a -> b) -> f a -> f b 
GHCi> :t (<$>) -- Infix synonym for 'fmap' 
(<$>) -- Infix synonym for 'fmap' 
    :: Functor f => (a -> b) -> f a -> f b 
GHCi> fmap ((*2) . (+1)) [1,2,3] 
[4,6,8] 
GHCi> (*2) . (+1) <$> ([1,2,3] >>= \x -> [1..x]) 
[4,4,6,4,6,8] 

(順便說一句,翻轉後fmap一個共同的名字是(<&>)。也就是說,例如,什麼鏡頭調用它。)


如果您正在使用DO-符號,沒有什麼理由明確地使用fmap的任何變體來進行這種轉換。只需切換您<-單子綁定鬆懈綁定:

bazDo = do 
    x <- [1,2,3] 
    let y = (+1) x 
     z = (*2) y 
    return z 
bazDo = do 
    x <- [1,2,3] 
    let y = (+1) x 
    return ((*2) z) 
+0

那麼我可以用'fmap'來定義我的'(>> $)''。我不想直接使用'(<$>)',因爲它會破壞連續的外觀。我希望我的代碼讀作「從[1,2,3]'中取數字,給它們加'1',然後用'2'將它們相乘(我的實際代碼會比這更大) –

+1

@ marc_r在這種monadic代碼中保持順序外觀最簡單的方法是使用do-notation。 – duplode

+0

因此,我的'leftLift'函數,我將編輯我的文章以包含'do'版本 –

1

爲了更好的可讀性...

這將是主觀的,因爲人們不同意什麼構成的可讀性。

這就是說,我同意有時當從左到右書寫數據時,理解數據變換會更容易。不過,我認爲你的>>$是過度殺傷。該&運營商Data.Function這項工作:

import Data.Function 

foo = [1,2,3] & fmap (+1) & fmap (*2) 

我喜歡這說正是入手,並正是在每一步做到從左至右。不像>>$,你不會被強迫留在單子:

bar = [1,2,3] & fmap (+1) & fmap (*2) & sum & negate 

或者你可以組裝你的改造事前並將其映射在你的單子:

import Control.Category 

f = (+1) >>> (*2) 
quuz = fmap f [1,2,3] 
+0

有關「(&)」的提示。在我的特定應用程序中(除了我的文章中的玩具示例),我正在使用State monad,並且我很高興留在其中。 –