packages feed

strict-list-1.0.0.1: library/StrictList.hs

-- |
-- Definitions of strict linked list.
--
-- Most basic operations like `fmap`, `filter`, `<*>`
-- are implemented efficiently by producing an intermediate list in reversed order
-- and then reversing it to the original order to avoid stack recursion.
-- These intermediate reversed functions are exposed by the API,
-- because they very well may be useful for efficient implementations of data-structures built on top of list.
-- E.g., the <http://hackage.haskell.org/package/deque "deque"> package exploits them heavily.
--
-- One useful rule of thumb would be that
-- whenever you see that a function has a reversed counterpart,
-- that counterpart is faster and hence if you don't care about the order or
-- intend to reverse the list further down the line, you should give preference to that counterpart.
module StrictList
  ( -- * Strict list type
    StrictList (Cons, Nil),

    -- * Conversions
    toList,
    toListReversed,
    fromList,
    fromListReversed,

    -- * Basic transformations
    reverse,
    take,
    takeReversed,
    drop,
    filter,
    filterReversed,
    takeWhile,
    takeWhileReversed,
    dropWhile,
    takeWhileFromEnding,
    dropWhileFromEnding,
    span,
    spanReversed,
    spanFromEnding,
    break,
    breakReversed,

    -- * Queries
    match,
    uncons,
    head,
    last,
    tail,
    init,
    initReversed,

    -- * Zipping and application
    apZipping,
    apZippingReversed,

    -- * Reversed-order helpers
    prependReversed,
    mapReversed,
    apReversed,
    explodeReversed,
    joinReversed,
    mapMaybeReversed,
    catMaybesReversed,
  )
where

