packages feed

primus-0.1.0.0: src/Primus/Fold.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}

{- |
Module      : Primus.Fold
Description : fold and unfolds
Copyright   : (c) Grant Weyburne, 2022
License     : BSD-3
-}
module Primus.Fold (
  -- * fill a container
  fillTraversable,
  fillTraversableExact,
  traverseLR,

  -- * extended traversals with access to past and future input
  histMapL,
  histMapR,
  histMapL',
  histMapR',

  -- * change inside of a container
  wrapL,
  wrap1,

  -- * fold and unfolds
  pFoldR,
  pFoldL,
  unfoldl,
  unfoldrM,
  unfoldlM,

  -- * zip
  zipExtrasT,
  zipExtrasRight,
  zipWithExact,
  zipExact,
  zipWithT,

  -- * compare container lengths
  CLCount (..),
  compareLength,
  compareLengthBy,
  compareLengths,
  clOrdering,

  -- * pad containers
  padR,
  padL,

  -- * chunking
  chunkN,
  chunkN',

  -- * scan
  postscanl,
  postscanr,

  -- * miscellaneous
  initsT,
  tailsT,
  reverseT,
  sortByT,
  unzipF,
  reverseF,
) where

import Control.Applicative
import Control.Arrow
import Control.Monad
import Data.Bool
import Data.Foldable
import Data.Kind
import qualified Data.List as L
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.List.NonEmpty as N
import Data.Semigroup.Foldable
import Data.These
import Data.These.Combinators
import Primus.Error
import Primus.Extra

data Hist a b = Hist ![a] ![a] !b

getHistZ :: Hist a b -> b
getHistZ (Hist _ _ z) = z

{- | left fold over a list giving the caller access to past and future input and state "z"
 if you want previous "b" values then put it in "z"
-}
histMapImpl ::
  Traversable t =>
  Bool ->
  ([a] -> [a] -> z -> a -> (z, b)) ->
  z ->
  t a ->
  (z, t b)
