packages feed

porcupine-core-0.1.0.0: src/Control/Arrow/FoldA.hs

{-# LANGUAGE Arrows                    #-}
{-# LANGUAGE BangPatterns              #-}
{-# LANGUAGE ExistentialQuantification #-}

-- | This module defines the type 'FoldA', which is a generalization of 'Fold'
-- from the `foldl` package.
--
-- This module intentionally doesn't provide a function to run the FoldA over a
-- Foldable in any arrow, because that function may depend on the arrow.
module Control.Arrow.FoldA
  ( module Control.Foldl
  , FoldA(..)
  , FoldA'
  , Pair(..)
  , arrowFold
  , arrowFold_
  , generalizeA
  , generalizeA_
  , specializeA
  , premapA
  , premapInitA
  , postmapA
  , prefilterA

  , toTup
  , fromTup
  , (&&&&)
  , (****)
  ) where

import           Prelude          hiding ((.), id)

import           Control.Arrow
import           Control.Category
import           Control.Foldl
import           Data.Profunctor


ret :: (Arrow arr) => t -> arr a t
ret = arr . const
{-# INLINE ret #-}

data Pair a b = Pair !a !b

toTup :: Pair a b -> (a,b)
toTup (Pair x y) = (x,y)
{-# INLINE toTup #-}

fromTup :: (a,b) -> Pair a b
fromTup (x,y) = Pair x y
{-# INLINE fromTup #-}

applyP :: Pair (a -> b) a -> b
applyP (Pair f x) = f x
{-# INLINE applyP #-}

uncurryP :: (a -> b -> c) -> Pair a b -> c
uncurryP f (Pair x y) = f x y
{-# INLINE uncurryP #-}

(****) :: (Arrow a) => a b c -> a b' c' -> a (Pair b b') (Pair c c')
a1 **** a2 =
  arr toTup >>> (a1 *** a2) >>> arr fromTup
{-# INLINE (****) #-}

(&&&&) :: (Arrow a) => a b c -> a b c' -> a b (Pair c c')
a1 &&&& a2 = a1 &&& a2 >>> arr fromTup
{-# INLINE (&&&&) #-}

secondP :: (Arrow a) => a c c' -> a (Pair b c) (Pair b c')
secondP ar =
  arr toTup >>> second ar >>> arr fromTup


-- | This is a generalization of 'Control.Foldl.Fold' that
-- allows computing on arrows.
--
-- 'FoldA (->) ()' is isomorphic to 'Fold' as testified
-- by 'generalizeA' and 'specializeA'.
--
-- 'FoldA (Kleisly m) ()' is isomorphic to 'FoldM m'.
--
-- We must keep an extra type parameter @i@ that will allow to initialize the
-- accumulator, else we would need 'ArrowApply' everytime we want to create a
-- 'FoldA' from an accumulator that will be computed by a previous arrow
-- computation.
data FoldA arr i a b =
  forall x. FoldA (arr (Pair x a) x) (arr i x) (arr x b)

-- | A fold that will directly receive its initial accumulator
type FoldA' arr a b = FoldA arr b a b

-- | Turns a function that returns a 'Fold' into a 'FoldA' that will feed the
-- initializer
generalizeA :: (Arrow arr) => (i -> Fold a b) -> FoldA arr i a b
generalizeA f =
  FoldA (arr $ \(Pair (Fold step !acc done) x) ->
            Fold step (step acc x) done)
        (arr f)
        (arr $ \(Fold _ acc done) -> done acc)

-- | Turns a 'Fold' into a 'FoldA' that just ignores its initializer
generalizeA_ :: (Arrow arr) => Fold a b -> FoldA arr i a b
generalizeA_ (Fold step start done) =
  FoldA (arr $ uncurryP step) (ret start) (arr done)

-- | Turns a 'FoldA' over pure function into a pure 'Fold'
specializeA :: FoldA (->) () a b -> Fold a b
specializeA (FoldA step start done) =
  Fold (\x a -> step $ Pair x a) (start ()) done

instance (Arrow arr) => Functor (FoldA arr i a) where
  fmap f (FoldA step start done) = FoldA step start done'
    where
      done' = done >>> arr (f $!)
  {-# INLINE fmap #-}

instance (Arrow arr) => Applicative (FoldA arr i a) where
  pure x = FoldA (ret ()) (ret ()) (ret x)
  {-# INLINE pure #-}

  FoldA stepL startL doneL <*> FoldA stepR startR doneR =
    let step =
          arr (\(Pair (Pair xL xR) a) ->
                 Pair (Pair xL a) (Pair xR a))
          >>> (stepL **** stepR)
        start = startL &&&& startR
        done = (doneL **** doneR) >>> arr applyP
    in FoldA step start done
  {-# INLINE (<*>) #-}

instance (Arrow arr) => Profunctor (FoldA arr i) where
  rmap = fmap
  lmap f (FoldA step start done) = FoldA step' start done
    where
      step' = arr (\(Pair x a) -> Pair x (f a)) >>> step
  {-# INLINE lmap #-}

-- | Changes the type initializing the accumulator
premapInitA :: (Arrow arr)
            => arr i' i -> FoldA arr i a b -> FoldA arr i' a b
premapInitA ar (FoldA step start done) =
  FoldA step (ar >>> start) done

-- | Changes all the inputs arriving to the 'FoldA'
premapA :: (Arrow arr)
        => arr a b -> FoldA arr i b r -> FoldA arr i a r
premapA ar (FoldA step start done) =
  FoldA (secondP ar >>> step) start done
{-# INLINABLE premapA #-}

-- | Changes the output of the 'FoldA'
postmapA :: (Category arr)
         => FoldA arr i a b -> arr b r -> FoldA arr i a r
postmapA (FoldA step start done) ar =
  FoldA step start (done >>> ar)
{-# INLINABLE postmapA #-}

prefilterA :: (ArrowChoice arr)
           => arr a Bool -> FoldA arr i a r -> FoldA arr i a r
prefilterA fltr (FoldA step start done) =
  FoldA (proc (Pair x a) -> do
            b <- fltr -< a
            if b
              then step -< Pair x a
              else returnA -< x)
        start
        done
{-# INLINABLE prefilterA #-}

-- | Creates a 'FoldA' from an arrow computation.
arrowFold :: (Arrow a)
          => a (acc,input) acc -- ^ The folding task
          -> FoldA' a input acc
arrowFold step =
  FoldA (arr onInput >>> step) id id
  where
    onInput (Pair acc x) = (acc,x)

-- | Creates a 'FoldA' that will never alter its accumulator's initial value,
-- just pass it around
arrowFold_ :: (Arrow a)
           => a (acc,input) ()
           -> FoldA a acc input ()
arrowFold_ task =
  rmap (const ()) $
  arrowFold $ proc (acc,input) -> do
    task -< (acc,input)
    returnA -< acc