使用通用元组函数在一次传递中进行多次折叠

Cli*_*ton 11 haskell ghc fold higher-order-functions

我如何编写一个函数,它接受一个类型的函数元组,ai -> b -> ai并返回一个函数,该函数接受类型元素的元组,类型的ai一个元素b,并将每个元素组合成一个新的元组ai:

那就是签名应该是这样的

f :: (a1 -> b -> a1, a2 -> b -> a2, ... , an -> b -> an) -> 
     (a1, a2, ... , an) -> 
     b -> 
     (a1, a2, ... , an)
Run Code Online (Sandbox Code Playgroud)

这样:

f (min, max, (+), (*)) (1,2,3,4) 5 = (1, 5, 8, 20) 
Run Code Online (Sandbox Code Playgroud)

关键是我可以写:

foldlMult' t = foldl' (f t)
Run Code Online (Sandbox Code Playgroud)

然后做一些像:

foldlMult' (min, max, (+), (*)) (head x, head x, 0, 0) x 
Run Code Online (Sandbox Code Playgroud)

一次通过多次折叠.GHC扩展是可以的.

Lui*_*las 11

如果我理解你的例子是正确的,那么这些类型ai -> b -> ai不是ai -> b -> a你第一次写的.让我们重写类型a -> ri -> ri,只是因为它有助于我思考.

首先要注意的是这种对应关系:

(a -> r1 -> r1, ..., a -> rn -> rn) ~ a -> (r1 -> r1, ..., rn -> rn)
Run Code Online (Sandbox Code Playgroud)

这允许您编写这两个函数,它们是反转的:

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))
Run Code Online (Sandbox Code Playgroud)

第二个观察:形式的类型ri -> ri有时被称为内同胚,并且这些类型中的每一个都具有作为关联操作的组合的monoid和作为标识的标识函数.该Data.Monoid包装具有此包装为:

newtype Endo a = Endo { appEndo :: a -> a }

instance Monoid (Endo a) where
    mempty = id
    mappend = (.)
Run Code Online (Sandbox Code Playgroud)

这允许您重写前面的内容pullArg:

pullArg :: (a -> r1 -> r1, a -> r2 -> r2) -> a -> (Endo r1, Endo r2)
pullArg (f, g) = \a -> (Endo $ f a, Endo $ g a)
Run Code Online (Sandbox Code Playgroud)

第三个观察:两个幺半群的乘积也是一个幺半群,根据这个例子也来自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)
Run Code Online (Sandbox Code Playgroud)

同样,对于任何数量的参数的元组.

第四个观察:什么是褶皱? 答案:褶皱是由幺半群组成的!

import Data.Monoid

fold :: Monoid m => (a -> m) -> [a] -> m
fold f = mconcat . map f
Run Code Online (Sandbox Code Playgroud)

fold只是foldMapfrom的一个特化Data.Foldable,所以实际上我们不需要定义它,我们可以只导入它更通用的版本:

foldMap :: (Foldable t, Monoid m) => (a -> m) -> t a -> m
Run Code Online (Sandbox Code Playgroud)

如果你foldEndo,那就像从右边折叠一样.要从左侧折叠,您需要使用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
Run Code Online (Sandbox Code Playgroud)

记住我们的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)
Run Code Online (Sandbox Code Playgroud)

我声称,这是你的2元组版本f,或至少与它同构.您可以将折叠函数重构为表单a -> Endo ri,然后执行:

let (f'1, ..., f'n) = foldMap (pullArgn f1 ... fn) xs
in (f'1 z1, ..., f'n zn) 
Run Code Online (Sandbox Code Playgroud)

另外值得一看:可组合流媒体折叠,这是对这些想法的进一步阐述.


Wil*_*ess 6

对于直接的方法,你可以定义的等价物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
Run Code Online (Sandbox Code Playgroud)

然后,

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 . (($)   .: ($))
Run Code Online (Sandbox Code Playgroud)

因此,元组中的元组功能foldr4是您想要的翻转版本.测试:

Prelude> g xs = foldr4(min,max,(+),(*))(head xs,head xs,0,1)xs
Prelude> 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
Run Code Online (Sandbox Code Playgroud)

我们有

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)
Run Code Online (Sandbox Code Playgroud)

对于元组的功能,我们甚至可以获得你想要的类型.

prod4必须使用更严格的版本来尽早强制论证foldl4'.