Not infrequently, one wants to implement the <=>
(comparison, or "spaceship") operator on a product data type, i.e., a class with multiple fields (all of which (we hope!) already have <=>
implemented), comparing the fields in a certain order.
def <=>(o)
f1 < o.f1 && (return -1)
f1 > o.f1 && (return 1)
f2 < o.f2 && (return -1)
f2 > o.f2 && (return 1)
return 0
end
This is both tedious and error-prone, especially with a lot of fields. It's error-prone enough that I frequently feel I should unit test that function, which just adds to the tediousness and verbosity.
Haskell offers a particularly nice way of doing this:
import Data.Monoid (mappend) import Data.Ord (comparing) -- From the standard library: -- data Ordering = LT | EQ | GT data D = D { f3 :: Int, f2 :: Double, f1 :: Char } deriving Show compareD :: D -> D -> Ordering compareD = foldl1 mappend [comparing f1, comparing f2, comparing f3]
(For those not familiar with fold
, the above expands to
comparing f1 `mappend` comparing f2 `mappend` comparing f3
which produces a function that can be applied to two D
s, to produce an Ordering
.)
The defintion of compareD
is so simple that it's obviously correct, and I wouldn't feel the need to unit test it even without static type checking.
Actually, the question may be even slightly more interesting than this, since I may not want to use just the standard <=>
operator, but sort in different ways at different times, e.g.:
sortByOrderings :: [a -> a -> Ordering] -> [a] -> [a] sortByOrderings = sortBy . foldl1 mappend sortByF3F1 = sortByOrderings [comparing f3, comparing f1] sortByF2F3 = sortByOrderings [comparing f2, comparing f3]
So, the questions:
- What's the typical way of implementing this sort of thing in Ruby?
- What's the nicest way of doing it using just what's defined in the standard libraries?
- How close can one get to the Haskell code above, and how reliable is it, in comparison? If necessary, how can one ensure that the fields have a properly implemented
<=>
or<
and>
operators?
Incidently, while this is a Ruby question, I'm happy to consider discussion of the Haskell techniques on-topic if the elders of this site so agree. Please feel free to comment on whether that's appropriate or not and, if it is, tag this post 'haskell' as well.