函数组合 Do Notation
Function Composition Do Notation
是否有 "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!!!"]
我不确定这样的事情是否可行,因为早期函数的结果本质上必须通过 "through" 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)))
实现类似目标的一种可能方法是箭头。基本上,在“存储间隙结果”中,您只是通过组合链拆分信息流。这就是 &&&
(fanout) 组合器的作用。
import Control.Arrow
composed = fmap (++ "!!!")
>>> ((. length) . (==) . maximum . fmap length &&& id)
>>> uncurry filter
虽然这绝对不是人类可理解的好代码。
状态 monad 似乎也允许一些相关的东西,但问题是状态类型是通过 do
块的 monadic 链固定的。这不够灵活,无法在整个组合链中获取不同类型的值。虽然肯定有可能规避这一点(其中确实有 RebindableSyntax
),但在 IMO 看来,这也不是一个好主意。
您所拥有的本质上是一个过滤器,但是当您遍历列表时,过滤功能会发生变化。我不会将其建模为 "forked" 组合,而是使用以下函数 f :: String -> (Int, [String])
:
进行折叠
- return 值保持当前最大值和该长度的所有字符串。
- 如果第一个参数比当前最大值短,则删除它。
- 如果第一个参数与当前最大值相同,将其添加到列表中。
- 如果第一个参数更长,使其长度成为新的最大值,并用新列表替换当前输出列表。
折叠完成后,您只需从元组中提取列表。
-- 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
专用于Applicative
函数实例的(<*>)
类型是:
(<*>) :: (r -> a -> b) -> (r -> a) -> (r -> b)
生成的 r -> b
函数将其参数传递给 r -> a -> b
和 r -> a
函数,然后使用 [=20= 生成的 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 (++ "!!!")
如果 pointfree 谓词生成函数不那么笨拙,这个 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
。
最后,还值得注意的是,虽然很想用 fmap (++ "!!!")
替换最新版本 maxLengthOnly
中的 id
,但这是行不通的,因为 fmap (++ "!!!")
改变字符串的长度,因此影响谓词的结果。但是,使用不使谓词无效的函数,它会工作得很好:
nicerComposed = filter
<$> ((. length) . (==) . maximum . fmap length) <*> fmap reverse
GHCi> nicerComposed ["alice","bob","david"]
["ecila","divad"]
如leftaroundabout所述,您可以使用Arrows
来编写您的函数。但是,ghc Haskell 编译器有一个特性,就是箭头的 proc
符号。它与众所周知的 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!!!"]
如果您有兴趣,可以阅读一些带有精美图片的教程,以了解什么是箭头以及这一强大功能的工作原理,以便您更深入地研究它:
是否有 "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!!!"]
我不确定这样的事情是否可行,因为早期函数的结果本质上必须通过 "through" 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)))
实现类似目标的一种可能方法是箭头。基本上,在“存储间隙结果”中,您只是通过组合链拆分信息流。这就是 &&&
(fanout) 组合器的作用。
import Control.Arrow
composed = fmap (++ "!!!")
>>> ((. length) . (==) . maximum . fmap length &&& id)
>>> uncurry filter
虽然这绝对不是人类可理解的好代码。
状态 monad 似乎也允许一些相关的东西,但问题是状态类型是通过 do
块的 monadic 链固定的。这不够灵活,无法在整个组合链中获取不同类型的值。虽然肯定有可能规避这一点(其中确实有 RebindableSyntax
),但在 IMO 看来,这也不是一个好主意。
您所拥有的本质上是一个过滤器,但是当您遍历列表时,过滤功能会发生变化。我不会将其建模为 "forked" 组合,而是使用以下函数 f :: String -> (Int, [String])
:
- return 值保持当前最大值和该长度的所有字符串。
- 如果第一个参数比当前最大值短,则删除它。
- 如果第一个参数与当前最大值相同,将其添加到列表中。
- 如果第一个参数更长,使其长度成为新的最大值,并用新列表替换当前输出列表。
折叠完成后,您只需从元组中提取列表。
-- 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
专用于Applicative
函数实例的(<*>)
类型是:
(<*>) :: (r -> a -> b) -> (r -> a) -> (r -> b)
生成的 r -> b
函数将其参数传递给 r -> a -> b
和 r -> a
函数,然后使用 [=20= 生成的 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 (++ "!!!")
如果 pointfree 谓词生成函数不那么笨拙,这个 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
。
最后,还值得注意的是,虽然很想用 fmap (++ "!!!")
替换最新版本 maxLengthOnly
中的 id
,但这是行不通的,因为 fmap (++ "!!!")
改变字符串的长度,因此影响谓词的结果。但是,使用不使谓词无效的函数,它会工作得很好:
nicerComposed = filter
<$> ((. length) . (==) . maximum . fmap length) <*> fmap reverse
GHCi> nicerComposed ["alice","bob","david"]
["ecila","divad"]
如leftaroundabout所述,您可以使用Arrows
来编写您的函数。但是,ghc Haskell 编译器有一个特性,就是箭头的 proc
符号。它与众所周知的 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!!!"]
如果您有兴趣,可以阅读一些带有精美图片的教程,以了解什么是箭头以及这一强大功能的工作原理,以便您更深入地研究它: