在之前的question中,我试图通过将它们组合在一起来询问如何混合纯函数和monadic函数,但是因为我可能错误地说了我的问题并且我的例子太简单了,我认为讨论已经进行了错误的方向,所以我想我会再试一次。
这是一个混合纯和一元过滤器的示例函数。在这个例子中,有一些纯粹的过滤器在monadic过滤器之间排序,试图减少工作量。
findFiles target =
getDirectoryContents target >>=
return . filter (not . (=~ "[0-9]{8}\\.txt$")) >>=
return . filter (=~ "\\.txt$") >>=
filterM doesFileExist >>=
mapM canonicalizePath
使用return
混合纯函数的方式编写它的好处是,从上到下有一个可视流数据。不需要临时变量fmap
,<$>
等。
理想情况下,我可以摆脱return
以使其更清洁。我有想法使用一些算子:
(|>=) :: Monad m => a -> (a -> m b) -> m b
a |>= b = (return a) >>= b
但我不知道如何编写此函数以避免运算符优先级问题。这已经存在了吗?它类似于<$>
,但与“其他方向”相似。如果没有,我该如何使这个操作符工作?
更一般地说,是否有一种以这种管道方式编写代码的好方法,或者我需要解决fmap
和临时变量,如上一个问题所述?
答案 0 :(得分:7)
唉。就这么简单:
infixl 1 |>=
(|>=) = flip fmap
findFiles target =
getDirectoryContents target |>=
filter (not . (=~ "[0-9]{8}\\.txt$")) |>=
filter (=~ "\\.txt$") >>=
filterM doesFileExist >>=
mapM canonicalizePath
答案 1 :(得分:3)
借调迭戈诺兰,对于使用do
符号,使用x <- ...
- 符号,使用monadic赋值(let
)或一个好的老式{绑定中间值,没有任何耻辱。 {1}}。代码的继承人会感谢你。
那就是说,如果你不能分数,你可能是一个类别理论家。说真的,你可以从John Hughes那里拿一页(见Programming with Arrows)并写下你的管道:
import Control.Arrow
findFiles = runKleisli $
Kleisli getDirectoryContents >>>
arr (filter (not . (=~ "[0-9]{8}\\.txt$"))) >>>
arr (filter (=~ "\\.txt$")) >>>
Kleisli (filterM doesFileExist) >>>
Kleisli (mapM canonicalizePath)
这可能比使用一个人自己的特殊绑定操作符更有原则性,但如果你问我,它仍然比普通的尖端风格更丑陋。 De gustibus non est disputandum,正如罗马人常说的关于garum。
答案 2 :(得分:1)
使用(<$>)
(也称为fmap
)将纯函数映射到仿函数。大多数monad都有仿函数的例子。如果他们没有,那么您可以使用liftM
查看类型
liftM :: Monad m => (a -> b) -> m a -> m b
(<$>) :: Functor f => (a -> b) -> f a -> f b
你的看起来像这样(没有检查ghc)。
findFiles target =
((filter (not . (=~ "[0-9]{8}\\.txt$")) .
filter (=~ "\\.txt$") ) <$>
getDirectoryContents target) >>=
filterM doesFileExist >>=
mapM canonicalizePath
但是在这一点上你可能最好使用do notation和let
。
答案 3 :(得分:1)
你需要一些额外的操作符,一个处理每个案例
Monad -> Monad
Monad -> Pure
Pure -> Monad
Pure -> Pure
您已经拥有Monad -> Monad
案例(>>=
),正如我在上一个问题的答案中所述,您可以|>=
使用Pure -> Monad
案例,但你仍然需要Monad -> Pure
一个。这将是棘手的,因为唯一的类型安全的方法是通过让该运算符将您的纯函数转换为monadic函数。我推荐以下一组操作符
Monad -> Monad >>= m a -> (a -> m b) -> m b
Monad -> Pure >|= m a -> (a -> b) -> m b
Pure -> Monad |>= a -> (a -> m b) -> m b
Pure -> Pure ||= (a -> b) -> (b -> c) -> (a -> c)
使用>
表示“monad”和|
表示“纯”的约定,所有结尾都以=
表示“起作用”。希望类型签名对于实现是有意义的:
import Data.Char (toUpper)
import Control.Monad (liftM)
infixl 1 |>=
(|>=) :: Monad m => a -> (a -> m b) -> m b
a |>= b = b a
infixl 1 >|=
(>|=) :: Monad m => m a -> (a -> b) -> m b
a >|= b = liftM b a
infixr 9 ||=
(||=) :: (a -> b) -> (b -> c) -> a -> c
a ||= b = b . a
一个例子
test :: IO ()
test =
getLine >|=
filter (/= 't') ||=
map toUpper >>=
putStrLn
> test
testing
ESING
>
这也等同于
test :: IO ()
test =
getLine >|=
filter (/= 't') >|=
map toUpper >>=
putStrLn
但是额外的||>
组合可以让你实际编写这些函数,这些函数具有不同的实现,而不是通过monadic动作来提供它们。
但是,我仍然敦促您使用惯用的方法来使用fmap
,do notation和临时变量。对于那些查看代码的人来说,它会更加清晰,并且在2个月内包括你。