{-# LANGUAGE Trustworthy #-} -- can't use Safe due to IsList instance {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE PartialTypeSignatures #-} {-# LANGUAGE OverloadedLists #-} -- {-# LANGUAGE OverloadedStrings #-} -- | -- Module : Control.Monad.List.NonEmpty.Exotic -- Description : Non-standard monads on the non-empty list functor -- Copyright : (c) Dylan McDermott, Maciej Piróg, Tarmo Uustalu, 2020 -- License : MIT -- Maintainer : maciej.adam.pirog@gmail.com -- Stability : experimental -- Portability : portable -- -- The usual list monad is only one of infinitely many ways to turn -- the 'NonEmpty.NonEmpty' (list) functor into a monad. This module -- collects a number of such exotic "non-empty list monads". Most of -- them have been introduced in the paper [Degrading -- Lists](https://raw.githubusercontent.com/maciejpirog/exotic-list-monads/master/degrading-lists.pdf) -- by Dylan McDermott, Maciej Piróg, Tarmo Uustalu (PPDP 2020). -- -- __Notes:__ -- -- * Types marked with \"(?)\" have not been formally verified to be -- monads (yet), though they were thoroughly tested with billions of -- QuickCheck tests. -- -- * Monads in this module are presented in terms of @join@ rather -- than '>>=', while 'return' is singleton, unless stated otherwise -- for a particular monad (e.g., 'HeadTails', 'HeadsTail', or -- 'IdXList'). -- -- * For readability, code snippets in this documentation assume the -- @OverloadedLists@ and @OverloadedStrings@ extensions, which allow -- us to omit some @newtype@ constructors. Example definitions of -- joins of monads always skip the @newtype@ constructors, that is, -- assume '>>=' is always defined as follows for a particular local -- @join@. -- -- @ -- m '>>=' f = 'wrap' $ join $ 'fmap' ('unwrap' . f) $ 'unwrap' m -- where -- join = ... -- @ -- -- * Sometimes it is more readable to define the join in terms of -- possibly-empty lists. In such a case, we call the local function -- @joinList@: -- -- @ -- m '>>=' f = 'wrap' $ 'GHC.Exts.fromList' $ joinList $ 'map' ('GHC.Exts.toList' . 'unwrap' . f) $ 'GHC.Exts.toList' $ 'unwrap' m -- where -- joinList = ... -- @ -- -- -- * The definitions of monads are optimized for readability and not -- run-time performance. This is because the monads in this module -- don't seem to be of any practical use, they are more of a -- theoretical curiosity. module Control.Monad.List.NonEmpty.Exotic ( -- * Non-empty monads in general IsNonEmpty(..) , NonEmptyMonad(..) -- ** More on non-empty lists , isSingle , splitSnoc , nonEmptyConcat , (+++) , nonEmptyAll , nonEmptyAny -- * Monads from magmas -- $magmas , Magma(..) , FreeRBM(..) -- ** The Keeper monad , XY , Keeper(..) -- ** The Non-Empty Discrete Hybrid monad , YZ , DiscreteHybridNE(..) -- ** The Non-Empty Discrete Op-Hybrid monad , XZ , OpDiscreteHybridNE(..) -- ** The Non-Empty Maze Walk monad , PalindromeMagma , MazeWalkNE(..) -- ** The Non-Empty Stutter monad , StutterMagma , StutterNE(..) -- * Other monads with finite presentation -- $others -- ** The Head-Tails monad , HeadTailTail(..) , HeadTails(..) , foldHeadTails -- ** The Heads-Tail monad , HeadHeadTail(..) , HeadsTail(..) , foldHeadsTail -- * Other monads -- ** The ΑΩ monad (?) , AlphaOmega(..) -- * Constructions on non-empty monads -- ** The dual non-empty list monad , DualNonEmptyMonad(..) -- ** The @Identity@ ⨉ @List@ monad , IdXList(..) -- ** Short-front monads , HasShortFront , ShortFront(..) -- ** Short-rear monads , HasShortRear , ShortRear(..) ) where import Data.List.NonEmpty (NonEmpty(..)) import qualified Data.List.NonEmpty as NonEmpty import Prelude hiding ((<>)) import Control.Monad (ap, join) import GHC.Exts (IsList(..), IsString(..), Constraint) import GHC.TypeLits import Data.Proxy import qualified Data.Semigroup (Semigroup) import Control.Monad.List.Exotic (ListMonad, palindromize) import qualified Control.Monad.List.Exotic as List.Exotic (ListMonad(..)) --------------------------- -- Non-empty list monads -- --------------------------- -- | This class collects types that are isomorphic to non-empty -- lists. It mimics the 'GHC.Exts.IsList' class. class IsNonEmpty l where type ItemNE l fromNonEmpty :: NonEmpty (ItemNE l) -> l toNonEmpty :: l -> NonEmpty (ItemNE l) instance IsNonEmpty (NonEmpty a) where type ItemNE (NonEmpty a) = a fromNonEmpty = id toNonEmpty = id -- | In this module, a \"non-empty monad\" is a monad in which the -- underlying functor is isomorphic to 'Data.List.NonEmpty.NonEmpty'. class (Monad m) => NonEmptyMonad m where wrap :: NonEmpty a -> m a default wrap :: (IsNonEmpty (m a), ItemNE (m a) ~ a) => NonEmpty a -> m a wrap = fromNonEmpty unwrap :: m a -> NonEmpty a default unwrap :: (IsNonEmpty (m a), ItemNE (m a) ~ a) => m a -> NonEmpty a unwrap = toNonEmpty instance NonEmptyMonad NonEmpty -- | Split a non empty list to reveal the last element. splitSnoc :: NonEmpty a -> ([a], a) splitSnoc (x :| []) = ([], x) splitSnoc (x :| xs) = (x : init xs, last xs) -- | Check if a list is a singleton. isSingle :: NonEmpty a -> Bool isSingle (_ :| []) = True isSingle _ = False -- | 'concat' for non-empty lists. nonEmptyConcat :: NonEmpty (NonEmpty a) -> NonEmpty a nonEmptyConcat = join -- | '++' for non-empty lists. (+++) :: NonEmpty a -> NonEmpty a -> NonEmpty a a +++ b = nonEmptyConcat [a, b] -- OverloadedLists -- | 'all' for non-empty lists. nonEmptyAll :: (a -> Bool) -> NonEmpty a -> Bool nonEmptyAll p (x :| xs) = p x && all p xs -- | 'any' for non-empty lists. nonEmptyAny :: (a -> Bool) -> NonEmpty a -> Bool nonEmptyAny p (x :| xs) = p x || any p xs ------------ -- Magmas -- ------------ -- $magmas -- -- This section contains monads that come about from free algebras of -- theories with one binary operation, that is, subcalsses of 'Magma' -- with no additional methods, but additional equations. -- | A very simple algebraic theory with one binary operations and no -- equations. class Magma a where (<>) :: a -> a -> a -- | The name of the class stands for __free right-braketed__ -- (subclass of) __magma__. (compare -- 'Control.Monad.List.Exotic.FreeRBPM' for more detailed -- explanation). -- -- We consider theories @c@ with one equation of the following shape: -- -- @ -- (x '<>' y) '<>' z == ... -- @ -- -- and normal forms of the following shape: -- -- @ -- x '<>' (y '<>' ( ... (z '<>' t) ... )) -- @ -- -- An instance @FreeRBM m c@ means that the monad @m@ comes about from -- free algebras of the theory @c@. For such monads and theories, we -- can define the following function: -- -- @ -- foldRBM f (toNonEmpty -> toList -> xs) = foldr1 (<>) (map f xs) -- @ -- -- which is the unique lifting of an interpretation of generators to a -- homomorphism (between algebras of this theory) from the list monad -- to any algebra (an instance) of @c@. -- -- Note that the default definition of 'foldRBM' is always the right -- one for right-bracketed subclasses of 'Magma', so it is -- enough to declare the relationship, for example: -- -- @ -- instance FreeRBM 'NonEmpty' 'Data.Semigroup.Semigroup' -- @ class (NonEmptyMonad m) => FreeRBM m (c :: * -> Constraint) | m -> c where foldRBM :: (Magma a, c a) => (x -> a) -> m x -> a foldRBM f (unwrap -> toList -> xs) = foldr1 (<>) (map f xs) instance FreeRBM NonEmpty Data.Semigroup.Semigroup ---------------------- -- The Keeper monad -- ---------------------- -- | Instances should satisfy the following equation: -- -- @ -- (x '<>' y) '<>' z == x '<>' y -- @ class (Magma a) => XY a -- | The keeper monad arises from free 'XY' magmas. Its join (in terms -- of @joinList@) is given as follows: -- -- @ -- joinList xss = map head (takeWhile 'Control.Monad.List.Exotic.isSingle' (init xss)) -- ++ head (dropWhile 'Control.Monad.List.Exotic.isSingle' (init xss) ++ [last xss]) -- @ -- -- Examples: -- -- >>> toList $ unwrap (join ["a", "b", "c", "hello", "there"] :: Keeper Char) -- "abchello" -- >>> toList $ unwrap (join ["a", "b", "c", "hello"] :: Keeper Char) -- "abchello" newtype Keeper a = Keeper { unKeeper :: NonEmpty a } deriving (Functor, Show, Eq) instance Applicative Keeper where pure = return (<*>) = ap instance Monad Keeper where return a = Keeper $ [a] -- OverloadedLists Keeper xs >>= f = Keeper $ join $ NonEmpty.map (unKeeper . f) xs where join (splitSnoc -> (xss, xs)) = fromList $ map NonEmpty.head (takeWhile isSingle xss) ++ toList (head (dropWhile isSingle xss ++ [xs])) -- OverloadedLists instance IsNonEmpty (Keeper a) where type ItemNE (Keeper a) = a fromNonEmpty = Keeper toNonEmpty = unKeeper instance NonEmptyMonad Keeper instance Magma (Keeper a) where m <> t = join $ Keeper $ [m, t] instance XY (Keeper a) instance FreeRBM Keeper XY -- The following two are needed for examples in the docs: instance IsList (Keeper a) where type Item (Keeper a) = a fromList = fromNonEmpty . fromList toList = toList . toNonEmpty instance IsString (Keeper Char) where fromString = fromList ----------------------------------------- -- The Non-Empty Discrete Hybrid monad -- ----------------------------------------- -- | Instances should satisfy the following equation: -- -- @ -- (x '<>' y) '<>' z == y '<>' z -- @ class (Magma a) => YZ a -- | The non-empty discrete hybrid monad arises from free 'YZ' -- magmas. Its join (in terms of @joinList@) can be given as follows: -- -- @ -- joinList xss = map last (init xss) ++ last xss -- @ -- -- See the possibly-empty version -- ('Control.Monad.List.Exotic.DiscreteHybrid') for more details. newtype DiscreteHybridNE a = DiscreteHybridNE { unDiscreteHybridNE :: NonEmpty a } deriving (Functor, Show, Eq) instance Applicative DiscreteHybridNE where pure = return (<*>) = ap instance Monad DiscreteHybridNE where return a = DiscreteHybridNE $ [a] -- OverloadedLists DiscreteHybridNE xs >>= f = DiscreteHybridNE $ join $ NonEmpty.map (unDiscreteHybridNE . f) xs where join (splitSnoc -> (xss, xs)) = fromList (map NonEmpty.last xss ++ toList xs) instance IsNonEmpty (DiscreteHybridNE a) where type ItemNE (DiscreteHybridNE a) = a fromNonEmpty = DiscreteHybridNE toNonEmpty = unDiscreteHybridNE instance NonEmptyMonad DiscreteHybridNE instance Magma (DiscreteHybridNE a) where m <> t = join $ DiscreteHybridNE $ [m, t] instance YZ (DiscreteHybridNE a) instance FreeRBM DiscreteHybridNE YZ -------------------------------------------- -- The Non-Empty Discrete Op-Hybrid monad -- ------------------------------------------- -- | Instances should satisfy the following equation: -- -- @ -- (x '<>' y) '<>' z == x '<>' z -- @ class (Magma a) => XZ a -- | The non-empty discrete op-hybrid monad arises from free 'XZ' -- magmas. It is dual to the 'DiscreteHybridNE' monad (but in a -- different dimension than 'DualNonEmptyMonad'). Its join (in terms -- of @joinList@) can be given as follows: -- -- @ -- joinList xss = map head (init xss) ++ last xss -- @ -- -- Examples: -- -- >>> toList $ unwrap (join ["John", "Ronald", "Reuel", "Tolkien"] :: OpDiscreteHybridNE Char) -- "JRRTolkien" -- -- Surprisingly, while the 'DiscreteHybridNE' monad has a counterpart -- for possibly-empty lists -- ('Control.Monad.List.Exotic.DiscreteHybrid'), the would-be -- counterpart of @OpDiscreteHybridNE@ obtained by taking first -- elements in the init is __not__ a monad. newtype OpDiscreteHybridNE a = OpDiscreteHybridNE { unOpDiscreteHybridNE :: NonEmpty a } deriving (Functor, Show, Eq) instance Applicative OpDiscreteHybridNE where pure = return (<*>) = ap instance Monad OpDiscreteHybridNE where return a = OpDiscreteHybridNE $ [a] -- OverloadedLists OpDiscreteHybridNE xs >>= f = OpDiscreteHybridNE $ join $ NonEmpty.map (unOpDiscreteHybridNE . f) xs where join (splitSnoc -> (xss, xs)) = fromList (map NonEmpty.head xss ++ toList xs) instance IsNonEmpty (OpDiscreteHybridNE a) where type ItemNE (OpDiscreteHybridNE a) = a fromNonEmpty = OpDiscreteHybridNE toNonEmpty = unOpDiscreteHybridNE instance NonEmptyMonad OpDiscreteHybridNE instance Magma (OpDiscreteHybridNE a) where m <> t = join $ OpDiscreteHybridNE $ [m, t] instance XZ (OpDiscreteHybridNE a) instance FreeRBM OpDiscreteHybridNE XZ -- The following two are needed for examples in the docs: instance IsList (OpDiscreteHybridNE a) where type Item (OpDiscreteHybridNE a) = a fromList = fromNonEmpty . fromList toList = toList . toNonEmpty instance IsString (OpDiscreteHybridNE Char) where fromString = fromList -------------------------------------------- -- The Non-empty Maze Walk monad -- ------------------------------------------- -- | Instances should satisfy the following equation: -- -- @ -- (x '<>' y) '<>' z == x '<>' (y '<>' (x '<>' z)) -- @ class (Magma a) => PalindromeMagma a -- | The non-empty maze walk monad arises from free -- 'PalindromeMagma'-s. Its join (in terms of @joinList@) can be given -- as follows: -- -- @ -- joinList xss = map 'Control.Monad.List.Exotic.palindromize' (init xss) ++ last xss -- @ -- -- See the possibly-empty version -- ('Control.Monad.List.Exotic.MazeWalk') for more details. newtype MazeWalkNE a = MazeWalkNE { unMazeWalkNE :: NonEmpty a } deriving (Functor, Show, Eq) instance Applicative MazeWalkNE where pure = return (<*>) = ap instance Monad MazeWalkNE where return a = MazeWalkNE $ [a] -- OverloadedLists MazeWalkNE xs >>= f = MazeWalkNE $ join $ NonEmpty.map (unMazeWalkNE . f) xs where join :: NonEmpty (NonEmpty a) -> NonEmpty a join (splitSnoc -> (xss, xs)) = fromList $ concatMap (palindromize . toList) xss ++ toList xs instance IsNonEmpty (MazeWalkNE a) where type ItemNE (MazeWalkNE a) = a fromNonEmpty = MazeWalkNE toNonEmpty = unMazeWalkNE instance NonEmptyMonad MazeWalkNE instance Magma (MazeWalkNE a) where m <> t = join $ MazeWalkNE $ [m, t] instance PalindromeMagma (MazeWalkNE a) instance FreeRBM MazeWalkNE PalindromeMagma --------------------------------- -- The Non-empty Stutter monad -- --------------------------------- -- | Instances should satisfy the following equation: -- -- @ -- (x '<>' y) '<>' z == 'foldr1' ('<>') ('replicate' (n + 2) x) -- @ class (KnownNat n, Magma a) => StutterMagma n a -- | The non-empty stutter monad arises from free 'StutterMagma'-s. -- Its join (in terms of @joinList@) can be given as follows: -- -- @ -- joinList xss | any (not . 'Control.Monad.List.Exotic.isSingle') (init xss) -- = map head (takeWhile 'Control.Monad.List.Exotic.isSingle' (init xss)) -- ++ replicate (n + 2) (head (head (dropWhile 'Control.Monad.List.Exotic.isSingle' (init xss)))) -- | otherwise -- = map head (init xss) ++ last xss -- @ -- -- Examples: -- -- >>> toList $ unwrap (join ["a", "b", "c", "hello", "there"] :: StutterNE 5 Char) -- "abchhhhhhh" -- >>> toList $ unwrap (join ["a", "b", "c", "hello"] :: StutterNE 5 Char) -- "abchello" newtype StutterNE (n :: Nat) a = StutterNE { unStutterNE :: NonEmpty a } deriving (Functor, Show, Eq) instance (KnownNat n) => Applicative (StutterNE n) where pure = return (<*>) = ap instance (KnownNat n) => Monad (StutterNE n) where return a = StutterNE $ [a] -- OverloadedLists StutterNE xs >>= f = StutterNE $ join $ NonEmpty.map (unStutterNE . f) xs where join :: NonEmpty (NonEmpty a) -> NonEmpty a join (splitSnoc -> (xss', xs)) | any (not . isSingle) xss' = let n = fromIntegral $ natVal (Proxy :: Proxy n) in fromList $ map NonEmpty.head (takeWhile isSingle xss') ++ replicate (n + 2) (NonEmpty.head $ head $ dropWhile isSingle xss') | otherwise = fromList $ map NonEmpty.head xss' ++ toList xs instance (KnownNat n) => IsNonEmpty (StutterNE n a) where type ItemNE (StutterNE n a) = a fromNonEmpty = StutterNE toNonEmpty = unStutterNE instance (KnownNat n) => NonEmptyMonad (StutterNE n) instance (KnownNat n) => Magma (StutterNE n a) where m <> t = join $ StutterNE $ [m, t] instance (KnownNat n) => StutterMagma n (StutterNE n a) instance (KnownNat n) => FreeRBM (StutterNE n) (StutterMagma n) -- The following two are needed for examples in the docs: instance (KnownNat n) => IsList (StutterNE n a) where type Item (StutterNE n a) = a fromList = fromNonEmpty . fromList toList = toList . toNonEmpty instance (KnownNat n) => IsString (StutterNE n Char) where fromString = fromList -------------------------- -- The Head-Tails monad -- -------------------------- -- $others -- -- In contrast to the possibly-empty-list case, there are known -- non-empty monads that arise from algebraic theories, but ones that -- cannot be presented with one binary operations (as in monads that -- come about from subclasses of 'Magma'). -- | The head-tail-tail algebra has two operations: unary 'hd' -- (intuitively, it produces a singleton list with the head of the -- argument as the element) and ternary 'htt' (intuitively, it -- produces the concat of the head of the first argument and tails of -- the other two arguments). -- -- Instances should satisfy the following equations: -- -- @ -- x == 'htt' x x ('hd' x) -- 'hd' ('hd' x) == 'hd' x -- 'hd' ('htt' x y z) == 'hd' x -- 'htt' x y ('hd' z) == 'htt' x y ('hd' y) -- 'htt' x y ('htt' z v w) == 'htt' x y ('htt' y v w) -- 'htt' x ('hd' y) ('hd' z) == 'hd' x -- 'htt' x ('hd' y) ('htt' z v w) == 'htt' x v w -- 'htt' x ('htt' y z v) w == 'htt' x z ('htt' z v w) -- 'htt' ('hd' x) y z == 'htt' x y z -- 'htt' ('htt' x y z) v w == 'htt' x v w -- @ -- -- Moreover, when read left-to-right they form a terminating and -- confluent rewriting system with normal forms of the following -- shape: -- -- @ -- 'htt' x y $ 'htt' y z $ 'htt' z v $ ... $ 'htt' w t ('hd' t) -- @ class HeadTailTail a where hd :: a -> a htt :: a -> a -> a -> a -- | The Head-Tails monad arises from free head-tail-tail algebras. Its unit is a dubleton, that is: -- -- @ -- return x = HeadTails (x :| [x]) -- @ -- -- Its join is defined as: -- -- @ -- join ((x :| _) :| xss) = x :| concatMap NonEmpty.tail xss -- @ -- -- For example: -- -- >>> toList $ unwrap (join ["John", "Paul", "George", "Ringo"] :: HeadTails Char) -- "Jauleorgeingo" newtype HeadTails a = HeadTails { unHeadTails :: NonEmpty a } deriving (Functor, Show, Eq) instance Applicative HeadTails where pure = return (<*>) = ap instance Monad HeadTails where return a = HeadTails $ [a,a] -- OverloadedLists HeadTails xs >>= f = HeadTails $ join $ NonEmpty.map (unHeadTails . f) xs where join ((x :| _) :| xss) = x :| concatMap NonEmpty.tail xss instance IsNonEmpty (HeadTails a) where type ItemNE (HeadTails a) = a fromNonEmpty = HeadTails toNonEmpty = unHeadTails instance NonEmptyMonad HeadTails instance HeadTailTail (HeadTails a) where hd a = join $ HeadTails [a] -- OverloadedLists htt a b c = join $ HeadTails [a, b, c] -- OverloadedLists -- | The 'HeadTails' monad arises from free head-tail-tail algebras, -- so an interpretation of generators @g@ to a head-tail-tail algebra -- @a@ can be (uniquely) lifted to a homomorphism between -- head-tail-tail algebras. foldHeadTails :: (HeadTailTail a) => (g -> a) -> HeadTails g -> a foldHeadTails f (HeadTails (x :| [])) = hd (f x) foldHeadTails f (HeadTails (x :| (y : ys))) = htt (f x) (f y) (foldHeadTails f $ HeadTails $ y :| ys) -- The following two are needed for examples in the docs: instance IsList (HeadTails a) where type Item (HeadTails a) = a fromList = fromNonEmpty . fromList toList = toList . toNonEmpty instance IsString (HeadTails Char) where fromString = fromList -------------------------- -- The Heads-Tail monad -- -------------------------- -- | Instances should satisfy the following equations: -- -- @ -- x == 'ht' x x -- 'hd'' ('hd'' x) == 'hd'' x -- 'hd'' ('ht' x y) == 'hd'' x -- 'hd'' ('hht' x y z) == 'hd'' x -- 'ht' x ('hd'' y) == 'hd'' x -- 'ht' x ('ht' y z) == 'ht' x z -- 'ht' x ('hht' y z v) == 'hht' x z v -- 'ht' ('hd'' x) y == 'ht' x y -- 'ht' ('ht' x y) z == 'ht' x z -- 'ht' ('hht' x y z) v == 'ht' x v -- 'hht' x y ('hd'' z) == 'hd'' x -- 'hht' x y ('ht' z v) == 'hht' x y v -- 'hht' x y ('hht' z v w) == 'hht' x y ('hht' y v w) -- 'hht' x ('hd'' y) z == 'hht' x y z -- 'hht' x ('ht' y z) v == 'hht' x y v -- 'hht' x ('hht' y z v) w == 'hht' x y w -- 'hht' ('hd'' x) y z == 'hht' x y z -- 'hht' ('ht' x y) z v == 'hht' x z v -- 'hht' ('hht' x y z) v w == 'hht' x v w -- @ -- -- Moreover, when read left-to-right they form a terminating and -- confluent rewriting system with normal forms of the following -- shape: -- -- @ -- 'hd'' x -- 'ht' x y -- 'hht' x y $ 'hht' y z $ 'hht' z v $ ... $ 'hht' w t u -- @ class HeadHeadTail a where hd' :: a -> a ht :: a -> a -> a hht :: a -> a -> a -> a -- | The Heads-Tail monad arises from free head-head-tail algebras. Its unit is a dubleton, that is: -- -- @ -- return x = HeadsTail (x :| [x]) -- @ -- -- Its join is defined as: -- -- @ -- join xss\@('splitSnoc' -> (xss', xs\@(_:|ys))) -- | 'isSingle' xss || 'isSingle' xs -- = (NonEmpty.head $ NonEmpty.head xss) :| [] -- | otherwise -- = fromList $ map NonEmpty.head xss' ++ ys -- @ -- -- For example: -- -- >>> toList $ unwrap (join ["John", "Paul", "George", "Ringo"] :: HeadsTail Char) -- "JPGingo" newtype HeadsTail a = HeadsTail { unHeadsTail :: NonEmpty a } deriving (Functor, Show, Eq) instance Applicative HeadsTail where pure = return (<*>) = ap instance Monad HeadsTail where return a = HeadsTail $ [a,a] -- OverloadedLists HeadsTail xs >>= f = HeadsTail $ join $ NonEmpty.map (unHeadsTail . f) xs where join xss@(splitSnoc -> (xss', xs@(_:|ys))) | isSingle xss || isSingle xs = [NonEmpty.head $ NonEmpty.head xss] -- OverloadedLists | otherwise = fromList $ map NonEmpty.head xss' ++ ys instance IsNonEmpty (HeadsTail a) where type ItemNE (HeadsTail a) = a fromNonEmpty = HeadsTail toNonEmpty = unHeadsTail instance NonEmptyMonad HeadsTail instance HeadHeadTail (HeadsTail a) where hd' a = join $ HeadsTail [a] -- OverloadedLists ht a b = join $ HeadsTail [a, b] -- OverloadedLists hht a b c = join $ HeadsTail [a, b, c] -- OverloadedLists -- | The 'HeadsTail' monad arises from free head-head-tail algebras, -- so an interpretation of generators @g@ to a head-head-tail algebra -- @a@ can be (uniquely) lifted to a homomorphism between -- head-head-tail algebras. foldHeadsTail :: (HeadHeadTail a) => (g -> a) -> HeadsTail g -> a foldHeadsTail f (HeadsTail (x :| [])) = hd' (f x) foldHeadsTail f (HeadsTail (x :| [y])) = ht (f x) (f y) foldHeadsTail f (HeadsTail (x :| [y, z])) = hht (f x) (f y) (f z) foldHeadsTail f (HeadsTail (x :| (y : ys))) = hht (f x) (f y) (foldHeadsTail f $ HeadsTail $ y :| ys) -- The following two are needed for examples in the docs: instance IsList (HeadsTail a) where type Item (HeadsTail a) = a fromList = fromNonEmpty . fromList toList = toList . toNonEmpty instance IsString (HeadsTail Char) where fromString = fromList ------------------ -- The ΑΩ monad -- ------------------ -- | The join of the ΑΩ (Alpha-Omega) monad takes the first element of -- the first list and the last element of the last list (unless the -- unit laws require otherwise): -- -- @ -- join xss | isSingle xss || nonEmptyAll isSingle xss -- = nonEmptyConcat xss -- | otherwise -- = NonEmpty.head (NonEmpty.head xss) -- :| NonEmpty.last (NonEmpty.last xss) : [] -- @ -- -- For example: -- -- >>> toList $ unwrap (join ["John", "Paul", "George", "Ringo"] :: AlphaOmega Char) -- "Jo" newtype AlphaOmega a = AlphaOmega { unAlphaOmega :: NonEmpty a } deriving (Functor, Show, Eq) instance Applicative AlphaOmega where pure = return (<*>) = ap instance Monad AlphaOmega where return a = AlphaOmega [a] -- OverloadedLists AlphaOmega xs >>= f = AlphaOmega $ join $ NonEmpty.map (unAlphaOmega . f) xs where join xss | isSingle xss || nonEmptyAll isSingle xss = nonEmptyConcat xss | otherwise = [ NonEmpty.head (NonEmpty.head xss) -- OverloadedLists , NonEmpty.last (NonEmpty.last xss) ] instance IsNonEmpty (AlphaOmega a) where type ItemNE (AlphaOmega a) = a fromNonEmpty = AlphaOmega toNonEmpty = unAlphaOmega instance NonEmptyMonad AlphaOmega -- The following two are needed for examples in the docs: instance IsList (AlphaOmega a) where type Item (AlphaOmega a) = a fromList = fromNonEmpty . fromList toList = toList . toNonEmpty instance IsString (AlphaOmega Char) where fromString = fromList ------------------------------- -- Dual non-empty list monad -- ------------------------------- liftNEFun :: (NonEmptyMonad m) => (NonEmpty a -> NonEmpty a) -> m a -> m a liftNEFun f = wrap . f . unwrap -- | Every non-empty list monad has a dual, in which join is defined -- as -- -- @ -- join . reverse . fmap reverse -- @ -- -- (where join is the join of the original list monad), while return is -- -- @ -- reverse . return -- @ -- -- (where return is the return of the original list monad). newtype DualNonEmptyMonad m a = DualNonEmptyMonad { unDualNonEmptyMonad :: m a } deriving (Functor, Show, Eq) instance (NonEmptyMonad m) => Applicative (DualNonEmptyMonad m) where pure = return (<*>) = ap instance (NonEmptyMonad m) => Monad (DualNonEmptyMonad m) where return = DualNonEmptyMonad . liftNEFun NonEmpty.reverse . return DualNonEmptyMonad m >>= f = DualNonEmptyMonad $ liftNEFun NonEmpty.reverse $ liftNEFun NonEmpty.reverse m >>= liftNEFun NonEmpty.reverse . unDualNonEmptyMonad . f instance (IsNonEmpty (m a)) => IsNonEmpty (DualNonEmptyMonad m a) where type ItemNE (DualNonEmptyMonad m a) = ItemNE (m a) toNonEmpty (DualNonEmptyMonad m) = toNonEmpty m fromNonEmpty xs = DualNonEmptyMonad (fromNonEmpty xs) instance (NonEmptyMonad m) => NonEmptyMonad (DualNonEmptyMonad m) where wrap = DualNonEmptyMonad . wrap unwrap = unwrap . unDualNonEmptyMonad --------------------------------------- -- Product of Identity and ListMonad -- --------------------------------------- -- | @'NonEmpty' a@ is isomorphic to the product @(a, [a])@. Thus, we -- can define a monadic structure on it by a product of the identity -- monad with any list monad. In particular: -- -- @ -- return x = IdXList x (return x) -- IdXList x m >>= f = IdXList (componentId $ f x) (m >>= componentM . f) -- @ -- -- where 'return' and '>>=' in definition bodies come from the -- transformed monad. data IdXList m a = IdXList { componentId :: a, componentM :: m a } deriving (Functor, Show, Eq) instance (ListMonad m) => Applicative (IdXList m) where pure = return (<*>) = ap instance (ListMonad m) => Monad (IdXList m) where return x = IdXList x (return x) IdXList x m >>= f = IdXList (componentId $ f x) (m >>= componentM . f) instance (ListMonad m) => IsNonEmpty (IdXList m a) where type ItemNE (IdXList m a) = a fromNonEmpty (x :| xs) = IdXList x $ List.Exotic.wrap xs toNonEmpty (IdXList x m) = x :| List.Exotic.unwrap m instance (ListMonad m) => NonEmptyMonad (IdXList m) --------------------------- -- The Short Front monad -- --------------------------- -- | Instances of this class are non-empty list monads for which the -- 'ShortFront' construction gives a monad. class (NonEmptyMonad m) => HasShortFront m instance HasShortFront NonEmpty -- | (?) instance HasShortFront Keeper -- | (?) instance HasShortFront OpDiscreteHybridNE -- | (?) instance HasShortFront MazeWalkNE -- | (?) instance (KnownNat n) => HasShortFront (StutterNE n) -- | (?) instance HasShortFront AlphaOmega instance (HasShortRear m) => HasShortFront (DualNonEmptyMonad m) -- | This is a transformer for a number of monads (instances of the -- 'HasShortFront' class), whose return is singleton and join takes -- the prefix of length @p + 2@ of the result of the join of the -- transformed monad (unless the unit laws require otherwise): -- -- @ -- joinList xss | 'Control.Monad.List.Exotic.isSingle' xss || all 'Control.Monad.List.Exotic.isSingle' xss = concat xss -- | otherwise = take (p + 2) (joinList xss) -- @ -- -- where @joinList@ in the @otherwise@ branch is the @joinList@ of the transformed monad. -- -- While there are quite a few \"short front\" monads on non-empty -- lists, only one such monad on possibly-empty lists is known, -- 'Control.Monad.List.Exotic.StutterKeeper' (the short version is -- 'Control.Monad.List.Exotic.ShortStutterKeeper'). -- -- For example: -- -- >>> toList $ unwrap (join ["John", "Paul", "George", "Ringo"] :: ShortFront NonEmpty 4 Char) -- "JohnPa" -- >>> toList $ unwrap (join ["John", "Paul", "George", "Ringo"] :: ShortFront MazeWalkNE 4 Char) -- "Johnho" -- >>> toList $ unwrap (join ["John", "Paul", "George", "Ringo"] :: ShortFront OpDiscreteHybridNE 4 Char) -- "JPGRin" -- >>> toList $ unwrap (join ["John", "Paul", "George", "Ringo"] :: ShortFront Keeper 4 Char) -- "John" -- >>> toList $ unwrap (join ["John", "Paul", "George", "Ringo"] :: ShortFront (StutterNE 2) 4 Char) -- "JJJJ" -- >>> toList $ unwrap (join ["John", "Paul", "George", "Ringo"] :: ShortFront (StutterNE 6) 4 Char) -- "JJJJJJ" newtype ShortFront m (p :: Nat) a = ShortFront { unShortFront :: m a } deriving (Functor, Show, Eq) instance (HasShortFront m, KnownNat p) => Applicative (ShortFront m p) where pure = return (<*>) = ap instance (HasShortFront m, KnownNat p) => Monad (ShortFront m p) where return = ShortFront . return ShortFront m >>= f | isSingle (unwrap m) || nonEmptyAll isSingle (unwrap $ unwrap . unShortFront . f <$> m) = ShortFront $ m >>= unShortFront . f | otherwise = let p = fromIntegral $ natVal (Proxy :: Proxy p) in ShortFront $ liftNEFun (fromList . NonEmpty.take (p + 2)) $ m >>= unShortFront . f instance (IsNonEmpty (m a), KnownNat p) => IsNonEmpty (ShortFront m p a) where type ItemNE (ShortFront m p a) = ItemNE (m a) toNonEmpty (ShortFront m) = toNonEmpty m fromNonEmpty xs = ShortFront (fromNonEmpty xs) instance (HasShortFront m, KnownNat p) => NonEmptyMonad (ShortFront m p) where wrap = ShortFront . wrap unwrap = unwrap . unShortFront -- The following two are needed for examples in the docs: instance (HasShortFront m, KnownNat p) => IsList (ShortFront m p a) where type Item (ShortFront m p a) = a fromList = wrap . fromList toList = toList . unwrap instance (HasShortFront m, KnownNat p) => IsString (ShortFront m p Char) where fromString = fromList --------------------------- -- The Short Rear monad -- --------------------------- -- | Instances of this class are non-empty list monads for which the -- 'ShortRear' construction gives a monad. class (NonEmptyMonad m) => HasShortRear m instance HasShortRear NonEmpty -- | (?) instance HasShortRear DiscreteHybridNE -- | (?) instance HasShortRear AlphaOmega instance (HasShortFront m) => HasShortRear (DualNonEmptyMonad m) -- | Similar to 'ShortFront', but gives a monad if restricted to a -- suffix of the length @p + 2@. -- -- For example: -- -- >>> toList $ unwrap (join ["John", "Paul", "George", "Ringo"] :: ShortRear NonEmpty 5 Char) -- "geRingo" -- >>> toList $ unwrap (join ["John", "Paul", "George", "Ringo"] :: ShortRear DiscreteHybridNE 5 Char) -- "leRingo" -- newtype ShortRear m (p :: Nat) a = ShortRear { unShortRear :: m a } deriving (Functor, Show, Eq) instance (HasShortRear m, KnownNat p) => Applicative (ShortRear m p) where pure = return (<*>) = ap nonEmptyTakeRear :: Int -> NonEmpty a -> [a] nonEmptyTakeRear p = reverse . NonEmpty.take p . NonEmpty.reverse instance (HasShortRear m, KnownNat p) => Monad (ShortRear m p) where return = ShortRear . return ShortRear m >>= f | isSingle (unwrap m) || nonEmptyAll isSingle (unwrap $ unwrap . unShortRear . f <$> m) = ShortRear $ m >>= unShortRear . f | otherwise = let p = fromIntegral $ natVal (Proxy :: Proxy p) in ShortRear $ liftNEFun (fromList . nonEmptyTakeRear (p + 2)) $ m >>= unShortRear . f instance (IsNonEmpty (m a), KnownNat p) => IsNonEmpty (ShortRear m p a) where type ItemNE (ShortRear m p a) = ItemNE (m a) toNonEmpty (ShortRear m) = toNonEmpty m fromNonEmpty xs = ShortRear (fromNonEmpty xs) instance (HasShortRear m, KnownNat p) => NonEmptyMonad (ShortRear m p) where wrap = ShortRear . wrap unwrap = unwrap . unShortRear -- The following two are needed for examples in the docs: instance (HasShortRear m, KnownNat p) => IsList (ShortRear m p a) where type Item (ShortRear m p a) = a fromList = wrap . fromList toList = toList . unwrap instance (HasShortRear m, KnownNat p) => IsString (ShortRear m p Char) where fromString = fromList