Multiple folds in one pass using generic tuple function

前端 未结 2 916
渐次进展
渐次进展 2021-02-08 07:22

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

2条回答
  •  暖寄归人
    2021-02-08 07:48

    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.

提交回复
热议问题