Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generalizing scala code into a function

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).

like image 443
Dan Burton Avatar asked Nov 23 '11 22:11

Dan Burton


People also ask

How do you create a function in Scala?

You can create function by using def keyword. You must mention return type of parameters while defining function and return type of a function is optional. If you don't specify return type of a function, default return type is Unit.

What is the meaning of => in Scala?

=> is syntactic sugar for creating instances of functions. Recall that every function in scala is an instance of a class. For example, the type Int => String , is equivalent to the type Function1[Int,String] i.e. a function that takes an argument of type Int and returns a String .

What does => int mean in Scala?

It means when you call the method, the argument is not evaluated before the method is executed, but rather, it is evaluated each time it is referred to inside the method.


1 Answers

You missed out the case keywords in your pattern-match, which are quite important!

All you need is for the collection parameter type to be able to use a compare method. There are two systems for comparing things in Scala: extending Ordered, or using an Ordering. 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:

  def myMaxBy[A <% Ordered[A]](xs: List[A]) = {
    (List(xs.head) /: xs.tail) { (acc, y) =>
      y compare acc.head match {
        case 1  => List(y)
        case 0  => y :: acc
        case -1 => acc
      }
    }  
  }

Here we give our generic type A a 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 themselves Ordered, but have implicit conversions to Ordered classes, e.g. Int to RichInt.

Alternatively, if you want flexibility to be able to change the ordering criteria, you can write it like this:

  def myMaxBy[A](xs: List[A])(implicit ord: Ordering[A]) = {
    (List(xs.head) /: xs.tail) { (acc, y) =>
      ord.compare(y, acc.head) match {
        case 1  => List(y)
        case 0  => y :: acc
        case -1 => acc
      }
    }
  }

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 an Ordering on 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 the Ordering so have to summon it using implicitly. So here it's probably best to state it explicity as above.

like image 105
Luigi Plinge Avatar answered Oct 07 '22 21:10

Luigi Plinge