使用通用元组函数一次传递多个折叠
如何编写一个函数,该函数使用ai -> b -> ai
类型的函数的元组并返回一个函数,该函数使用元素类型为ai
的元组的元素,并将每个元素组合为b
元素进入ai
一个新的元组的:使用通用元组函数一次传递多个折叠
即签名应该像
f :: (a1 -> b -> a1, a2 -> b -> a2, ... , an -> b -> an) ->
(a1, a2, ... , an) ->
b ->
(a1, a2, ... , an)
使得:
f (min, max, (+), (*)) (1,2,3,4) 5 = (1, 5, 8, 20)
的这点是让我可以写:
foldlMult' t = foldl' (f t)
然后像做:
foldlMult' (min, max, (+), (*)) (head x, head x, 0, 0) x
做多褶皱一个通行证。 GHC扩展是可以的。
如果我正确理解您的示例,则类型为ai -> b -> ai
,而不是您第一次写入的ai -> b -> a
。我们将其重写为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 -> (r1 -> r1, r2 -> r2)) -> (a -> r1 -> r1, a -> r2 -> r2)
pushArg f = (\a -> fst (f a), \a -> snd (f a))
第二个观察:种形式ri -> ri
的有时也被称为自同态,并且这些类型中的每一个都具有组合作为关联操作和身份函数作为标识的幺半群。该Data.Monoid
包有此包装为:
newtype Endo a = Endo { appEndo :: a -> a }
instance Monoid (Endo a) where
mempty = id
mappend = (.)
这使您可以重写前面pullArg
这样:
pullArg :: (a -> r1 -> r1, a -> r2 -> r2) -> a -> (Endo r1, Endo r2)
pullArg (f, g) = \a -> (Endo $ f a, Endo $ g a)
三观察:二类群的产品也是一个独异,按本例如也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)
同样,对于任何数量的参数的元组。
第四观察:What are folds made of?答:folds are made of monoids!
import Data.Monoid
fold :: Monoid m => (a -> m) -> [a] -> m
fold f = mconcat . map f
这fold
距离Data.Foldable
的foldMap
专业化,因此在现实中,我们不需要去定义它,我们就可以导入它的更一般的版本:
foldMap :: (Foldable t, Monoid m) => (a -> m) -> t a -> m
如果fold
与Endo
,这与从右边的折叠。从左侧折叠,要与Dual (Endo r)
幺折:
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
记住我们pullArg
功能?让我们来修改它多一点,因为你从左边折叠:
pullArg :: (a -> r1 -> r1, a -> r2 -> r2) -> a -> Dual (Endo r1, Endo r2)
pullArg (f, g) = \a -> Dual (Endo $ f a, Endo $ g a)
而这一点,我要求,你f
的2元组版本,或者至少同构于它。你可以重构你的折叠功能到形式a -> Endo ri
,然后执行:
let (f'1, ..., f'n) = foldMap (pullArgn f1 ... fn) xs
in (f'1 z1, ..., f'n zn)
也值得一读:Composable Streaming Folds,这是这些想法进一步阐述。
对于直接的方法,你可以定义Control.Arrow
的(***)
和(&&&)
的等价物明确,对于每一个N
(如N == 4
):
prod4 (f1,f2,f3,f4) (x1,x2,x3,x4) = (f1 x1,f2 x2,f3 x3,f4 x4) -- cf (***)
call4 (f1,f2,f3,f4) x = (f1 x, f2 x, f3 x, f4 x) -- cf (&&&)
uncurry4 f (x1,x2,x3,x4) = f x1 x2 x3 x4
然后,
foldr4 :: (b -> a1 -> a1, b -> a2 -> a2,
b -> a3 -> a3, b -> a4 -> a4)
-> (a1, a2, a3, a4) -> [b]
-> (a1, a2, a3, a4) -- (f .: g) x y = f (g x y)
foldr4 t z xs = foldr (prod4 . call4 t) z xs -- foldr . (prod4 .: call4)
-- f x1 (f x2 (... (f xn z) ...)) -- foldr . (($) .: ($))
所以元组的foldr4
的功能是你想要的翻转版本。测试:
前奏曲>克XS = foldr4(最小,最大,(+),(*))(头XS,头XS,0,1)XS
前奏曲> G [1..5]
(1,5,15,120)
foldl4'
是一个调整了。由于
foldr f z xs == foldl (\k x r-> k (f x r)) id xs z
foldl f z xs == foldr (\x k a-> k (f a x)) id xs z
我们
foldl4, foldl4' :: (t -> a -> t, t1 -> a -> t1,
t2 -> a -> t2, t3 -> a -> t3)
-> (t, t1, t2, t3) -> [a]
-> (t, t1, t2, t3)
foldl4 t z xs = foldr (\x k a-> k (call4 (prod4 t a) x))
(prod4 (id,id,id,id)) xs z
foldl4' t z xs = foldr (\x k a-> k (call4 (prod4' t a) x))
(prod4 (id,id,id,id)) xs z
prod4' (f1,f2,f3,f4) (x1,x2,x3,x4) = (f1 $! x1,f2 $! x2,f3 $! x3,f4 $! x4)
我们甚至已经得到了类型,你想,对于元组的功能。
必须使用更严格的prod4
版本才能在foldl4'
的早期强制使用参数。
我认为有一个解决方案使用Arrow的***和&&&,使用像(f,(g,(h,i)))而不是(f,g,h,i)在引擎盖下的类型,现在距离我的笔记本电脑几百英里远,所以今天不能玩。 – AndrewC 2014-09-26 23:32:36