A Haskell-ish take on this question is to observe that the following operations:
max, min :: Ord a => a -> a -> a
max a b = if a < b then b else a
min a b = if a < b then a else b
...are associative:
max a (max b c) == max (max a b) c
min a (min b c) == min (min a b) c
As such, any type Ord a => a
together with either of these operations is a semigroup, a concept for which reusable abstractions can be built.
And you're dealing with Maybe
(Haskell for "option"), which adds a generic "neutral" element to the base a
type (you want max Nothing x == x
to hold as a law). This takes you into monoids, which are a subtype of semigroups.
The Haskell semigroups
library provides a Semigroup
type class and two wrapper types, Max
and Min
, that generically implement the corresponding behaviors.
Since we're dealing with Maybe
, in terms of that library the type that captures the semantics you want is Option (Max a)
—a monoid that has the same binary operation as the Max
semigroup, and uses Nothing
as the identity element. So then the function simply becomes:
maxOpt :: Ord a => Option (Max a) -> Option (Max a) -> Option (Max a)
maxOpt a b = a <> b
...which since it's just the <>
operator for Option (Max a)
is not worth writing. You also gain all the other utility functions and classes that work on Semigroup
and Monoid
, so for example to find the maximum element of a [Option (Max a)]
you'd just use the mconcat
function.
The scalaz library comes with a Semigroup
and a Monoid
trait, as well as Max
, Min
, MaxVal
and MinVal
tags that implement those traits, so in fact the stuff that I've demonstrated here in Haskell exists in scalaz as well.