Copyright | (c) Dylan McDermott Maciej Piróg Tarmo Uustalu 2020 |
---|---|
License | MIT |
Maintainer | maciej.adam.pirog@gmail.com |
Stability | experimental |
Portability | portable |
Safe Haskell | Trustworthy |
Language | Haskell2010 |
The usual list monad is only one of infinitely many ways to turn
the 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
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>>=
, whilereturn
is singleton, unless stated otherwise for a particular monad (e.g.,HeadTails
,HeadsTail
, orIdXList
). - For readability, code snippets in this documentation assume the
OverloadedLists
andOverloadedStrings
extensions, which allow us to omit somenewtype
constructors. Example definitions of joins of monads always skip thenewtype
constructors, that is, assume>>=
is always defined as follows for a particular localjoin
.
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
$fromList
$ joinList $map
(toList
.unwrap
. f) $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.
Synopsis
- class IsNonEmpty l where
- type ItemNE l
- fromNonEmpty :: NonEmpty (ItemNE l) -> l
- toNonEmpty :: l -> NonEmpty (ItemNE l)
- class Monad m => NonEmptyMonad m where
- isSingle :: NonEmpty a -> Bool
- splitSnoc :: NonEmpty a -> ([a], a)
- nonEmptyConcat :: NonEmpty (NonEmpty a) -> NonEmpty a
- (+++) :: NonEmpty a -> NonEmpty a -> NonEmpty a
- nonEmptyAll :: (a -> Bool) -> NonEmpty a -> Bool
- nonEmptyAny :: (a -> Bool) -> NonEmpty a -> Bool
- class Magma a where
- (<>) :: a -> a -> a
- class NonEmptyMonad m => FreeRBM m (c :: * -> Constraint) | m -> c where
- class Magma a => XY a
- newtype Keeper a = Keeper {}
- class Magma a => YZ a
- newtype DiscreteHybridNE a = DiscreteHybridNE {}
- class Magma a => XZ a
- newtype OpDiscreteHybridNE a = OpDiscreteHybridNE {}
- class Magma a => PalindromeMagma a
- newtype MazeWalkNE a = MazeWalkNE {
- unMazeWalkNE :: NonEmpty a
- class (KnownNat n, Magma a) => StutterMagma n a
- newtype StutterNE (n :: Nat) a = StutterNE {
- unStutterNE :: NonEmpty a
- class HeadTailTail a where
- newtype HeadTails a = HeadTails {
- unHeadTails :: NonEmpty a
- foldHeadTails :: HeadTailTail a => (g -> a) -> HeadTails g -> a
- class HeadHeadTail a where
- newtype HeadsTail a = HeadsTail {
- unHeadsTail :: NonEmpty a
- foldHeadsTail :: HeadHeadTail a => (g -> a) -> HeadsTail g -> a
- newtype AlphaOmega a = AlphaOmega {
- unAlphaOmega :: NonEmpty a
- newtype DualNonEmptyMonad m a = DualNonEmptyMonad {
- unDualNonEmptyMonad :: m a
- data IdXList m a = IdXList {
- componentId :: a
- componentM :: m a
- class NonEmptyMonad m => HasShortFront m
- newtype ShortFront m (p :: Nat) a = ShortFront {
- unShortFront :: m a
- class NonEmptyMonad m => HasShortRear m
- newtype ShortRear m (p :: Nat) a = ShortRear {
- unShortRear :: m a
Non-empty monads in general
class IsNonEmpty l where Source #
This class collects types that are isomorphic to non-empty
lists. It mimics the IsList
class.
fromNonEmpty :: NonEmpty (ItemNE l) -> l Source #
toNonEmpty :: l -> NonEmpty (ItemNE l) Source #
Instances
class Monad m => NonEmptyMonad m where Source #
In this module, a "non-empty monad" is a monad in which the
underlying functor is isomorphic to NonEmpty
.
Nothing
wrap :: NonEmpty a -> m a Source #
wrap :: (IsNonEmpty (m a), ItemNE (m a) ~ a) => NonEmpty a -> m a Source #
unwrap :: m a -> NonEmpty a Source #
unwrap :: (IsNonEmpty (m a), ItemNE (m a) ~ a) => m a -> NonEmpty a Source #
Instances
More on non-empty lists
Monads from 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.
Instances
Magma (MazeWalkNE a) Source # | |
Defined in Control.Monad.List.NonEmpty.Exotic (<>) :: MazeWalkNE a -> MazeWalkNE a -> MazeWalkNE a Source # | |
Magma (OpDiscreteHybridNE a) Source # | |
Defined in Control.Monad.List.NonEmpty.Exotic (<>) :: OpDiscreteHybridNE a -> OpDiscreteHybridNE a -> OpDiscreteHybridNE a Source # | |
Magma (DiscreteHybridNE a) Source # | |
Defined in Control.Monad.List.NonEmpty.Exotic (<>) :: DiscreteHybridNE a -> DiscreteHybridNE a -> DiscreteHybridNE a Source # | |
Magma (Keeper a) Source # | |
KnownNat n => Magma (StutterNE n a) Source # | |
class NonEmptyMonad m => FreeRBM m (c :: * -> Constraint) | m -> c where Source #
The name of the class stands for free right-braketed
(subclass of) magma. (compare
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 FreeRBMNonEmpty
Semigroup
Nothing
Instances
FreeRBM NonEmpty Semigroup Source # | |
FreeRBM MazeWalkNE PalindromeMagma Source # | |
Defined in Control.Monad.List.NonEmpty.Exotic foldRBM :: (Magma a, PalindromeMagma a) => (x -> a) -> MazeWalkNE x -> a Source # | |
FreeRBM OpDiscreteHybridNE XZ Source # | |
Defined in Control.Monad.List.NonEmpty.Exotic | |
FreeRBM DiscreteHybridNE YZ Source # | |
Defined in Control.Monad.List.NonEmpty.Exotic | |
FreeRBM Keeper XY Source # | |
KnownNat n => FreeRBM (StutterNE n) (StutterMagma n) Source # | |
Defined in Control.Monad.List.NonEmpty.Exotic |
The Keeper monad
The keeper monad arises from free XY
magmas. Its join (in terms
of joinList
) is given as follows:
joinList xss = map head (takeWhileisSingle
(init xss)) ++ head (dropWhileisSingle
(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"
Instances
Monad Keeper Source # | |
Functor Keeper Source # | |
Applicative Keeper Source # | |
HasShortFront Keeper Source # | (?) |
Defined in Control.Monad.List.NonEmpty.Exotic | |
NonEmptyMonad Keeper Source # | |
FreeRBM Keeper XY Source # | |
IsList (Keeper a) Source # | |
Eq a => Eq (Keeper a) Source # | |
Show a => Show (Keeper a) Source # | |
IsString (Keeper Char) Source # | |
Defined in Control.Monad.List.NonEmpty.Exotic fromString :: String -> Keeper Char # | |
XY (Keeper a) Source # | |
Defined in Control.Monad.List.NonEmpty.Exotic | |
Magma (Keeper a) Source # | |
IsNonEmpty (Keeper a) Source # | |
type Item (Keeper a) Source # | |
Defined in Control.Monad.List.NonEmpty.Exotic | |
type ItemNE (Keeper a) Source # | |
Defined in Control.Monad.List.NonEmpty.Exotic |
The Non-Empty Discrete Hybrid monad
class Magma a => YZ a Source #
Instances
FreeRBM DiscreteHybridNE YZ Source # | |
Defined in Control.Monad.List.NonEmpty.Exotic | |
YZ (DiscreteHybridNE a) Source # | |
Defined in Control.Monad.List.NonEmpty.Exotic |
newtype DiscreteHybridNE a Source #
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
(DiscreteHybrid
) for more details.
Instances
The Non-Empty Discrete Op-Hybrid monad
class Magma a => XZ a Source #
Instances
FreeRBM OpDiscreteHybridNE XZ Source # | |
Defined in Control.Monad.List.NonEmpty.Exotic | |
XZ (OpDiscreteHybridNE a) Source # | |
Defined in Control.Monad.List.NonEmpty.Exotic |
newtype OpDiscreteHybridNE a Source #
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
(DiscreteHybrid
), the would-be
counterpart of OpDiscreteHybridNE
obtained by taking first
elements in the init is not a monad.
Instances
The Non-Empty Maze Walk monad
class Magma a => PalindromeMagma a Source #
Instances
FreeRBM MazeWalkNE PalindromeMagma Source # | |
Defined in Control.Monad.List.NonEmpty.Exotic foldRBM :: (Magma a, PalindromeMagma a) => (x -> a) -> MazeWalkNE x -> a Source # | |
PalindromeMagma (MazeWalkNE a) Source # | |
Defined in Control.Monad.List.NonEmpty.Exotic |
newtype MazeWalkNE a Source #
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 palindromize
(init xss) ++ last xss
See the possibly-empty version
(MazeWalk
) for more details.
Instances
The Non-Empty Stutter monad
class (KnownNat n, Magma a) => StutterMagma n a Source #
Instances
KnownNat n => StutterMagma n (StutterNE n a) Source # | |
Defined in Control.Monad.List.NonEmpty.Exotic | |
KnownNat n => FreeRBM (StutterNE n) (StutterMagma n) Source # | |
Defined in Control.Monad.List.NonEmpty.Exotic |
newtype StutterNE (n :: Nat) a Source #
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 .isSingle
) (init xss) = map head (takeWhileisSingle
(init xss)) ++ replicate (n + 2) (head (head (dropWhileisSingle
(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"
StutterNE | |
|
Instances
Other monads with finite presentation
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-Tails monad
class HeadTailTail a where Source #
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
xhd
(htt
x y z) ==hd
xhtt
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
xhtt
x (hd
y) (htt
z v w) ==htt
x v whtt
x (htt
y z v) w ==htt
x z (htt
z v w)htt
(hd
x) y z ==htt
x y zhtt
(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)
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"
HeadTails | |
|
Instances
Monad HeadTails Source # | |
Functor HeadTails Source # | |
Applicative HeadTails Source # | |
Defined in Control.Monad.List.NonEmpty.Exotic | |
NonEmptyMonad HeadTails Source # | |
IsList (HeadTails a) Source # | |
Eq a => Eq (HeadTails a) Source # | |
Show a => Show (HeadTails a) Source # | |
IsString (HeadTails Char) Source # | |
Defined in Control.Monad.List.NonEmpty.Exotic fromString :: String -> HeadTails Char # | |
HeadTailTail (HeadTails a) Source # | |
IsNonEmpty (HeadTails a) Source # | |
type Item (HeadTails a) Source # | |
Defined in Control.Monad.List.NonEmpty.Exotic | |
type ItemNE (HeadTails a) Source # | |
Defined in Control.Monad.List.NonEmpty.Exotic |
foldHeadTails :: HeadTailTail a => (g -> a) -> HeadTails g -> a Source #
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.
The Heads-Tail monad
class HeadHeadTail a where Source #
Instances should satisfy the following equations:
x ==ht
x xhd'
(hd'
x) ==hd'
xhd'
(ht
x y) ==hd'
xhd'
(hht
x y z) ==hd'
xht
x (hd'
y) ==hd'
xht
x (ht
y z) ==ht
x zht
x (hht
y z v) ==hht
x z vht
(hd'
x) y ==ht
x yht
(ht
x y) z ==ht
x zht
(hht
x y z) v ==ht
x vhht
x y (hd'
z) ==hd'
xhht
x y (ht
z v) ==hht
x y vhht
x y (hht
z v w) ==hht
x y (hht
y v w)hht
x (hd'
y) z ==hht
x y zhht
x (ht
y z) v ==hht
x y vhht
x (hht
y z v) w ==hht
x y whht
(hd'
x) y z ==hht
x y zhht
(ht
x y) z v ==hht
x z vhht
(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'
xht
x yhht
x y $hht
y z $hht
z v $ ... $hht
w t u
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"
HeadsTail | |
|
Instances
Monad HeadsTail Source # | |
Functor HeadsTail Source # | |
Applicative HeadsTail Source # | |
Defined in Control.Monad.List.NonEmpty.Exotic | |
NonEmptyMonad HeadsTail Source # | |
IsList (HeadsTail a) Source # | |
Eq a => Eq (HeadsTail a) Source # | |
Show a => Show (HeadsTail a) Source # | |
IsString (HeadsTail Char) Source # | |
Defined in Control.Monad.List.NonEmpty.Exotic fromString :: String -> HeadsTail Char # | |
HeadHeadTail (HeadsTail a) Source # | |
IsNonEmpty (HeadsTail a) Source # | |
type Item (HeadsTail a) Source # | |
Defined in Control.Monad.List.NonEmpty.Exotic | |
type ItemNE (HeadsTail a) Source # | |
Defined in Control.Monad.List.NonEmpty.Exotic |
foldHeadsTail :: HeadHeadTail a => (g -> a) -> HeadsTail g -> a Source #
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.
Other monads
The ΑΩ monad (?)
newtype AlphaOmega a Source #
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"
Instances
Constructions on non-empty monads
The dual non-empty list monad
newtype DualNonEmptyMonad m a Source #
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).
Instances
The Identity
⨉ List
monad
is isomorphic to the product NonEmpty
a(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.
IdXList | |
|
Instances
ListMonad m => Monad (IdXList m) Source # | |
Functor m => Functor (IdXList m) Source # | |
ListMonad m => Applicative (IdXList m) Source # | |
Defined in Control.Monad.List.NonEmpty.Exotic | |
ListMonad m => NonEmptyMonad (IdXList m) Source # | |
(Eq a, Eq (m a)) => Eq (IdXList m a) Source # | |
(Show a, Show (m a)) => Show (IdXList m a) Source # | |
ListMonad m => IsNonEmpty (IdXList m a) Source # | |
type ItemNE (IdXList m a) Source # | |
Defined in Control.Monad.List.NonEmpty.Exotic |
Short-front monads
class NonEmptyMonad m => HasShortFront m Source #
Instances of this class are non-empty list monads for which the
ShortFront
construction gives a monad.
Instances
HasShortFront NonEmpty Source # | |
Defined in Control.Monad.List.NonEmpty.Exotic | |
HasShortFront AlphaOmega Source # | (?) |
Defined in Control.Monad.List.NonEmpty.Exotic | |
HasShortFront MazeWalkNE Source # | (?) |
Defined in Control.Monad.List.NonEmpty.Exotic | |
HasShortFront OpDiscreteHybridNE Source # | (?) |
Defined in Control.Monad.List.NonEmpty.Exotic | |
HasShortFront Keeper Source # | (?) |
Defined in Control.Monad.List.NonEmpty.Exotic | |
KnownNat n => HasShortFront (StutterNE n) Source # | (?) |
Defined in Control.Monad.List.NonEmpty.Exotic | |
HasShortRear m => HasShortFront (DualNonEmptyMonad m) Source # | |
Defined in Control.Monad.List.NonEmpty.Exotic |
newtype ShortFront m (p :: Nat) a Source #
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 |isSingle
xss || allisSingle
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,
StutterKeeper
(the short version is
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"
ShortFront | |
|
Instances
Short-rear monads
class NonEmptyMonad m => HasShortRear m Source #
Instances of this class are non-empty list monads for which the
ShortRear
construction gives a monad.
Instances
HasShortRear NonEmpty Source # | |
Defined in Control.Monad.List.NonEmpty.Exotic | |
HasShortRear AlphaOmega Source # | (?) |
Defined in Control.Monad.List.NonEmpty.Exotic | |
HasShortRear DiscreteHybridNE Source # | (?) |
Defined in Control.Monad.List.NonEmpty.Exotic | |
HasShortFront m => HasShortRear (DualNonEmptyMonad m) Source # | |
Defined in Control.Monad.List.NonEmpty.Exotic |
newtype ShortRear m (p :: Nat) a Source #
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"
ShortRear | |
|