使用通用元组函数在一次传递中进行多次折叠 [英] Multiple folds in one pass using generic tuple function

查看:103
本文介绍了使用通用元组函数在一次传递中进行多次折叠的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如何编写一个函数,该函数需要一个 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 有时被称为 endomorphisms ,并且每个类型都有一个monoid,其组合作为关联操作,身份函数作为标识。 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