我正在寻找一种方法从以下函数中删除return
:
naming path =
getModificationTime path >>=
return . formatTime defaultTimeLocale "%Y%m%d" >>=
return . printf "%s_%s" (takeBaseName path) >>=
return . replaceBaseName path
我以这种方式构造它的原因是因为>>=
实际上变成了一种管道操作符,并且数据从一行流向下一行。
我想我可以按照
的方式定义一个运算符 a |>= b = (return a) >>= b
并获取
naming path =
getModificationTime path >>=
formatTime defaultTimeLocale "%Y%m%d" |>=
printf "%s_%s" (takeBaseName path) |>=
replaceBaseName path
但我收到了错误
Precedence parsing error
cannot mix `|>=' [infixl 9] and `.' [infixr 9] in the same infix expression
解决此问题的最佳方法是什么?更好的是,是否有某种标准的运算符或其他方式可以更容易地以这种方式构造代码?
答案 0 :(得分:8)
据推测,您希望它的行为与>>=
具有相同的固定性,因此如果您加载GHCi并输入
> :info (>>=)
...
infixl 1 >>=
然后,您可以将运营商定义为
infixl 1 |>=
(|>=) :: Monad m => a -> (a -> m b) -> m b
a |>= b = return a >>= b
但是,如果你的monad保留monad定律,这与仅仅b a
相同,所以首先不需要运算符。
我还建议使用do notation:
naming path = do
modTime <- getModificationTime path
let str = formatTime defaultTimeLocale "%Y%m%d" modTime
name = printf "%s_%s" (takeBaseName path) str
replaceBaseName path name
答案 1 :(得分:5)
您需要return
,因为您的整个链的其余部分组成了纯函数,因此最好的方法是使用普通函数组合运算符.
。但是,与>>=
相比,.
组成了不同的方向,因为>>=
用于组成两个monadic操作,您至少需要一次返回:
naming path =
getModificationTime path >>=
return .
replaceBaseName path .
printf "%s_%s" (takeBaseName path) .
formatTime defaultTimeLocale "%Y%m%d"
摆脱额外return
的一种方法是使用<**>
中的pure
和Control.Applicative
代替>>=
。
naming path =
getModificationTime path <**> pure (
replaceBaseName path .
printf "%s_%s" (takeBaseName path) .
formatTime defaultTimeLocale "%Y%m%d" )
要“修复”从上到下流动的操作顺序,我们可以将.
替换为来自>>>
的Control.Category
,并提供
naming path =
getModificationTime path <**> pure (
formatTime defaultTimeLocale "%Y%m%d" >>>
replaceBaseName path >>>
printf "%s_%s" (takeBaseName path) )
或者,如果你想变得疯狂(使用Control.Arrow
):
naming path = flip runKleisli path $
Kleisli getModificationTime >>^
formatTime defaultTimeLocale "%Y%m%d" >>^
replaceBaseName path >>^
printf "%s_%s" (takeBaseName path)
不幸的是,Control.Applicative
并未提供<$>
的翻转版本,但您可以自行定义以获得更整洁的版本:
(<$$>) = flip (<$>)
infixr 1 <$$>
naming path =
getModificationTime path <$$>
formatTime defaultTimeLocale "%Y%m%d" >>>
replaceBaseName path >>>
printf "%s_%s" (takeBaseName path)
我们可以这样做:
(|>=) = flip fmap
naming path =
getModificationTime path |>=
formatTime defaultTimeLocale "%Y%m%d" |>=
printf "%s_%s" (takeBaseName path) |>=
replaceBaseName path
答案 2 :(得分:4)
return x >>= f
与f x
相同---换句话说,您的大多数内部函数都是纯函数,根本不需要(>>=)
。在do
表示法中,您使用let
来绑定纯计算。
naming path = do
time <- getModificationTime path
let str = formatTime defaultTimeLocale "%Y%m%d" time
name = printf "%s_%s" (takeBaseName path) str
replaceBaseName path name
为了更加简洁,上面name
可以内联计算
naming path = do
time <- getModificationTime path
replaceBaseName path
$ printf "%s_%s" (takeBaseName path)
$ formatTime defaultTimeLocale "%Y%m%d" time
我们发现我们只是做了一些纯函数组合,所以我们可以使用(.)
naming path =
getModificationTime path >>= replaceBaseName path . mkTime
where
mkTime = printf "%s_%s" (takeBaseName path)
. formatTime defaultTimeLocale "%Y%m%d"
如果我们path
,getModificationTime
和replaceBaseName
不需要takeBaseName
,我们只需使用(>=>)
即可删除path
完全指出。这仍然可以通过使用ReaderT
monad变换器来完成,但它可能会变得更加丑陋,而不是以这个速率更好。