histMapImpl isright f z0 lst =
  first getHistZ $
    bool
      L.mapAccumL
      L.mapAccumR
      isright
      g
      (Hist [] (bool toList reverseF isright lst) z0)
      lst
 where
  g (Hist ps ft z) a =
    case ft of
      [] -> programmError "histMapImpl: ran out of data!"
      _ : ft0 ->
        let (z', b) = f ps ft0 z a
         in (Hist (a : ps) ft0 z', b)

-- | left/right fold over a list giving the caller access state "z" (for finite containers only)
histMapL
  , histMapR ::
    Traversable t =>
    ([a] -> [a] -> z -> a -> (z, b)) ->
    z ->
    t a ->
    (z, t b)
histMapL = histMapImpl False
histMapR = histMapImpl True

-- | left/right fold that gives access to past input (reverse order) and future input
pFoldL, pFoldR :: forall a b. ([a] -> [a] -> b -> a -> b) -> b -> [a] -> b
pFoldR f n = go []
 where
  go :: [a] -> [a] -> b
  go pres = \case
    [] -> n
    a : as -> f pres as (go (a : pres) as) a
pFoldL f = go []
 where
  go :: [a] -> b -> [a] -> b
  go pres !z = \case
    [] -> z
    a : as -> go (a : pres) (f pres as z a) as

histMapImpl' ::
  forall a b t.
  Traversable t =>
  Bool ->
  ([a] -> [a] -> a -> b) ->
  t a ->
  t b
histMapImpl' isright f = snd . bool histMapL histMapR isright g ()
 where
  g :: [a] -> [a] -> () -> a -> ((), b)
  g ps ft () a = ((), f ps ft a)

-- | same as 'histMapL' or 'histMapR' but skips state
histMapL'
  , histMapR' ::
    forall a b t.
    Traversable t =>
    ([a] -> [a] -> a -> b) ->
    t a ->
    t b
histMapL' = histMapImpl' False
histMapR' = histMapImpl' True

-- | like 'Data.List.unfoldr' but reverses the order of the list
unfoldl :: forall s a. (s -> Maybe (a, s)) -> s -> [a]
unfoldl f = go []
 where
  go :: [a] -> s -> [a]
  go as !s = case f s of
    Nothing -> as
    Just (a, s1) -> go (a : as) s1

-- | monadic unfoldr
unfoldrM :: forall m s a. Monad m => (s -> m (Maybe (a, s))) -> s -> m [a]
unfoldrM f s = do
  mas <- f s
  case mas of
    Nothing -> return []
    Just (a, s') -> (a :) <$> unfoldrM f s'

-- | monadic unfoldl
unfoldlM :: forall m s a. Monad m => (s -> m (Maybe (a, s))) -> s -> m [a]
unfoldlM f = go []
 where
  go :: [a] -> s -> m [a]
  go as s = do
    mas <- f s
    case mas of
      Nothing -> return as
      Just (a, s') -> go (a : as) s'

-- | traverse a container using 'StateLR'
traverseLR ::
  forall t a b c.
  Traversable t =>
  (c -> a -> Either String (c, b)) ->
  c ->
  t a ->
  Either String (c, t b)
traverseLR f c0 ta =
  let g :: a -> StateLR String c b
      g a = StateLR $ \c -> f c a
   in unStateLR (traverse g ta) c0

-- | fill a traversable with a list and fail if not enough data
fillTraversable ::
  forall t a z.
  Traversable t =>
  t z ->
  [a] ->
  Either String ([a], t a)
fillTraversable tz as0 =
  let g :: z -> StateLR String [a] a
      g _ = StateLR $ \case
        [] -> Left "fillTraversable: not enough data"
        d : ds' -> Right (ds', d)
   in unStateLR (traverse g tz) as0

-- | fill a traversable with a list and fail if there are leftovers: see 'fillTraversable'
fillTraversableExact ::
  forall f a z.
  Traversable f =>
  f z ->
  [a] ->
  Either String (f a)
fillTraversableExact = g .@ fillTraversable
 where
  g :: Either String ([a], b) -> Either String b
  g = \case
    Right ([], ret) -> Right ret
    Right (_ : _, _) -> Left "fillTraversableExact: too many elements found"
    Left e -> Left e

-- | run a function against the contents of the 'Foldable1' container as a nonempty list
wrap1 ::
  forall (g :: Type -> Type) a b.
  (Traversable g, Foldable1 g) =>
  (NonEmpty a -> NonEmpty b) ->
  g a ->
  Either String (g b)
wrap1 f gx = fillTraversableExact gx (toList (f (toNonEmpty gx)))

-- | run a function against the contents of the 'Foldable' container as a list
wrapL ::
  forall (g :: Type -> Type) a b.
  (Traversable g) =>
  ([a] -> [b]) ->
  g a ->
  Either String (g b)
wrapL f gx = fillTraversableExact gx (f (toList gx))

-- | pad fill "as" to the right or left with values from "zs"
padR, padL :: forall t a. Traversable t => t a -> [a] -> Either String (t a)
padR = padImpl True
padL = padImpl False

-- | pad fill "as" to the left/right with values from "zs"
padImpl :: forall t a. Traversable t => Bool -> t a -> [a] -> Either String (t a)
padImpl isright as zs =
  let (rs, zz) = bool (L.mapAccumR f (reverseF zs)) (L.mapAccumL f zs) isright as
   in case rs of
        [] -> Right zz
        _ : _ -> Left $ "pad" ++ bool "L" "R" isright ++ ": negative fill: would need to truncate the data"
 where
  f :: [a] -> a -> ([a], a)
  f xs a =
    case xs of
      [] -> ([], a)
      b : bs -> (bs, b)

-- | have to call a second time if the left container is bigger than the right one
zipExtrasT :: forall a b t. Traversable t => t a -> t b -> t (These a b)
zipExtrasT xs ys =
  let (rs, ret) = zipExtrasRight (toList xs) ys
   in case rs of
        [] -> ret
        _ : _ -> swapThese <$> zipExtrasT ys xs

-- | zip a foldable into a traversable container and return any leftovers
zipExtrasRight ::
  forall a b t.
  Traversable t =>
  [a] ->
  t b ->
  ([a], t (These a b))
zipExtrasRight = L.mapAccumL f
 where
  f :: [a] -> b -> ([a], These a b)
  f zs b = case zs of
    [] -> ([], That b)
    a : as -> (as, These a b)

-- | predicate for 'CEQ'
clOrdering :: CLCount b -> Maybe Ordering
clOrdering = \case
  CError{} -> Nothing
  CLT{} -> Just LT
  CEQ -> Just EQ
  CGT -> Just GT

-- | difference between two foldables but quick exit if lhs is larger than rhs
data CLCount b
  = -- | error
    CError !String
  | -- | leftovers from rhs: ie lhs is smaller than rhs
    CLT !(NonEmpty b)
  | -- | same size
    CEQ
  | -- | lhs is larger than rhs
    CGT
  deriving stock (Ord, Show, Eq, Functor, Traversable, Foldable)

-- | compare lengths of foldables
compareLengths :: Foldable t => NonEmpty (t a) -> [CLCount a]
compareLengths (xs :| xss) = map (compareLengthBy mempty xs) xss

-- | compare length where lhs or rhs can be infinite but not both
compareLength ::
  forall t u a b.
  (Foldable t, Foldable u) =>
  t a ->
  u b ->
  CLCount b
compareLength = compareLengthBy mempty

-- | compare length where lhs or rhs can be infinite but not both
compareLengthBy ::
  forall t u a b.
  (Foldable t, Foldable u) =>
  (Int -> a -> b -> Maybe String) ->
  t a ->
  u b ->
  CLCount b
compareLengthBy p xs ys =
  foldr f g xs (0, toList ys)
 where
  g :: (Int, [b]) -> CLCount b
  g (_, zs) = case zs of
    [] -> CEQ
    w : ws -> CLT (w :| ws)
  f :: a -> ((Int, [b]) -> CLCount b) -> (Int, [b]) -> CLCount b
  f a k (i, zs) = case zs of
    [] -> CGT -- quickexit
    b : bs -> case p i a b of
      Nothing -> k (i + 1, bs)
      Just e -> CError e

-- | 'zipWith' with an Applicative result
zipWithT ::
  (Applicative f, Traversable t, Applicative t) =>
  (a -> b -> f c) ->
  t a ->
  t b ->
  f (t c)
zipWithT f = sequenceA .@ liftA2 f

-- | fills a container with chunks using a user supplied unfold function
chunkN ::
  forall t s b z.
  Traversable t =>
  (s -> Either String (s, b)) ->
  t z ->
  s ->
  Either String (s, t b)
chunkN f tz = unStateLR (traverse (const (StateLR f)) tz)

-- | similar to 'chunkN' but "s" is restricted to a foldable: if there is data left then will fail
chunkN' ::
  forall t a u b z.
  (Traversable t, Foldable u) =>
  (u a -> Either String (u a, b)) ->
  t z ->
  u a ->
  Either String (t b)
chunkN' f tz s = do
  (s', ret) <- chunkN g tz s
  if null s'
    then Right ret
    else Left "chunkN': there is still data remaining at eof"
 where
  g s' =
    if null s'
      then Left "chunkN': not enough data"
      else f s'

-- | splits a container "u" into parts of length "len" and fills container "t"
zipWithExact ::
  forall t u a b c.
  (Traversable t, Foldable u) =>
  (a -> b -> c) ->
  t a ->
  u b ->
  Either String (t c)
zipWithExact f ta ub = do
  let g a = StateLR $ \case
        [] -> Left "zipWithExact: lhs has more data"
        b : bs -> Right (bs, f a b)
  (vx, ret) <- unStateLR (traverse g ta) (toList ub)
  if null vx
    then Right ret
    else Left "zipWithExact: lhs has less data"

-- | see 'zipWithExact'
zipExact ::
  forall t u a b.
  (Traversable t, Foldable u) =>
  t a ->
  u b ->
  Either String (t (a, b))
zipExact = zipWithExact (,)

-- | combines state and failure as a monad
newtype StateLR e s a = StateLR {unStateLR :: s -> Either e (s, a)}
  deriving stock (Functor)

instance Applicative (StateLR e s) where
  pure a = StateLR $ \s -> Right (s, a)
  (<*>) = ap

instance Monad (StateLR e s) where
  return = pure
  StateLR sa >>= amb =
    StateLR $ \s -> case sa s of
      Left e -> Left e
      Right (s1, a) -> unStateLR (amb a) s1

-- | 'Data.List.inits' for a traversable container
initsT :: forall a t. Traversable t => t a -> t (NonEmpty a)
initsT ta = case toList ta of
  [] -> fmap pure ta
  i : is -> frp $ fillTraversableExact ta (map (i :|) (L.inits is))

-- | 'Data.List.tails' for a traversable container
tailsT :: forall a t. Traversable t => t a -> t (NonEmpty a)
tailsT ta = forceRight "tailsT" $ do
  (xs, ret) <- traverseLR g (toList ta) ta
  case xs of
    [] -> pure ret
    _ : _ -> Left "extra data at eof"
 where
  g :: [a] -> p -> Either String ([a], NonEmpty a)
  g s _ = case s of
    [] -> Left "ran out of data"
    a : as -> Right (as, a :| as)

-- | 'Data.List.reverse' for a traversable container
reverseT :: forall a t. Traversable t => t a -> t a
reverseT = frp . wrapL reverse

-- | 'Data.List.sortBy' for a traversable container
sortByT :: forall a t. Traversable t => (a -> a -> Ordering) -> t a -> t a
sortByT f = frp . wrapL (L.sortBy f)

-- | 'N.scanr' for a traversable that drops the last value
postscanr :: Traversable f => (a -> b -> b) -> b -> f a -> f b
postscanr f c = frp . wrapL (N.init . N.scanr f c)

-- | 'N.scanl' for a traversable that drops the first value
postscanl :: Traversable f => (b -> a -> b) -> b -> f a -> f b
postscanl f c = frp . wrapL (N.tail . N.scanl f c)

-- | unzip for a functor of pairs
unzipF :: Functor f => f (a, b) -> (f a, f b)
unzipF = fmap fst &&& fmap snd

-- | reverse a foldable
reverseF :: Foldable t => t a -> [a]
reverseF = foldl' (flip (:)) []