2017-03-08 36 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,并且我很高兴留在其中。 –