使用通用元组函数在一次传递中进行多次折叠 [英] Multiple folds in one pass using generic tuple function
问题描述
如何编写一个函数,该函数需要一个 ai - > b - > ai
并返回一个函数,它接收一个元素类型为 ai
的元组,元素类型为 b
,并将每个元素组合成一个新元组: ai
:
这就是签名应该如
f ::(a1 - > b - > a1,a2 - > b - > a2, ...,an - > b - > an) - >
(a1,a2,...,an) - >
b - >
(a1,a2,...,an)
这样:
可以这样写:
foldlMult't = foldl'(ft)
然后做一些事情:
foldlMult'(min,max,(+),(*) )(头x,头x,0,0)x
可以一次完成多次折叠。 GHC扩展是可以的。
如果我理解你的例子,类型是 ai - > b - > ai
,而不是 ai - > b - >一个
就像你第一次写的那样。我们将类型重写为 a - > ri - > ri
,只是因为它帮助我思考。
首先要注意的是这种信件:
(a - > r1 - > r1,...,a - > rn - > rn)〜a - > (r1 - > r1,...,rn - > rn)
写下这两个函数,它们是逆函数:
pullArg ::(a - > r1 - > r1,a - > ; r2→r2)→> a - > (r1→r1,r2→r2)
pullArg(f,g)= \ a→> (f a,g a)
pushArg ::(a - >(r 1 - > r 1,r 2 - > r 2)) - > (a→r1→r1→a→r2→r2)
pushArg f =(\ a→fst(fa),\ a→> snd(fa) )
第二种观察:形式类型 ri - > ri
有时被称为 Data.Monoid
包有这样的包装:
newtype Endo a = Endo {appEndo :: a - > a}
实例Monoid(Endo a)其中
mempty = id
mappend =(。)
这允许您将前面的 pullArg
改写为:
<$ (> r1→r1,a→r2→r2)→> a - > (Endo r1,Endo r2)
pullArg(f,g)= \ a - >> (Endo $ fa,Endo $ ga)
第三个观察:两个monoid的乘积也是一个monoid ,根据这个实例也从 Data.Monoid
:
instance(幺半群a,幺半群b)=> Monoid(a,b)其中
mempty =(mempty,mempty)
(a,b)`mappend`(c,d)=(a`mappend` c,b`mappend d)
同样适用于任意数量参数的元组。
import Data.Monoid
fold: :Monoid m => (a - > m) - > [a] - > m
折叠f = mconcat。 map f
这个 fold
只是一个专门化 foldMap
来自 Data.Foldable
,所以实际上我们不需要定义它,我们可以直接导入它更通用的版本:
foldMap ::(可折叠t,Monoid m)=> (a - > m) - > t a - > m
如果 fold
with Endo
,这与从右侧折叠相同。要从左侧折叠,您希望使用 Dual(Endo r)
monoid进行折叠:
myfoldl ::(a - > Dual(Endo r)) - > r - > - > [a] - > r
myfoldl f z xs = appEndo(getDual(fold f xs))z
- 从`Data.Monoid`。这只是翻转`mappend`的顺序。
newtype Dual m = Dual {getDual :: m}
instance Monoid m => Monoid(Dual m)其中
mempty =双重空白
双重`mappend`双重b =双重$ b`mappend` a
记住我们的 pullArg
函数?我们再来修改一下,因为你从左边折叠:
pullArg ::(a - > r1 - > r1,a - > r2 - > r2) - > a - > Dual(Endo r1,Endo r2)
pullArg(f,g)= \ a - >> Dual(Endo $ fa,Endo $ ga)
这个我声称是2元组你的 f
的版本,或者至少与它同构。你可以将你的折叠函数重构为 a - > Endo ri
,然后执行:
let(f'1,...,f' n)= foldMap(pullArgn f1 ... fn)xs
in(f'1 z1,...,f'n zn)
另外值得一看:可堆叠的流式折叠,这是对这些想法的进一步阐述。
How can I write a function which takes a tuple of functions of type ai -> b -> ai
and returns a function which takes a tuple of elements of type ai
, one element of type b
, and combines each of the elements into a new tuple of ai
:
That is the signature should be like
f :: (a1 -> b -> a1, a2 -> b -> a2, ... , an -> b -> an) ->
(a1, a2, ... , an) ->
b ->
(a1, a2, ... , an)
Such that:
f (min, max, (+), (*)) (1,2,3,4) 5 = (1, 5, 8, 20)
The point of this is so I can write:
foldlMult' t = foldl' (f t)
And then do something like:
foldlMult' (min, max, (+), (*)) (head x, head x, 0, 0) x
to do multiple folds in one pass. GHC extensions are okay.
If I understand your examples right, the types are ai -> b -> ai
, not ai -> b -> a
as you first wrote. Let's rewrite the types to a -> ri -> ri
, just because it helps me think.
First thing to observe is this correspondence:
(a -> r1 -> r1, ..., a -> rn -> rn) ~ a -> (r1 -> r1, ..., rn -> rn)
This allows you to write these two functions, which are inverses:
pullArg :: (a -> r1 -> r1, a -> r2 -> r2) -> a -> (r1 -> r1, r2 -> r2)
pullArg (f, g) = \a -> (f a, g a)
pushArg :: (a -> (r1 -> r1, r2 -> r2)) -> (a -> r1 -> r1, a -> r2 -> r2)
pushArg f = (\a -> fst (f a), \a -> snd (f a))
Second observation: types of the form ri -> ri
are sometimes called endomorphisms, and each of these types has a monoid with composition as the associative operation and the identity function as the identity. The Data.Monoid
package has this wrapper for that:
newtype Endo a = Endo { appEndo :: a -> a }
instance Monoid (Endo a) where
mempty = id
mappend = (.)
This allows you to rewrite the earlier pullArg
to this:
pullArg :: (a -> r1 -> r1, a -> r2 -> r2) -> a -> (Endo r1, Endo r2)
pullArg (f, g) = \a -> (Endo $ f a, Endo $ g a)
Third observation: the product of two monoids is also a monoid, as per this instance also from Data.Monoid
:
instance (Monoid a, Monoid b) => Monoid (a, b) where
mempty = (mempty, mempty)
(a, b) `mappend` (c, d) = (a `mappend` c, b `mappend d)
Likewise for tuples of any number of arguments.
Fourth observation: What are folds made of? Answer: folds are made of monoids!
import Data.Monoid
fold :: Monoid m => (a -> m) -> [a] -> m
fold f = mconcat . map f
This fold
is just a specialization of foldMap
from Data.Foldable
, so in reality we don't need to define it, we can just import its more general version:
foldMap :: (Foldable t, Monoid m) => (a -> m) -> t a -> m
If you fold
with Endo
, that's the same as folding from the right. To fold from the left, you want to fold with the Dual (Endo r)
monoid:
myfoldl :: (a -> Dual (Endo r)) -> r -> -> [a] -> r
myfoldl f z xs = appEndo (getDual (fold f xs)) z
-- From `Data.Monoid`. This just flips the order of `mappend`.
newtype Dual m = Dual { getDual :: m }
instance Monoid m => Monoid (Dual m) where
mempty = Dual mempty
Dual a `mappend` Dual b = Dual $ b `mappend` a
Remember our pullArg
function? Let's revise it a bit more, since you're folding from the left:
pullArg :: (a -> r1 -> r1, a -> r2 -> r2) -> a -> Dual (Endo r1, Endo r2)
pullArg (f, g) = \a -> Dual (Endo $ f a, Endo $ g a)
And this, I claim, is the 2-tuple version of your f
, or at least isomorphic to it. You can refactor your fold functions into the form a -> Endo ri
, and then do:
let (f'1, ..., f'n) = foldMap (pullArgn f1 ... fn) xs
in (f'1 z1, ..., f'n zn)
Also worth looking at: Composable Streaming Folds, which is a further elaboration of these ideas.
这篇关于使用通用元组函数在一次传递中进行多次折叠的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!