Math-y List Operations

Earlier this month we explored some functions related to booleans and lists. Today we'll consider a few simple helpers related to lists of numbers.

As in Python, Haskell allows you to easily take the product and sum of a list. These functions are very straightforward:

sum :: (Num a) => [a] -> a

product :: (Num a) => [a] -> a

...

>> sum [1, 2, 3, 4]
10
>> sum [-1, 1]
0
>> product [1, 2, 3, 4]
24
>> product [5, 6, -2]
-60

As with our boolean functions, I originally gave the type signatures in terms of lists, but they actually work for any "Foldable" type. So both the inner and outer type of our input are parameterized.

sum :: (Foldable t, Num a) => t a -> a

product :: (Foldable t, Num a) => t a -> a

...

>> sum $ Set.fromList [1, 2, 3]
6

Both of these functions also work with empty lists, since these operations have an identity to start with.

>> sum []
0
>> product []
1

The same cannot be said for the functions minimum and maximum. These require non-empty lists, or they will throw an error!

minimum :: (Foldable t, Ord a) => t a -> a

maximum :: (Foldable t, Ord a) => t a -> a

...

>> maximum [5, 6, -1, 4]
6
>> minimum [5, 6, -1, 4]
-1
>> maximum []
*** Exception: Prelude.maximum: empty list

And while they can be used with numbers, as above, they can also be used with any orderable type, such as strings.

>> maximum ["hello", "world"]
"world"

Remember what we mentioned a while ago with groupBy and how many functions have another version with By? This applies for our min and max functions. These allow us to provide a custom comparison operation.

maximumBy :: (Foldable t) => (a -> a -> Ordering) -> t a -> a
minimumBy :: (Foldable t) => (a -> a -> Ordering) -> t a -> a

As an example, let's consider that when comparing tuples, the first element is used to sort them. Only if there is a tie to we go to the second element.:

>> maximum [(1, 4), (2, 1), (2, 3)]
(2, 3)

However, we can instead use the second element as the primary comparison like so:

>> let f (x1, x2) (y1, y2) = if x2 == y2 then x1 `compare` y1 else x2 `compare` y2
>> maximumBy f [(1, 4), (2, 1), (2, 3)]
(1, 4)
>> minimumBy f [(1, 4), (2, 1), (2, 3)]
(2, 1)

This approach is also very helpful when you're dealing with more complex types, but you only care about comparing a single field.

As a final note, sort and sortBy follow this same pattern. The first one assumes an ordering property, the second allows you a custom ordering.

sort :: (Ord a) => [a] -> [a]

sortBy :: (a -> a -> Ordering) -> [a] -> [a]

For more tricks like this, keep following the Monday Morning Haskell blog every Monday and Thursday! You can subscribe to get our monthly newsletter as a summary. This will also get you access to subscriber resources like our Beginners Checklist.

Previous
Previous

Taking and Dropping

Next
Next

Changing and Re-Arranging