是否有一个标准的Haskell函数(返回a)>> = b?

时间:2013-11-25 20:52:20

标签: haskell

我正在寻找一种方法从以下函数中删除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

解决此问题的最佳方法是什么?更好的是,是否有某种标准的运算符或其他方式可以更容易地以这种方式构造代码?

3 个答案:

答案 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的一种方法是使用<**>中的pureControl.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 >>= ff 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"

如果我们pathgetModificationTimereplaceBaseName不需要takeBaseName,我们只需使用(>=>)即可删除path完全指出。这仍然可以通过使用ReaderT monad变换器来完成,但它可能会变得更加丑陋,而不是以这个速率更好。