kleene-list-0.1.0.0: src/Data/List/Kleene/Internal.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE TypeFamilies #-}
-- |
-- Module : Data.List.Kleene.Internal
-- Description : Common utility functions and definitions for the kleene-list package.
-- Copyright : (c) Donnacha Oisín Kidney, 2020
-- License : Apache
-- Maintainer : mail@doisinkidney.com
-- Stability : experimental
-- Portability : ghc
--
-- = WARNING
--
-- This module is considered __internal__.
--
-- The Package Versioning Policy __does not apply__.
--
-- This contents of this module may change __in any way whatsoever__
-- and __without any warning__ between minor versions of this package.
--
-- Authors importing this module are expected to track development
-- closely.
{-# OPTIONS_HADDOCK not-home #-}
module Data.List.Kleene.Internal where
import Control.DeepSeq (NFData (rnf))
import Data.Data (Data, Typeable)
import Data.Functor.Classes
import GHC.Generics (Generic)
import GHC.Exts (IsList)
import qualified GHC.Exts
import Control.Applicative
import Control.Monad
import Control.Monad.Fix
import Control.Monad.Zip
import Data.Foldable
import Prelude hiding (filter, head, scanl, scanr, tail)
-- | A list, based on the Kleene star.
-- This type is isomorphic to Haskell's standard @[]@ type, so it can be used
-- in the same way.
data Star a
= Nil
| Cons (Plus a)
deriving (Eq, Ord, Generic, Data, Typeable, Functor, Traversable)
infixr 5 :-
-- | A non-empty list type, based on the Kleene plus.
-- This type is isomorphic to 'Data.List.NonEmpty.NonEmpty' type, so it
-- can be used in the same way.
data Plus a
= (:-)
{ head :: a
, tail :: Star a
} deriving (Eq, Ord, Generic, Data, Typeable, Functor, Traversable)
instance Foldable Star where
foldr _ b Nil = b
foldr f b (Cons xs) = foldr f b xs
foldl _ b Nil = b
foldl f b (Cons xs) = foldl f b xs
foldl' _ !b Nil = b
foldl' f !b (Cons xs) = foldl' f b xs
foldl1 _ Nil = errorWithoutStackTrace "foldl1: empty list"
foldl1 f (Cons xs) = foldl1 f xs
foldr1 _ Nil = errorWithoutStackTrace "foldr1: empty list"
foldr1 f (Cons xs) = foldr1 f xs
foldMap _ Nil = mempty
foldMap f (Cons xs) = foldMap f xs
minimum Nil = errorWithoutStackTrace "minimum: empty list"
minimum (Cons xs) = minimum xs
maximum Nil = errorWithoutStackTrace "maximum: empty list"
maximum (Cons xs) = maximum xs
instance Foldable Plus where
foldr f b ~(x :- xs) = f x (foldr f b xs)
foldl f b ~(x :- xs) = foldl f (f b x) xs
foldl' f !b ~(x :- xs) = foldl' f (f b x) xs
foldl1 f ~(x :- xs) = foldl f x xs
foldr1 f = go
where
go (x :- xs) = case xs of
Nil -> x
Cons ys -> f x (go ys)
foldMap f ~(x :- xs) = f x <> foldMap f xs
null _ = False
minimum = foldr1 min
maximum = foldr1 max
instance Eq1 Star where
liftEq _ Nil Nil = True
liftEq eq (Cons xs) (Cons ys) = liftEq eq xs ys
liftEq _ _ _ = False
instance Eq1 Plus where
liftEq eq ~(x :- xs) (y :- ys) = eq x y && liftEq eq xs ys
instance Ord1 Star where
liftCompare _ Nil Nil = EQ
liftCompare _ Nil (Cons _) = LT
liftCompare _ (Cons _) Nil = GT
liftCompare c (Cons xs) (Cons ys) = liftCompare c xs ys
instance Ord1 Plus where
liftCompare c ~(x :- xs) ~(y :- ys) = c x y <> liftCompare c xs ys
instance Show1 Plus where
liftShowsPrec _ sp _ = sp . foldr (:) []
instance Show1 Star where
liftShowsPrec _ sp _ = sp . foldr (:) []
-- | A pattern for building up star lists as cons-lists.
--
-- >>> 1 :* 2 :* 3 :* Nil
-- [1,2,3]
infixr 5 :*
pattern (:*) :: a -> Star a -> Star a
pattern (:*) x xs = Cons (x :- xs)
{-# COMPLETE (:*), Nil #-}
-- | A pattern for building up plus lists as cons-lists.
--
-- >>> 1 :+ 2 :+ One 3
-- [1,2,3]
infixr 5 :+
pattern (:+) :: a -> Plus a -> Plus a
pattern (:+) x xs = x :- Cons xs
-- | A pattern for a singleton plus list.
pattern One :: a -> Plus a
pattern One x = x :- Nil
{-# COMPLETE (:+), One #-}
instance IsList (Star a) where
type Item (Star a) = a
fromList = foldr (:*) Nil
toList = foldr (:) []
instance IsList (Plus a) where
type Item (Plus a) = a
fromList [] = errorWithoutStackTrace "Cannot make plus from empty list"
fromList (x:xs) = x :- GHC.Exts.fromList xs
toList = foldr (:) []
instance Show a => Show (Star a) where
showsPrec n = showsPrec n . toList
instance Show a => Show (Plus a) where
showsPrec n = showsPrec n . toList
instance NFData a => NFData (Star a) where
rnf Nil = ()
rnf (Cons xs) = rnf xs
instance NFData a => NFData (Plus a) where
rnf (x :- xs) = rnf x `seq` rnf xs
instance Semigroup (Plus a) where
~(x :- xs) <> ys = x :+ (xs *<>+ ys)
(*<>+) :: Star a -> Plus a -> Plus a
Nil *<>+ ys = ys
Cons xs *<>+ ys = xs <> ys
instance Semigroup (Star a) where
Nil <> ys = ys
Cons xs <> ys = Cons (xs +<>* ys)
(+<>*) :: Plus a -> Star a -> Plus a
~(x :- xs) +<>* ys = x :- (xs <> ys)
instance Monoid (Star a) where
mempty = Nil
instance Applicative Star where
pure = Cons . pure
Nil <*> _ = Nil
f :* fs <*> xs = foldr ((:*) . f) (fs <*> xs) xs
liftA2 _ Nil _ = Nil
liftA2 f (x :* xs) ys = foldr ((:*) . f x) (liftA2 f xs ys) ys
-- |
-- >>> (,) <$> (1 :+ 2 :+ One 3) <*> ('a' :+ 'b' :+ One 'c')
-- [(1,'a'),(1,'b'),(1,'c'),(2,'a'),(2,'b'),(2,'c'),(3,'a'),(3,'b'),(3,'c')]
--
-- >>> liftA2 (,) (1 :+ 2 :+ One 3) ('a' :+ 'b' :+ One 'c')
-- [(1,'a'),(1,'b'),(1,'c'),(2,'a'),(2,'b'),(2,'c'),(3,'a'),(3,'b'),(3,'c')]
instance Applicative Plus where
pure = One
~(f' :- fs') <*> xs = f' (head xs) :- foldr ((:*) . f') (go fs') (tail xs)
where
go Nil = Nil
go (f :* fs) = foldr ((:*) . f) (go fs) xs
liftA2 f ~(x' :- xs') ys = f x' (head ys) :- foldr ((:*) . f x') (go xs') (tail ys)
where
go Nil = Nil
go (x :* xs) = foldr ((:*) . f x) (go xs) ys
instance Monad Star where
xs >>= f = foldr ((<>) . f) Nil xs
instance Monad Plus where
~(x :- xs) >>= f = f x +<>* go xs
where
go Nil = Nil
go (Cons ys) = Cons (ys >>= f)
instance Alternative Star where
(<|>) = (<>)
empty = Nil
instance MonadPlus Star
instance MonadFix Plus where
mfix f = case fix (f . head) of
~(x :- _) -> x :- mfix (tail . f)
instance MonadFix Star where
mfix f = case fix (f . head . unStar) of
Nil -> Nil
(x :* _) -> x :* mfix (tail . unStar . f)
where
unStar ~(Cons xs) = xs
instance MonadZip Plus where
mzip ~(x :- xs) ~(y :- ys) = (x, y) :- mzip xs ys
mzipWith f ~(x :- xs) ~(y :- ys) = f x y :- mzipWith f xs ys
munzip ~(~(y,z) :- xs) = (y :- ys, z :- zs)
where
~(ys,zs) = munzip xs
instance MonadZip Star where
mzip Nil _ = Nil
mzip _ Nil = Nil
mzip (Cons xs) (Cons ys) = Cons (mzip xs ys)
mzipWith _ Nil _ = Nil
mzipWith _ _ Nil = Nil
mzipWith f (Cons xs) (Cons ys) = Cons (mzipWith f xs ys)
munzip Nil = (Nil, Nil)
munzip (Cons xs) = (Cons ys, Cons zs)
where
~(ys,zs) = munzip xs
merge :: (a -> a -> Ordering) -> Star a -> Star a -> Star a
merge _ Nil ys = ys
merge cmp (Cons xs) ys = Cons (mergel cmp xs ys)
mergel :: (a -> a -> Ordering) -> Plus a -> Star a -> Plus a
mergel _ xs Nil = xs
mergel cmp xs (Cons ys) = mergelr cmp xs ys
merger :: (a -> a -> Ordering) -> Star a -> Plus a -> Plus a
merger _ Nil ys = ys
merger cmp (Cons xs) ys = mergelr cmp xs ys
mergelr :: (a -> a -> Ordering) -> Plus a -> Plus a -> Plus a
mergelr cmp xss@ ~(x :- xs) yss@ ~(y :- ys) = case cmp x y of
LT -> x :+ merger cmp xs yss
EQ -> x :+ y :- merge cmp xs ys
GT -> y :+ mergel cmp xss ys
treeFoldMap :: (a -> b) -> (b -> b -> b) -> Plus a -> b
treeFoldMap c f = go
where
go (One x) = c x
go (x :+ y :- xs) = go' (f (c x) (c y) :- pairMap xs)
pairMap (x1 :* x2 :* xs) = f (c x1) (c x2) :* pairMap xs
pairMap (x1 :* Nil) = c x1 :* Nil
pairMap Nil = Nil
go' (One x) = x
go' (x :+ y :- xs) = go' (f x y :- pairMap' xs)
pairMap' (x1 :* x2 :* xs) = f x1 x2 :* pairMap' xs
pairMap' xs = xs
-- |
-- >>> prescanlPlus (+) 0 [1,2,3]
-- [1,3,6]
prescanlPlus :: (b -> a -> b) -> b -> Plus a -> Plus b
prescanlPlus f b (x :- xs) = scanl f (f b x) xs
-- |
-- >>> prescanlStar (+) 0 [1,2,3]
-- [1,3,6]
prescanlStar :: (b -> a -> b) -> b -> Star a -> Star b
prescanlStar _ _ Nil = Nil
prescanlStar f b (Cons xs) = Cons (prescanlPlus f b xs)
-- | Functions the same as 'Data.List.scanl' in "Data.List".
--
-- >>> scanl (+) 0 [1,2,3]
-- [0,1,3,6]
scanl :: (b -> a -> b) -> b -> Star a -> Plus b
scanl f b xs = b :- prescanlStar f b xs
-- | Functions the same as 'Data.List.scanr' in "Data.List".
--
-- >>> scanr (+) 0 ([1,2,3] :: Star Int)
-- [6,5,3,0]
scanr :: Foldable f => (a -> b -> b) -> b -> f a -> Plus b
scanr f b = foldr (\x xs -> f x (head xs) :+ xs) (One b)
-- | Functions the same as 'Data.List.filter' in "Data.List".
--
-- >>> filter even ([1..5] :: Star Int)
-- [2,4]
filter :: Foldable f => (a -> Bool) -> f a -> Star a
filter p = foldr f Nil
where
f x xs
| p x = x :* xs
| otherwise = xs
takeStar :: Int -> Star a -> Star a
takeStar _ Nil = Nil
takeStar i (Cons xs) = takePlus i xs
takePlus :: Int -> Plus a -> Star a
takePlus 0 _ = Nil
takePlus i ~(x :- xs) = x :* takeStar (i-1) xs
indexPlus :: Plus a -> Int -> a
indexPlus xs 0 = head xs
indexPlus xs i = indexStar (tail xs) (i-1)
indexStar :: Star a -> Int -> a
indexStar Nil _ = errorWithoutStackTrace "index: empty list!"
indexStar (Cons xs) i = indexPlus xs i
-- $setup
-- >>> :set -XOverloadedLists