import Control.Applicative (Alternative (..), Applicative (..))
import Control.Arrow (first)
import Control.DeepSeq (NFData, NFData1)
import Control.Monad (Monad (..), MonadPlus (..))
import Data.Bool (Bool (..))
import Data.Data (Data)
import Data.Foldable (Foldable (foldl', foldr))
import Data.Function (const, flip, id, (.))
import Data.Functor (Functor (..), (<$>))
import Data.Functor.Alt (Alt (..))
import Data.Functor.Apply (Apply ((<.>)))
import Data.Functor.Bind (Bind (..))
import Data.Functor.Plus (Plus (..))
import Data.Hashable (Hashable)
import Data.Int (Int)
import Data.Maybe (Maybe (..))
import Data.Monoid (Monoid (..))
import Data.Ord (Ord (..), Ordering (..))
import Data.Semigroup (Semigroup (..))
import Data.Traversable (Traversable (sequenceA))
import qualified GHC.Exts
import GHC.Generics (Generic, Generic1)
import qualified Test.QuickCheck as Qc
import Prelude (Eq (..), Read, Show, pred)

-- |
-- Strict linked list.
data StrictList a = Cons !a !(StrictList a) | Nil
  deriving
    (Eq, Show, Read, Generic, Generic1, Data)

instance (Ord a) => Ord (StrictList a) where
  compare Nil Nil = EQ
  compare Nil _ = LT
  compare _ Nil = GT
  compare (Cons leftHead leftTail) (Cons rightHead rightTail) =
    case compare leftHead rightHead of
      EQ -> compare leftTail rightTail
      ordering -> ordering

instance GHC.Exts.IsList (StrictList a) where
  type Item (StrictList a) = a
  fromList = fromList
  toList = toList

instance Semigroup (StrictList a) where
  (<>) a b = case b of
    Nil -> a
    _ -> prependReversed (reverse a) b

instance Monoid (StrictList a) where
  mempty = Nil
  mappend = (<>)

instance Functor StrictList where
  fmap f = reverse . mapReversed f

instance Foldable StrictList where
  foldr step init =
    let loop = \case
          Cons head tail -> step head (loop tail)
          _ -> init
     in loop
  foldl' step init =
    let loop !acc = \case
          Cons head tail -> loop (step acc head) tail
          _ -> acc
     in loop init

instance Traversable StrictList where
  sequenceA = foldr (liftA2 Cons) (pure Nil)

instance Apply StrictList where
  (<.>) fList aList = apReversed (reverse fList) (reverse aList)

instance Applicative StrictList where
  pure a = Cons a Nil
  (<*>) = (<.>)

instance Alt StrictList where
  (<!>) = mappend

instance Plus StrictList where
  zero = mempty

instance Alternative StrictList where
  empty = zero
  (<|>) = (<!>)

instance Bind StrictList where
  (>>-) ma amb = reverse (explodeReversed amb ma)
  join = reverse . joinReversed

instance Monad StrictList where
  return = pure
  (>>=) = (>>-)

instance MonadPlus StrictList where
  mzero = empty
  mplus = (<|>)

instance (Hashable a) => Hashable (StrictList a)

instance (NFData a) => NFData (StrictList a)

instance NFData1 StrictList

instance (Qc.Arbitrary a) => Qc.Arbitrary (StrictList a) where
  arbitrary = fromList <$> Qc.arbitrary
  shrink = fmap fromList . Qc.shrink . toList

instance Qc.Arbitrary1 StrictList where
  liftArbitrary elemGen = fromList <$> Qc.liftArbitrary elemGen
  liftShrink elemShrink = fmap fromList . Qc.liftShrink elemShrink . toList

-- |
-- Convert to lazy list.
toList :: StrictList a -> [a]
toList = foldr (:) []

-- |
-- Convert to lazy list in normal form (with all elements and spine evaluated).
toListReversed :: StrictList a -> [a]
toListReversed = go []
  where
    go !outputList = \case
      Cons element list -> go (element : outputList) list
      Nil -> outputList

-- |
-- Construct from a lazy list.
fromList :: [a] -> StrictList a
fromList = reverse . fromListReversed

-- |
-- Reverse the list.
{-# INLINE reverse #-}
reverse :: StrictList a -> StrictList a
reverse = foldl' (flip Cons) Nil

-- |
-- Leave only the specified amount of elements.
{-# INLINE take #-}
take :: Int -> StrictList a -> StrictList a
take amount = reverse . takeReversed amount

-- |
-- Leave only the specified amount of elements, in reverse order.
takeReversed :: Int -> StrictList a -> StrictList a
takeReversed =
  let loop !output !amount =
        if amount > 0
          then \case
            Cons head tail -> loop (Cons head output) (pred amount) tail
            _ -> output
          else const output
   in loop Nil

-- |
-- Leave only the elements after the specified amount of first elements.
drop :: Int -> StrictList a -> StrictList a
drop amount =
  if amount > 0
    then \case
      Cons _ tail -> drop (pred amount) tail
      _ -> Nil
    else id

-- |
-- Leave only the elements satisfying the predicate.
{-# INLINE filter #-}
filter :: (a -> Bool) -> StrictList a -> StrictList a
filter predicate = reverse . filterReversed predicate

-- |
-- Leave only the elements satisfying the predicate,
-- producing a list in reversed order.
filterReversed :: (a -> Bool) -> StrictList a -> StrictList a
filterReversed predicate =
  let loop !newList = \case
        Cons head tail ->
          if predicate head
            then loop (Cons head newList) tail
            else loop newList tail
        Nil -> newList
   in loop Nil

-- |
-- Leave only the first elements satisfying the predicate.
{-# INLINE takeWhile #-}
takeWhile :: (a -> Bool) -> StrictList a -> StrictList a
takeWhile predicate = reverse . takeWhileReversed predicate

-- |
-- Leave only the first elements satisfying the predicate,
-- producing a list in reversed order.
takeWhileReversed :: (a -> Bool) -> StrictList a -> StrictList a
takeWhileReversed predicate =
  let loop !newList = \case
        Cons head tail ->
          if predicate head
            then loop (Cons head newList) tail
            else newList
        _ -> newList
   in loop Nil

-- |
-- Drop the first elements satisfying the predicate.
dropWhile :: (a -> Bool) -> StrictList a -> StrictList a
dropWhile predicate = \case
  Cons head tail ->
    if predicate head
      then dropWhile predicate tail
      else Cons head tail
  Nil -> Nil

-- |
-- An optimized version of the same predicate applied to `takeWhile` and `dropWhile`.
-- IOW,
--
-- >span predicate list = (takeWhile predicate list, dropWhile predicate list)
{-# INLINE span #-}
span :: (a -> Bool) -> StrictList a -> (StrictList a, StrictList a)
span predicate = first reverse . spanReversed predicate

-- |
-- Same as `span`, only with the first list in reverse order.
spanReversed :: (a -> Bool) -> StrictList a -> (StrictList a, StrictList a)
spanReversed predicate =
  let buildPrefix !prefix = \case
        Cons head tail ->
          if predicate head
            then buildPrefix (Cons head prefix) tail
            else (prefix, Cons head tail)
        _ -> (prefix, Nil)
   in buildPrefix Nil

-- |
-- An opposite version of `span`. I.e.,
--
-- >break predicate = span (not . predicate)
{-# INLINE break #-}
break :: (a -> Bool) -> StrictList a -> (StrictList a, StrictList a)
break predicate = first reverse . breakReversed predicate

-- |
-- Same as `break`, only with the first list in reverse order.
breakReversed :: (a -> Bool) -> StrictList a -> (StrictList a, StrictList a)
breakReversed predicate =
  let buildPrefix !prefix = \case
        Cons head tail ->
          if predicate head
            then (prefix, Cons head tail)
            else buildPrefix (Cons head prefix) tail
        _ -> (prefix, Nil)
   in buildPrefix Nil

-- |
-- Same as @(`takeWhile` predicate . `reverse`)@.
-- E.g.,
--
-- >>> takeWhileFromEnding (> 2) (fromList [1,4,2,3,4,5])
-- fromList [5,4,3]
{-# INLINE takeWhileFromEnding #-}
takeWhileFromEnding :: (a -> Bool) -> StrictList a -> StrictList a
takeWhileFromEnding predicate =
  foldl'
    ( \newList a ->
        if predicate a
          then Cons a newList
          else Nil
    )
    Nil

-- |
-- Same as @(`dropWhile` predicate . `reverse`)@.
-- E.g.,
--
-- >>> dropWhileFromEnding (> 2) (fromList [1,4,2,3,4,5])
-- fromList [2,4,1]
dropWhileFromEnding :: (a -> Bool) -> StrictList a -> StrictList a
dropWhileFromEnding predicate =
  let loop confirmed unconfirmed = \case
        Cons head tail ->
          if predicate head
            then loop confirmed (Cons head unconfirmed) tail
            else
              let !newConfirmed = Cons head unconfirmed
               in loop newConfirmed newConfirmed tail
        Nil -> confirmed
   in loop Nil Nil

-- |
-- Same as @(`span` predicate . `reverse`)@.
spanFromEnding :: (a -> Bool) -> StrictList a -> (StrictList a, StrictList a)
spanFromEnding predicate =
  let loop !confirmedPrefix !unconfirmedPrefix !suffix = \case
        Cons head tail ->
          if predicate head
            then loop confirmedPrefix (Cons head unconfirmedPrefix) (Cons head suffix) tail
            else
              let !prefix = Cons head unconfirmedPrefix
               in loop prefix prefix Nil tail
        Nil -> (suffix, confirmedPrefix)
   in loop Nil Nil Nil

-- |
-- Pattern match on list using functions.
--
-- Allows to achieve all the same as `uncons` only without intermediate `Maybe`.
--
-- Essentially provides the same functionality as `either` for `Either` and `maybe` for `Maybe`.
{-# INLINE match #-}
match :: result -> (element -> StrictList element -> result) -> StrictList element -> result
match nil cons = \case
  Cons head tail -> cons head tail
  Nil -> nil

-- |
-- Get the first element and the remainder of the list if it's not empty.
{-# INLINE uncons #-}
uncons :: StrictList a -> Maybe (a, StrictList a)
uncons = \case
  Cons head tail -> Just (head, tail)
  _ -> Nothing

-- |
-- Get the first element, if list is not empty.
{-# INLINE head #-}
head :: StrictList a -> Maybe a
head = \case
  Cons head _ -> Just head
  _ -> Nothing

-- |
-- Get the last element, if list is not empty.
{-# INLINE last #-}
last :: StrictList a -> Maybe a
last =
  let loop !previous = \case
        Cons head tail -> loop (Just head) tail
        _ -> previous
   in loop Nothing

-- |
-- Get all elements of the list but the first one.
{-# INLINE tail #-}
tail :: StrictList a -> StrictList a
tail = \case
  Cons _ tail -> tail
  Nil -> Nil

-- |
-- Get all elements but the last one.
{-# INLINE init #-}
init :: StrictList a -> StrictList a
init = reverse . initReversed

-- |
-- Get all elements but the last one, producing the results in reverse order.
initReversed :: StrictList a -> StrictList a
initReversed =
  let loop !confirmed !unconfirmed = \case
        Cons head tail -> loop unconfirmed (Cons head unconfirmed) tail
        _ -> confirmed
   in loop Nil Nil

-- |
-- Apply the functions in the left list to elements in the right one.
{-# INLINE apZipping #-}
apZipping :: StrictList (a -> b) -> StrictList a -> StrictList b
apZipping left right = apZippingReversed (reverse left) (reverse right)

-- |
-- Apply the functions in the left list to elements in the right one,
-- producing a list of results in reversed order.
apZippingReversed :: StrictList (a -> b) -> StrictList a -> StrictList b
apZippingReversed =
  let loop bList = \case
        Cons f fTail -> \case
          Cons a aTail -> loop (Cons (f a) bList) fTail aTail
          _ -> bList
        _ -> const bList
   in loop Nil

-- ** Reversed intermediate functions used in instances

-------------------------

-- |
-- Construct from a lazy list in reversed order.
{-# INLINE fromListReversed #-}
fromListReversed :: [a] -> StrictList a
fromListReversed = foldl' (flip Cons) Nil

-- |
-- Add elements of the left list in reverse order
-- in the beginning of the right list.
{-# INLINE prependReversed #-}
prependReversed :: StrictList a -> StrictList a -> StrictList a
prependReversed = \case
  Cons head tail -> prependReversed tail . Cons head
  Nil -> id

-- |
-- Map producing a list in reversed order.
mapReversed :: (a -> b) -> StrictList a -> StrictList b
mapReversed f =
  let loop !newList = \case
        Cons head tail -> loop (Cons (f head) newList) tail
        _ -> newList
   in loop Nil

-- |
-- Apply the functions in the left list to every element in the right one,
-- producing a list of results in reversed order.
{-# INLINE apReversed #-}
apReversed :: StrictList (a -> b) -> StrictList a -> StrictList b
apReversed fList aList = foldl' (\z f -> foldl' (\z a -> Cons (f a) z) z aList) Nil fList

-- |
-- Use a function to produce a list of lists and then concat them sequentially,
-- producing the results in reversed order.
{-# INLINE explodeReversed #-}
explodeReversed :: (a -> StrictList b) -> StrictList a -> StrictList b
explodeReversed amb = foldl' (\z -> foldl' (flip Cons) z . amb) Nil

-- |
-- Join (concat) producing results in reversed order.
{-# INLINE joinReversed #-}
joinReversed :: StrictList (StrictList a) -> StrictList a
joinReversed = foldl' (foldl' (flip Cons)) Nil

-- |
-- Map and filter elements producing results in reversed order.
{-# INLINE mapMaybeReversed #-}
mapMaybeReversed :: (a -> Maybe b) -> StrictList a -> StrictList b
mapMaybeReversed f = go Nil
  where
    go !outputList = \case
      Cons inputElement inputTail -> case f inputElement of
        Just outputElement -> go (Cons outputElement outputList) inputTail
        Nothing -> go outputList inputTail
      Nil -> outputList

-- |
-- Keep only the present values, reversing the order.
catMaybesReversed :: StrictList (Maybe a) -> StrictList a
catMaybesReversed = go Nil
  where
    go !outputList = \case
      Cons inputElement inputTail -> case inputElement of
        Just outputElement -> go (Cons outputElement outputList) inputTail
        Nothing -> go outputList inputTail
      Nil -> outputList