功能组成表示法

时间:2016-11-10 00:35:13

标签: haskell composition 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的绑定,但我愿意听到任何其他类似的表达选项。基本上我需要在整理作文时收集信息,以便以后使用。

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

感谢您的帮助!

修改

这种事情有点起作用:

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 个答案:

答案 0 :(得分:5)

实现类似目标的一种可能方式是箭头。基本上,在“存储插页式结果”中,您只是将信息流分解为组合链。这就是&&& (fanout)组合子的作用。

import Control.Arrow

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

但这绝对不是人类易于理解的代码。

状态monad似乎也允许相关的东西,但问题是状态类型是通过do块的monadic链来修复的。这并不足以灵活地在整个构图链中获取不同类型的值。虽然有可能规避这一点(其中,确实是RebindableSyntax),但这也不是IMO的好主意。

答案 1 :(得分:3)

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

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

生成的r -> b函数将其参数传递给r -> 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

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

最后,值得注意的是,虽然最新版本的id中的maxLengthOnlyfmap (++ "!!!")取代很有吸引力,但是因为{{{}}而无法工作1}}更改字符串的长度,因此会影响谓词的结果。但是,使用一个不会使谓词无效的函数,它可以很好地工作:

fmap (++ "!!!")
nicerComposed = filter
    <$> ((. length) . (==) . maximum . fmap length) <*> fmap reverse

答案 2 :(得分:2)

如上所述leftaroundabout,您可以使用Arrows来编写您的函数。但是, ghc Haskell编译器中有一个功能,它是proc - 符号表示Arrows。它与众所周知的do - 符号非常相似,但不幸的是,没有多少人知道它。

使用proc - 表示法,您可以用下一个更加可更加优雅的方式编写所需的功能:

{-# 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

答案 3 :(得分:1)

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

  1. 返回值保持当前最大值和该长度的所有字符串。
  2. 如果第一个参数短于当前最大值,请将其删除。
  3. 如果第一个参数与当前最大值相同,请将其添加到列表中。
  4. 如果第一个参数更长,请将其长度设为新的最大值,并将当前输出列表替换为新列表。
  5. 折叠完成后,您只需从元组中提取列表。

    -- 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