Listable
Listable is a module which contains the Listable class. The Listable class allows a user to create instances of types which can be treated as lists. Given a minimal description:
- takeL :: Integer -> m -> m
- dropL :: Integer -> m -> m
- cons :: m -> m -> m
- (+++) :: m -> m -> m
- unit :: m
one gets the following functions for free:
- filterL :: (m -> Bool) -> m -> m
- (!!!) :: m -> Integer -> m
- lengthL :: m -> Integer
- reverseL :: m -> m
- headL :: m -> m
- tailL :: m -> m
Notice below that each of the Free functions are defined in terms of the required ones.
By indenting these definitions, placing them under the class banner, the haskell compiler expects the function definitions to be self-contained and defined purely in terms of the Listable class and standard functions given by Prelude. Additionally, Listable demands that its instances be comparable via the Equality class, Eq. Without being able to compare two values, there is no way to identify when a base case, unit, is reached. lengthL and reverseL rely upon this in their recursion.
class Eq m => Listable m where
takeL :: Integer -> m -> m
dropL :: Integer -> m -> m
cons :: m -> m -> m
(+++) :: m -> m -> m
unit :: m
(!!!) :: m -> Integer -> m
lengthL :: m -> Integer
reverseL :: m -> m
headL :: m -> m
tailL :: m -> m
headL = takeL 1
tailL = dropL 1
(!!!) ls n = headL.dropL n $ ls
lengthL ls | unit == ls = 0
| otherwise = 1 + (lengthL.dropL 1) ls
reverseL ns = ff ns unit
where
ff ns accum | ns == unit = accum
| otherwise = ff (tailL ns) $ headL ns `cons` accum
filterL b ns = f b ns unit
where
f b js accum | js == unit = accum
| (b.headL) js = f b (tailL js) $ headL js +++ accum
| otherwise = f b (tailL js) accum
Now for some instances. Since [a]
is essentially the defacto model of our class, it seems most obvious to begin with this instance. Above, the Listable type of cons is slightly different than one usually expects for (:)
.
Here, cons is constrained as: cons :: m -> m -> m
where (:)
is usually constrained as: (:) :: a -> [a] -> [a]
.
This difference will ultimately have an impact on how a Listable cons behaves.
instance Eq a => Listable [a] where
dropL = drop.fromIntegral
takeL = take.fromIntegral
cons [n] ns = n : ns -- wrap the left argument with [].
(+++) = (++)
unit = []
[2] `cons` dropL 3 [1..9]
Next, something less trivial. We can in fact treat numbers of type Integer as Listable as well. Best of all, all one needs to do is write instances for the necessary functions: takeL, dropL, cons, (+++) and unit.
instance Listable Integer where
(+++) ns ms = ns * 10^lengthL ms + ms
dropL n zs = div zs (10^n)
takeL n zs = mod zs (10^n)
cons n ns = ns * 10 + n
unit = 0
2 `cons` dropL 3 987654321
It is probably worth mentioning that the Listable behavior of [a]
and of Integer are noticeably different. Listable treats an Integer type as a list whose order is opposite to the order of a Listable [a]. This choice is arbitrary in that the Integer instance could likely have been written so that the two types have the same direction, though I am not entirely sure how to do it purely arithmetically. Additionally note, that since zero is defined as the monoidal unit, it behaves more similarly to []
than it does to the actual number 0.
[takeL 1 1000002 == reverseL 20, lengthL 0 == lengthL 1]