So I accidentally wrote a Haskell answer to a Scala question recently. Being rather familiar with Haskell, the solution came quite easily to me:
myMaxBy :: (a -> a -> Ordering) -> [a] -> [a]
myMaxBy _ [] = undefined
myMaxBy f (x:xs) = foldr step [x] xs
where step y acc@(z:_) = case f y z of
GT -> [y]
EQ -> y:acc
LT -> acc
Then someone reminded me that it was a Scala question. I set out to transate my code into Scala, and after much pain I settled for:
(List(xs.head) /: xs.tail) { (acc, y) =>
y compare acc.head match {
1 => List(y)
0 => y :: acc
-1 => acc
}
}
But I could not for the life of me get the Scala type system to bend to my will and generalize this into a function where xs and compare are inputs (ideally, curried inputs with the comparator first). Though this is surely due to my general unfamiliarity with Scala, I also slightly blame Scala’s complex (though very powerful) type system. Can you do some hand-holding and walk me through how I could turn this into a generalized function, with a type signature similar to the Haskell equivalent? (Read: as general as.) Please also demonstrate usage, if it is more complicated than myMaxBy(myCompare)(someList).
You missed out the
casekeywords in your pattern-match, which are quite important!All you need is for the collection parameter type to be able to use a
comparemethod. There are two systems for comparing things in Scala: extendingOrdered, or using anOrdering. There are implicit conversions between the two so it doesn’t matter too much which you choose; the first is perhaps easier to understand.First off, using
Ordered:Here we give our generic type
Aa view bound using<%, which means “can be seen as”. This is more general than using an upper bound<:, and useful for classes that are not themselvesOrdered, but have implicit conversions toOrderedclasses, e.g.InttoRichInt.Alternatively, if you want flexibility to be able to change the ordering criteria, you can write it like this:
When invoking, if there’s an implicit
Ordering[A]in scope, you can leave the second argument out. This second way also has the advantage that you can define anOrderingon arbitrary classes, whether or not they already support it.You can invoke both using, for example,
myMaxBy(List(1,2,3,4,3,4)). In the second, if you wanted, say, reverse ordering:myMaxBy(List(1,2,3,4,3,4))(Ordering.Int.reverse).Another thing you might see in this context are context bounds. E.g.
[A: Ordering]. This means the same as[A](implicit ord: Ordering[A]), which is more concise, except that you don’t get a handle on theOrderingso have to summon it usingimplicitly. So here it’s probably best to state it explicity as above.