3

Given a higher order function that takes a function of type (A,A) => Boolean as a parameter:

 def isSorted[A](as: Array[A], ordered :  (A,A) => Boolean) : Boolean =
 {
   def doSorted(i: Int) : Boolean = 
     if (i >= as.length-1) true
     else if (ordered(as(i), as(i+1))) false
     else doSorted(i+1)

  doSorted(0)
 }
 val lst1 = Array(1,2,3,4,5,6)

I can declare a function with known parameter types and pass it in:

 def gtr(a : Int, b : Int) : Boolean = {
   a > b
 }

isSorted(lst1,gtr) /* works :-) */

I'd like to do one of the following. None of which seem to work for me:

  1. Use a function with parametrised types:

     def gtr[T](a : T, b : T) : Boolean = {
          a > b    /* gives value > is not a member of type parameter T */
     }
    

    Is this possible in Scala. Do I have to tell the compiler that T is inherited from an object that has a > operator?

  2. Use an anonymous function:

    isSorted(lst1, ((x,y) => x > y)) /* gives missing parameter type */
    
  3. Use Scala underscore magic to pass the > operator

    isSorted(lst1, _>_) /* gives missing parameter type */
    

None of these three options work for me and I'm struggling to work out why. Can anyone tell me which of the above approaches are valid in Scala and what I'm doing wrong.

DUFF
  • 454
  • 1
  • 4
  • 18

2 Answers2

3

You're right. You need to specify, that T can be compared. One approach is to use view bounds:

    def gtr[T <% Ordered[T]](a : T, b : T) : Boolean = {
      a > b  
 }

[T <% Ordered[T]] can be read as "T may be viewed as an Ordered[T]" (extended or implicitly converted). This approach is now deprecated https://issues.scala-lang.org/browse/SI-7629), so you may use implicit evidence to convert T to Ordered[T]

def gtr[T](a : T, b : T)(implicit toOrdered: T => Ordered[T]) : Boolean

A very nice post, which explains view bounds in more details: What are Scala context and view bounds?

Regarding your 2nd and 3rd questions: in 2nd example, you need to infer the types for x and y to make code compile. 3rd - is just syntactic sugar for 2nd. Both of them will compile.

isSorted(Array(1,2,3,4,5,6), ((x : Int,y: Int) => x > y))
isSorted(Array(1,2,3,4,5,6), ((_: Int) > (_: Int)))
Community
  • 1
  • 1
I See Voices
  • 842
  • 5
  • 13
3

The problem is that your type parameter A has no restrictions on it, which means you can pass any type. But that can't be, because not every type has a > method, like you say. There is also no generic type that other types with > inherit from, either. The only option left is type classes, for which we have the Ordering type class. You will need to require that an Ordering[A] exists (implicitly) in the signature of your method.

I'll do it for the greater method, with one caveat. We will need to curry the parameters of isSorted.

def isSorted[A](as: Array[A])(ordered: (A, A) => Boolean): Boolean = {
    def doSorted(i: Int) : Boolean = 
        if (i >= as.length - 1) true
        else if (ordered(as(i), as(i + 1))) false
        else doSorted(i + 1)

    doSorted(0)
}

Now we define greater to implicitly find an Ordering[A]. Once we have one, we can use its gt method to make the actual comparison.

def greater[A: Ordering](a: A, b: A): Boolean = implicitly[Ordering[A]].gt(a, b)

This is the same as:

def greater[A](a: A, b: A)(implicit ord: Ordering[A]): Boolean = ord.gt(a, b)

Usage:

scala> isSorted(Array(1, 2, 3, 4))(greater _)
res88: Boolean = true

scala> isSorted(Array(1, 4, 3, 4))(greater _)
res89: Boolean = false
Michael Zajac
  • 55,144
  • 7
  • 113
  • 138