I have just started learning Haskell. As I know maximum
gives the max value for a list of integers. So, maximum [2,5,7,1]
gives 7. But why by giving a tuple input does maximum always give the second element? E.g., maximum (8,1)
gives 1. The same thing happens with sum (8,1)
, product (5,2)
, minimum (4,5)
... all give the second element of the tuple. So, can anyone explain to a beginner why this happens?

- 200
- 1
- 7
-
2For some additional context [Why does length return 1 for a tuple with 2 elements, and gives an error for a tuple with more elements?](https://stackoverflow.com/questions/36460833/why-does-length-return-1-for-a-tuple-with-2-elements-and-gives-an-error-for-a-t) – Redu Nov 06 '19 at 10:47
-
2A key concern is that for __tuples__, the types of the 2 elements are allowed to differ, so that (8, True) and (True, 8) are perfectly legal Haskell expressions. So there is no general way to define a maximum function that is both general and intuitively satisfying. For __lists__, all elements have to be of the same type, so [True, 8] is illegal, while maximum [8,1] and maximum [1,8] are both legal and evaluate to 8, as you would intuitively expect. – jpmarinier Nov 06 '19 at 11:31
-
4Tuples aren't sequences in the sense that lists are. They are single values with multi-values tags. `(8, 1)` can be thought of as `1` with a tag of `8`; `(1,2,3,4,5)` can be thought of as a `5` with a four distinguished tags `1`, `2`, `3`, and `4`. – chepner Nov 06 '19 at 12:57
2 Answers
Short answer: for a 2-tuple, the Foldable
instance takes (only) the second item into account. The maximum
function will thus always return the second item of the 2-tuple.
Because a 2-tuple is an instance of Foldable
. Indeed, it is defined as [src]:
instance Foldable ((,) a) where foldMap f (_, y) = f y foldr f z (_, y) = f y z
The maximum
is in essence a foldr pattern. It is implemented as an equivalent of:
maximum = foldr1 max
Where foldr1
is implemented as:
foldr1 f = fromMaybe (error "…") . foldr mf Nothing
where mf x m = Just (case m of
Nothing -> x
Just y -> f x y)
So that means that for a 2-tuple, it will be implemented as:
maximum (_, y) = fromMaybe (error "…") (mf y Nothing)
where mf x m = Just (case m of
Nothing -> x
Just y -> max x y)
So here we call mf
with y
(as x
parameter), and Nothing
as m
. The case … of …
this matches with Nothing
and returns x
. So the maximum for a 2-tuple is defined as:
maximum (_, y) = fromMaybe (error "…") (Just y)
and thus shorter:
-- maximum for a (a, b)
maximum (_, y) = y

- 443,496
- 30
- 428
- 555
-
Do you know why it's defined this way rather than taking both values into account? – TheInnerLight Nov 06 '19 at 10:46
-
1@TheInnerLight: it can not take both values into account since the instance of a `Foldable` should be of kind `* -> *`, so an `instance Foldable (,)` would "crash", since that has as kind `* -> * -> *`. – Willem Van Onsem Nov 06 '19 at 10:48
-
1@TheInnerLight what would be the maximum of this `('qwerty', 12345)`? Define the maximum of any tuple is actually senseless. But you can always define your own `maxTuple` function for tuples of type `(Int, Int)` – lsmor Nov 06 '19 at 10:50
-
3@TheInnerLight Because the types are different, in general. For instance `maximum ("hello", 4)` can not consider both elements. Here the type is `(,) String Int`, which is seen by `Foldable` as a container-like type `(,) String` which happens to have `Int` as its element. I'm not terribly convinced it was a good idea to allow `Foldable ((,) a)` in the libraries, but if we allow that, we have no type-safe option but to consider only the second component of the pairs. – chi Nov 06 '19 at 10:51
-
1@chi: me neither. TO be honest, I think a 2-tuple should probably not be a `Foldable`, since it creates a lot of confusion. Yes, we can see a 2-tuple as some sort of "value with a context" (that context is then the first item), but it will likely give a lot of confusion. – Willem Van Onsem Nov 06 '19 at 10:58
-
2To play devil's advocate, I think the `Foldable` instance is good not because you would often want to fold a single-element container, but because it emphasizes the fact that a tuple is not just a minor variation on a list. The fact that `maximum (8,1)` doesn't return `8` should force you to stop and ask yourself why you have a tuple in the first place. – chepner Nov 06 '19 at 13:35
-
1I'm not sure where that metaphor is going. My point is, if you think passing a tuple to `maximum` is a good idea, rethink why you are using a tuple. Tuples are foldable, just not in the way lists are foldable, and that's neither good nor bad, just *different*. – chepner Nov 06 '19 at 15:22
-
@chepner If the goal is to discourage me from using `maximum (x,y)`, a type error would be the most effective means, IMO. Unrelated: I wonder how many packages on hackage rely on such a weird instance, after it being in `base` for 5? years. – chi Nov 06 '19 at 15:51
-
1My goal *isn't* to discourage you from using `maximum (x,y)`; it's to discourage you from using `(x, y)` in a context where you think comparing `x` and `y` to each other will be necessary. – chepner Nov 06 '19 at 16:05
Tuples in Haskell are not multi-valued containers like they are in, say, Python. Rather, they are closer to single-valued containers like Maybe a
or Either b a
: a value with a context. While both Maybe
and Either
carry the concept of possibly no value (Either
simply providing more information than Maybe
about the lack of a value), a tuple carries the context of information about the value itself.
A value like (8, 1
) does not contain two values 8
and 1
. Rather, 8
is part of a container that contains a 1
.
As such, tuples are foldable, even if doing so seems trivial. Reducing a value of type (a, b)
to a value of b
simply has to return the value of type b
, without worrying about what to do with the other values of type b
, because there aren't any.
>>> maximum (Just 5)
5
>>> minimum (Just 5)
5
>>> maximum (Right 5)
5
>>> minimum (Right 5)
5
>>> maximum (True, 5)
5
>>> minimum (True, 5)
5
Such functions are total with tuples, though, as compared to Maybe
or Either
:
>>> maximum Nothing
*** Exception: maximum: empty structure
>>> maximum (Left 5)
*** Exception: maximum: empty structure
No matter how many types the tuple contains, all but the right-most one will fixed for any given instance of Foldable
.
-- Actual instance for (a, b)
instance Foldable ((,) a) where
foldMap f (_, y) = f y
foldr f z (_, y) = f y z
-- Instance for (a, b, c) if you wanted it
instance Foldable ((,,) a b) where
foldMap f (_, _, y) = f y
foldr f z (_, _, y) = f y z

- 497,756
- 71
- 530
- 681