A few times now, I've found myself defining:
(<?>) :: [a] -> [a] -> [a]
[] <?> ys = ys
xs <?> _ = xs
This is an associative operation, of course, and the empty list [] is both left- and right-identity. It functions like Python's or.
It seems to me that this would make a nice (<|>), better than (++) would. Choosing the first nonempty list feels more like what I would expect from a typeclass named Alternative than concatenating lists. Admittedly, it doesn't fit MonadPlus as well, but I think that a small price to pay for salvation. We already have (++) and (<>) in the standard library; do we need another synonym, or would a new function (as far as I can tell) be more helpful?
I was at first thinking this might be a good Alternative instance for ZipList, but the discussion following this answer on the relevant question has convinced me otherwise. Other than backwards-compatibility and keeping MonadPlus sensible, what arguments are there for the current instance rather than this new one?
It is tricky to give your question a straight answer. Considered in isolation, there is nothing fundamentally wrong with your proposed instance. Still, there are quite a few things that can be said in support of the existing Alternative instance for lists.
Admittedly, it doesn't fit MonadPlus as well, but I think that a small price to pay for salvation.
One problem with going down that route is that Alternative is meant to capture the same general concept that MonadPlus does, but in terms of Applicative rather than Monad. To quote a relevant answer by Edward Kmett:
Effectively, Alternative is to Applicative what MonadPlus is to Monad.
From that point of view, having mismatching Alternative and MonadPlus instances is confusing and misleading, much like the analogous situation with Applicative and Monad instances would be.
(A possible counter to this argument would be wondering why do we need to care about MonadPlus anyway, given that it expresses the same concepts and offers essentially the same methods as Alternative. It should be noted, though, that the MonadPlus laws are stronger than the Alternative ones, as the relevant interactions of its methods with the Monad ones aren't expressible in terms of Alternative. That being so, MonadPlus still has meaning of its own, and a conceivable outcome of a hypothetical reform of the classes would be retaining it as a laws-only class, as discussed for instance in the final section of this answer by Antal Spector-Zabusky.)
Given such considerations, in what follows I will assume the continued relevance of MonadPlus. That makes writing the rest of this answer much easier, as MonadPlus is the original expression of the general concept in Haskell, and so it is pretty much necessary to refer to it while tracing the origin of the list instance of Alternative.
It seems to me that this would make a nice (<|>), better than (++) would. Choosing the first nonempty list feels more like what I would expect from a typeclass named Alternative than concatenating lists.
Tracing the roots of MonadPlus and Alternative, though, shows that the concatenating list instance is not just well-established, but even paradigmatic. For instance, quoting the classic paper by Hutton and Meijer, Monadic parsing in Haskell (1998), p. 4:
That is, a type constructor m is a member of the class MonadZero if it is a member of the class Monad, and if it is also equipped with a value zero of the specified type. In a similar way, the class MonadPlus builds upon the class MonadZero by adding a (++) operation of the specified type.
(Note that the authors do use (++) as their name for mplus.)
The notion mplus captures here is that of non-deterministic choice: if computations u and v each have some possible results, the possible results of u `mplus` v will be all of the possible results of u and v. The most elementary realisation of that is through MonadPlus for lists, though the idea extends to cover other non-determinism monads, such as Hutton and Meijer's Parser:
newtype Parser a = Parser (String -> [(a,String)])
To spin it another way, we might describe non-deterministic choice as inclusive disjunction, while the operation you propose is a form of (left-biased) exclusive choice. (It is worth noting that Hutton and Meijer also define (+++), a deterministic choice operator for their Parser which is rather like your operator except that it only picks the first result of the first successsful computation.)
A further relevant observation: one of the monad transformers from transformers that doesn't have a mtl class counterpart is ListT. That is so because the class which generalises the ListT functionality is precisely MonadPlus. Quoting a Gabriella Gonzalez comment:
MonadPlus is basically the "list monad" type class. For example: cons a as = return a `mplus` as and nil = mzero.
Note that the brokenness of transformers' ListT is not an issue. In general, the various formulations of ListT-done-right are equipped with a concatenating MonadPlus instance (examples: one, two, three).
So much for reasons why we might want to leave the Alternative [] and MonadPlus [] instances as they are. Still, this answer would be lacking if it didn't recognise that, as Will Ness reminds us, there are multiple reasonable notions of choice, and your operator embodies one of them.
The "official" laws (that is, the ones actually mentioned by the documentation) of Alternative and MonadPlus don't specify a single notion of choice. That being so, we end up with both non-deterministic (e.g. mplus #[]) and deterministic (e.g. mplus #Maybe) choice instances under the same Alternative/MonadPlus umbrella. Furthermore, if one chose to disregard my argument above and replace mplus #[] with your operator, nothing in the "official" laws would stop them. Over the years, there has been some talk of reforming MonadPlus by splitting it into classes with extra laws, in order to separate the different notions of choice. The odds of such a reform actually happening, though, don't seem high (lots of churn for relatively little practical benefit).
For the sake of contrast, it is interesting to consider the near-semiring interpretation, which is one of the reimaginings of MonadPlus and Alternative that might be invoked in a hypothetical class hierarchy reform. For a fully fleshed account of it, see Rivas, Jaskelioff and Schrijvers, A Unified View of Monadic and Applicative Non-determinism (2018). For our current purposes, it suffices to note the interpretation tailors the classes to non-deterministic choice by adding, to the monoid laws, "left zero" and "left distribution" laws for Alternative...
empty <*> x = empty
(f <|> g) <*> x = (f <*> x) <|> (g <*> x)
... as well as for MonadPlus:
mzero >>= k = mzero
(m1 `mplus` m2) >>= k = (m1 >>= k) `mplus` (m2 >>= k)
(Those MonadPlus laws are strictly stronger than their Alternative counterparts.)
In particular, your choice operator follows the purported Alternative left distribution law, but not the MonadPlus one. In that respect, it is similar to mplus #Maybe. MonadPlus left distribution makes it difficult (probably impossible, though I don't have a proof at hand right now) to drop any results in mplus, as we can't tell, on the right hand side of the law, whether m1 >>= k or m2 >>= k will fail without inspecting the results of m1 and m2. To conclude this answer with something tangible, here is a demonstration of this point:
-- Your operator.
(<?>) :: [a] -> [a] -> [a]
[] <?> ys = ys
xs <?> _ = xs
filter' :: (a -> Bool) -> [a] -> [a]
filter' p xs = xs >>= \x -> if p x then [x] else []
-- If MonadPlus left distribution holds, then:
-- filter' p (xs `mplus` ys) = filter' p xs `mplus` filter' p ys
GHCi> filter' even ([1,3,5] <|> [0,2,4])
[0,2,4]
GHCi> filter' even [1,3,5] <|> filter' even [0,2,4]
[0,2,4]
GHCi> filter' even ([1,3,5] <?> [0,2,4])
[]
GHCi> filter' even [1,3,5] <?> filter' even [0,2,4]
[0,2,4]
Related
What I mean is a device like a list:
mempty = [ ]
lift x = [x]
mappend = (++)
Is it merely IsList?
Given the framing of your question, I would be inclined to characterise your lift...
(:[]) :: a -> [a]
... as reflecting how lists are an encoding of the free monoid for Haskell types. In particular, the universal property (illustrated by the diagram at the end of the Category Theory for Programmers chapter linked to above) implies that:
-- q is an arbitrary a -> m function, with m being an arbitrary monoid.
foldMap q . (:[]) = q
As far as the types go, Alternative might seem to also express what you are looking for: empty and (<|>) are generally expected to be monoidal operations, and pure from Applicative could be taken as your lift. However, I'm not sure if there is any connection that might be drawn between pure and the Alternative methods that would clarify the role of pure in such a construction. (On this latter point, you might find this tangentially related question which discusses the relationship between Alternative and Applicative interesting.)
You are talking about Alternative as #Robin Zigmond said:
instance Alternative [] where
empty = []
(<|>) = (++)
And if you want to know, it is also a MonadPlus:
instance MonadPlus []
I am trying to convince myself that the List monad (the one with flat lists, concatenation of lists and map element-wise) is not a free monad (to be precise, the free monad associated to some functor T). As far as I understand, I should be able to achieve that by
first finding a relation in the monad List between the usual operators fmap, join etc,
then showing that this relation does not hold in any free monad over a functor T, for all T.
What is a peculiar relation that holds in the List monad, that sets it apart from the free monads? How can I handle step2 if I don't know what T is? Is there some other strategy to show that flat lists are not free?
As a side note, to dispell any terminology clash, let me remark that the free monad associated to the pair functor is a tree monad (or a nested list monad), it is not the flat List monad.
Edit: for people acquainted with the haskell programming language, the question can be formulated as follows: how to show that there is no functor T such that List a = Free T a (for all T and up to monad isomorphism)?
(Adapted from my post in a different thread.)
Here is a full proof why the list monad is not free, including a bit of context.
Recall that we can construct, for any functor f, the free monad over f:
data Free f a = Pure a | Roll (f (Free f a))
Intuitively, Free f a is the type of f-shaped trees with leaves of type a.
The join operation merely grafts trees together and doesn't perform any
further computations. Values of the form (Roll _) shall be called
"nontrivial" in this posting. The task is to show that for no functor f, the monad Free f is isomorphic to the list monad.
The intuitive reason why this is true is because the
join operation of the list monad (concatenation) doesn't merely graft
expressions together, but flattens them.
More specifically, in the free monad over any functor, the result of binding a nontrivial
action with any function is always nontrivial, i.e.
(Roll _ >>= _) = Roll _
This can be checked directly from the definition of (>>=) for the free
monad.
If the list monad would be isomorphic-as-a-monad to the free monad over some
functor, the isomorphism would map only singleton lists [x] to values of
the form (Pure _) and all other lists to nontrivial values. This is
because monad isomorphisms have to commute with "return" and return x
is [x] in the list monad and Pure x in the free monad.
These two facts contradict each other, as can be seen with the following
example:
do
b <- [False,True] -- not of the form (return _)
if b
then return 47
else []
-- The result is the singleton list [47], so of the form (return _).
After applying a hypothetical isomorphism to the free monad over some
functor, we'd have that the binding of a nontrivial value (the image
of [False,True] under the isomorphism) with some function results in a
trivial value (the image of [47], i.e. return 47).
If you're okay with the free monad being applied to a type in particular which seems to be the case given the way you consider the Nat example given in the comments, then List can indeed be described using Free:
type List a = Free ((,) a) ()
The underlying idea here is that a List a is a Nat where each Suc node has been labelled with an a (hence the use of the (a,) functor rather than the Identity one).
Here is a small module witnessing the isomorphism together with an example:
module FreeList where
import Data.Function
import Control.Monad.Free
type List a = Free ((,) a) ()
toList :: [a] -> List a
toList = foldr (curry Free) (Pure ())
fromList :: List a -> [a]
fromList = iter (uncurry (:)) . fmap (const [])
append :: List a -> List a -> List a
append xs ys = xs >>= const ys
example :: [Integer]
example = fromList $ (append `on` toList) [1..5] [6..10]
I remember that when I showed some code that I wrote to my professor he remarked, offhand, that
It rarely matters, but it's worth noting that fold* is a little bit more efficient than fold*' in SML/NJ, so you should prefer it over fold* when possible.
I forget whether fold* was foldr or foldl. I know that this is one of those micro-optimization things that probably doesn't make a big difference in practice, but I'd like to be in the habit of using the more efficient one when I have the choice.
Which is which? My guess is that this is SML/NJ specific and that MLton will be smart enough to optimize both down to the same machine code, but answers for other compilers are good to know.
foldl is tail-recursive, while foldr is not. Although you can do foldr in a tail-recursive way by reversing the list (which is tail recursive), and then doing foldl.
This is only going to matter if you are folding over huge lists.
Prefer the one that converts the given input into the intended output.
If both produce the same output such as with a sum, and if dealing with a list, folding from the left will be more efficient because the fold can begin with head element, while folding from the right will first require walking the list to find the last element before calculating the first intermediate result.
With arrays and similar random access data structures, there's probably not going to be much difference.
A compiler optimization that always chose the better of left and right would require the compiler to determine that left and right were equivalent over all possible inputs. Since foldl and foldr take a functions as arguments, this is a bit of a tall order.
I'm going to keep the accepted answer here, but I had the chance to speak to my professor, and his reply was actually the opposite, because I forgot a part of my question. The code in question was building up a list, and he said:
Prefer foldr over foldl when possible, because it saves you a reverse at the end in cases where you're building up a list by appending elements during the fold.
As in, for a trivial example:
- val ls = [1, 2, 3];
val ls = [1,2,3] : int list
- val acc = (fn (x, xs) => x::xs);
val acc = fn : 'a * 'a list -> 'a list
- foldl acc [] ls;
val it = [3,2,1] : int list
- foldr acc [] ls;
val it = [1,2,3] : int list
The O(n) save of a reverse is probably more important than the other differences between foldl and foldr mentioned in answers to this question.
map :: (a -> b) -> [a] -> [b]
fmap :: Functor f => (a -> b) -> f a -> f b
liftM :: Monad m => (a -> b) -> m a -> m b
Why do we have three different functions that do essentially the same thing?
map exists to simplify operations on lists and for historical reasons (see What's the point of map in Haskell, when there is fmap?).
You might ask why we need a separate map function. Why not just do away with the current
list-only map function, and rename fmap to map instead? Well, that’s a good question. The
usual argument is that someone just learning Haskell, when using map incorrectly, would much
rather see an error about lists than about Functors.
-- Typeclassopedia, page 20
fmap and liftM exist because monads were not automatically functors in Haskell:
The fact that we have both fmap and liftM is an
unfortunate consequence of the fact that the Monad type class does not require
a Functor instance, even though mathematically speaking, every monad is a
functor. However, fmap and liftM are essentially interchangeable, since it is
a bug (in a social rather than technical sense) for any type to be an instance
of Monad without also being an instance of Functor.
-- Typeclassopedia, page 33
Edit: agustuss's history of map and fmap:
That's not actually how it happens. What happened was that the type of map was generalized to cover Functor in Haskell 1.3. I.e., in Haskell 1.3 fmap was called map. This change was then reverted in Haskell 1.4 and fmap was introduced. The reason for this change was pedagogical; when teaching Haskell to beginners the very general type of map made error messages more difficult to understand. In my opinion this wasn't the right way to solve the problem.
-- What's the point of map in Haskell, when there is fmap?
It seems to me like [] (empty list) and None/Nothing are so similar.
I was wondering if any of that family of languages had the basic list type where each element is an option and the tail guard is Nothing?
Is it(not done this way, in languages that have separate types) because it would make pattern matching lists overly verbose?
You have something similar in Lisp, where nil is used both to represent both the absence of an optional value and the empty list. You can do something like this in Haskell (and I assume most ML-like languages),
newtype MyList a = MyList (Maybe (a, MyList a))
but it's more verbose and doesn't have any obvious benefits over using its own data type.
Hmm, not that I'm aware of. That would be difficult for a Hindley-Milner type system, which forms the basis the type systems for those languages. (In Haskell nomenclature) Nothing would have to have type Maybe a and [] a simultaneously.
Something similar (but unfortunately too unwieldy to use in practice IMO) can be constructed using a fixed point type over Maybe:
-- fixed point
newtype Mu f = In (f (Mu f))
-- functor composition
newtype (f :. g) a = O (f (g a))
type List a = Mu (Maybe :. (,) a)
This is isomorphic to what you are asking for, but is a pain in the butt. We can make a cons function easily:
In (O (Just (1, In (O (Just (2, In (O Nothing)))))))
In and O are "identity constructors" -- they only exist to guide type checking, so you can mentally remove them and you have what you want. But, unfortunately, you cannot physically remove them.
We can make a cons function easily. We are not so lucky with pattern matching. I can't speak for the other ML family languages, but IIRC they can't even represent higher-kinded types like Mu.