在 Haskell monad / left-lifting 中使用纯函数?
Using a pure function in a Haskell monad / left-lifting?
考虑以下函数:
foo =
[1,2,3] >>=
return . (*2) . (+1)
为了更好的可读性和逻辑性,我想将纯函数 (*2)
和 (+1)
移到 return 的左侧。我可以这样实现:
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
我在 Hoogle 上没有找到签名为 leftLift
的函数。这样的功能是否存在,如果存在,它叫什么?如果不是,我应该怎么称呼它?做我想做的事情最惯用的方法是什么?
编辑:这是受@dunlop 下面的回答启发的版本:
infixl 4 <&>
(<&>) :: Functor f => f a -> (a -> b) -> f b
(<&>) = flip fmap
blah =
[1,2,3] <&>
(+1) <&>
(*2) >>=
return
我还应该补充一点,我在寻找 bind
-变体,因为我想以无点风格编写我的代码。对于 do
-notation,我想我不需要 "pretend" 我正在做任何一元的事情,所以我可以使用 let
s.
每个 Monad
都是一个 Functor
(也是一个 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]
(顺便说一句,flipped fmap
的通用名称是 (<&>)
。也就是说,例如,lens 称呼它。)
如果您使用 do-notation,则没有理由明确地使用 fmap
的任何变体来进行这种转换。只需将 <-
monadic 绑定切换为 let-bindings:
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)
For better readability...
这将是主观的,因为人们对什么是可读性存在分歧。
话虽这么说,但我同意有时从左到右书写数据转换更容易理解。不过,我认为您的 >>$
太过分了。 Data.Function
中的 &
运算符完成工作:
import Data.Function
foo = [1,2,3] & fmap (+1) & fmap (*2)
我喜欢它从左到右准确地说明了从什么开始以及每一步要做什么。与 >>$
不同,您不会被迫留在 monad 中:
bar = [1,2,3] & fmap (+1) & fmap (*2) & sum & negate
或者您可以预先 assemble 您的转换并将其映射到您的 monad 上:
import Control.Category
f = (+1) >>> (*2)
quuz = fmap f [1,2,3]
考虑以下函数:
foo =
[1,2,3] >>=
return . (*2) . (+1)
为了更好的可读性和逻辑性,我想将纯函数 (*2)
和 (+1)
移到 return 的左侧。我可以这样实现:
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
我在 Hoogle 上没有找到签名为 leftLift
的函数。这样的功能是否存在,如果存在,它叫什么?如果不是,我应该怎么称呼它?做我想做的事情最惯用的方法是什么?
编辑:这是受@dunlop 下面的回答启发的版本:
infixl 4 <&>
(<&>) :: Functor f => f a -> (a -> b) -> f b
(<&>) = flip fmap
blah =
[1,2,3] <&>
(+1) <&>
(*2) >>=
return
我还应该补充一点,我在寻找 bind
-变体,因为我想以无点风格编写我的代码。对于 do
-notation,我想我不需要 "pretend" 我正在做任何一元的事情,所以我可以使用 let
s.
每个 Monad
都是一个 Functor
(也是一个 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]
(顺便说一句,flipped fmap
的通用名称是 (<&>)
。也就是说,例如,lens 称呼它。)
如果您使用 do-notation,则没有理由明确地使用 fmap
的任何变体来进行这种转换。只需将 <-
monadic 绑定切换为 let-bindings:
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)
For better readability...
这将是主观的,因为人们对什么是可读性存在分歧。
话虽这么说,但我同意有时从左到右书写数据转换更容易理解。不过,我认为您的 >>$
太过分了。 Data.Function
中的 &
运算符完成工作:
import Data.Function
foo = [1,2,3] & fmap (+1) & fmap (*2)
我喜欢它从左到右准确地说明了从什么开始以及每一步要做什么。与 >>$
不同,您不会被迫留在 monad 中:
bar = [1,2,3] & fmap (+1) & fmap (*2) & sum & negate
或者您可以预先 assemble 您的转换并将其映射到您的 monad 上:
import Control.Category
f = (+1) >>> (*2)
quuz = fmap f [1,2,3]