7

是否有用于简单函数组合的“do notation”语法糖?

(即(.) :: (b -> c) -> (a -> b) -> a -> c

我希望能够存储一些作品的结果以供以后使用(同时仍然继续链条。

如果可能,我宁愿不使用 RebindableSyntax 扩展。

我正在寻找这样的东西:

composed :: [String] -> [String]
composed = do
    fmap (++ "!!!")
    maxLength <- maximum . fmap length
    filter ((== maxLength) . length)

composed ["alice", "bob", "david"]
-- outputs: ["alice!!!", "david!!!"]

我不确定这样的事情是否可行,因为早期函数的结果本质上必须“通过”maxLength 的绑定,但我愿意听到任何其他类似的表达选项。基本上,我需要在完成构图时收集信息,以便以后使用。

也许我可以用状态单子做这样的事情?

谢谢你的帮助!

编辑

这种事情有点工作:

split :: (a -> b) -> (b -> a -> c) -> a -> c
split ab bac a = bac (ab a) a

composed :: [String] -> [String]
composed = do
    fmap (++ "!!!")
    split 
        (maximum . fmap length)
        (\maxLength -> (filter ((== maxLength) . length)))
4

4 回答 4

5

实现类似目标的一种可能方法是箭头。基本上,在“存储间隙结果”中,您只是通过组合链拆分信息流。这就是&&&(扇出)组合器所做的。

import Control.Arrow

composed = fmap (++ "!!!")
       >>> ((. length) . (==) . maximum . fmap length &&& id)
       >>> uncurry filter

不过,这绝对不是人类可理解的好代码。

状态单子似乎也允许相关的东西,但问题是状态类型是通过do块的单子链固定的。这还不够灵活,无法在整个组合链中获取不同类型的值。虽然当然可以规避这一点(其中确实是,RebindableSyntax),但这也不是一个好主意 IMO。

于 2016-11-10T01:06:28.827 回答
3

(<*>)专用于函数实例的类型Applicative是:

(<*>) :: (r -> a -> b) -> (r -> a) -> (r -> b)

结果r -> b函数将其参数传递给 ther -> a -> br -> a函数,然后使用a函数产生的值r -> a作为第二个参数r -> a -> b

这和你的功能有什么关系?filter是两个参数的函数,一个谓词和一个列表。现在,您尝试做的一个关键方面是谓词是从列表中生成的。这意味着您的功能的核心可以用以下方式表示(<*>)

-- Using the predicate-generating function from leftaroundabout's answer.
maxLengthOnly :: Foldable t => [t a] -> [t a]
maxLengthOnly = flip filter <*> ((. length) . (==) . maximum . fmap length)

composed :: [String] -> [String]
composed = maxLengthOnly . fmap (++ "!!!")

如果无点谓词生成函数不是那么笨重,这个maxLengthOnly定义将是一个非常好的单行。

由于Applicative函数的实例在权力上等同于Monad一个,maxLengthOnly也可以表述为:

maxLengthOnly = (. length) . (==) . maximum . fmap length >>= filter

split顺便说一句,您添加到问题中的是(>>=)函数。)

另一种写法Applicative是:

maxLengthOnly = filter <$> ((. length) . (==) . maximum . fmap length) <*> id

这看起来很像 leftaroundabout 的解决方案并非巧合:对于函数,(,) <$> f <*> g = liftA2 (,) f g = f &&& g.

最后,还值得注意的是,虽然id在最新版本的maxLengthOnlywith中替换很诱人,但由于会更改字符串的长度,因此会影响谓词的结果fmap (++ "!!!"),这将不起作用。fmap (++ "!!!")但是,使用不会使谓词无效的函数,它会很好地工作:

nicerComposed = filter
    <$> ((. length) . (==) . maximum . fmap length) <*> fmap reverse
GHCi> nicerComposed ["alice","bob","david"]
["ecila","divad"]
于 2016-11-10T05:21:24.537 回答
2

正如leftaroundabout 所提到的,您可以使用它Arrows来编写您的函数。但是,ghc Haskell 编译器中有一个特性,即箭头的proc-notation。它与众所周知的do-notation 非常相似,但不幸的是,没有多少人知道它。

使用proc-notation,您可以用下一个更可编辑和更优雅的方式编写您想要的函数:

{-# LANGUAGE Arrows #-}

import Control.Arrow (returnA)
import Data.List     (maximum)

composed :: [String] -> [String]
composed = proc l -> do
    bangedL <- fmap (++"!!!")        -< l
    maxLen  <- maximum . fmap length -< bangedL
    returnA -< filter ((== maxLen) . length) bangedL

这在ghci中按预期工作:

ghci> composed ["alice", "bob", "david"]
["alice!!!","david!!!"]

如果您有兴趣,可以阅读一些带有精美图片的教程,以了解箭头是什么以及这个强大的功能是如何工作的,这样您就可以更深入地了解它:

https://www.haskell.org/arrows/index.html

https://en.wikibooks.org/wiki/Haskell/Understanding_arrows

于 2016-11-10T14:05:02.923 回答
1

您所拥有的本质上是一个过滤器,但是当您遍历列表时过滤功能会发生变化。我不会将其建模为“分叉”组合,而是使用以下函数将其建模为折叠f :: String -> (Int, [String])

  1. 返回值保持当前最大值和该长度的所有字符串。
  2. 如果第一个参数比当前最大值短,则删除它。
  3. 如果第一个参数与当前最大值相同,则将其添加到列表中。
  4. 如果第一个参数较长,则将其长度设为新的最大值,并将当前输出列表替换为新列表。

折叠完成后,您只需从元组中提取列表。

-- Not really a suitable name anymore, but...
composed :: [String] -> [String]
composed = snd . foldr f (0, [])
    where f curr (maxLen, result) = let currLen = length curr
                                    in case compare currLen maxLen of
                                       LT -> (maxLen, result)       -- drop
                                       EQ -> (maxLen, curr:result)  -- keep
                                       GT -> (length curr, [curr])  -- reset
于 2016-11-10T04:50:17.377 回答