Why in Haskell maximum (8,1) = 1? [duplicate]
Asked Answered
G

2

7

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?

Gramps answered 6/11, 2019 at 10:32 Comment(3)
For some additional context Why does length return 1 for a tuple with 2 elements, and gives an error for a tuple with more elements?Senile
A 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.Handbook
Tuples 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.Osber
T
8

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
Tollefson answered 6/11, 2019 at 10:41 Comment(9)
Do you know why it's defined this way rather than taking both values into account?Tullus
@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 * -> * -> *.Tollefson
@Tullus 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)Samford
@Tullus 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.Elephantiasis
@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.Tollefson
To 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.Osber
I'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.Osber
@Osber 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.Elephantiasis
My 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.Osber
O
1

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
Osber answered 6/11, 2019 at 16:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.