7

In scala, we can use implicit typeclasses to conditionally add methods onto a parameterized type dependent on that type's parameters. For example, Iterator.sum:

def sum[B >: A](implicit num: Numeric[B]): B = foldLeft(num.zero)(num.plus)

There must be an instance of the Numeric typeclass for this method to even be called:

scala> List(1, 2, 3).sum
res0: Int = 6

scala> List("a", "b").sum
<console>:6: error: could not find implicit value for parameter num: Numeric[java.lang.String]
       List("a", "b").sum
                  ^

So far, so good. Let's say I want to have some collection type, My2Col:

class My2Col[A](a1 : A, a2 : A)

But I want to mandate that, if this is made with a A : Numeric, then a2 > a1. However, it is entirely valid for it to be made with an A which is not numeric.

My2Col("a", "b") //OK
My2Col("b", "a") //OK
My2Col(1, 2)     //OK
My2Col(2, 1)     //THROW IllegalArgumentException

Has anyone any ideas as to how I might do this?

PS. If anyone has any suggestions for a better question title, I'm all ears

oxbow_lakes
  • 133,303
  • 56
  • 317
  • 449

2 Answers2

12
class My2Col[A](a1 : A, a2 : A)(implicit num: Numeric[A] = null){
  for{check <- Option(num); if(check.gteq(a1, a2))}
     throw new IllegalArgumentException
}
Vasil Remeniuk
  • 20,519
  • 6
  • 71
  • 81
3

I would implement this by creating 2 implicits that represent requirements, one being more general (for all types other than, say, Int or Numeric[T]), and the other being more specific (for Int or Numeric[T]).

Then, following the rules of implicit resolution, I would put the more specific one in the companion object of the Requirement type, and the more general one in the base class of the companion object. This way I would ensure that the compiler tries to apply the more specific one first.

Of course, the downside is that if the user were to explicitly provide the implicit parameter, this mechanism could be circumvented not to do the check.

Something like this (where Int is the type with specific rules, and Foo is the collection class):

  package ex                                                                                                          

  trait Requirement[T] {                                                                                            
    def check(a1: T, a2: T): Unit                                                                                   
  }                                                                                                                 

  trait BaseReq {                                                                                                   
    implicit def genericReq[T] = new Requirement[T] {                                                               
      def check(a1: T, a2: T) {println("generic")}                                                                  
    }                                                                                                               
  }                                                                                                                 

  object Requirement extends BaseReq {                                                                              
    implicit object IntReq extends Requirement[Int] {                                                               
      def check(a1: Int, a2: Int) = {                                                                               
        println("int")                                                                                              
        if (a2 <= a1) throw new IllegalArgumentException                                                            
      }                                                                                                             
    }                                                                                                               
  }                                                                                                                 

  class Foo[T](a1: T, a2: T)(implicit req: Requirement[T]) {                                                        
    req.check(a1, a2)                                                                                               

    // whatever `foo` does follows                                                                                  
  }                                                                                                            

  object Main {                                                                                                     
    def main(args: Array[String]) {                                                                                 
      new Foo(1, 2)                                                                                                 
      new Foo("S1", "S2")                                                                                           
      new Foo(2, 1)                                                                                                 
    }                                                                                                               
  }
axel22
  • 32,045
  • 9
  • 125
  • 137
  • Hm, I'm not sure if one can then access the `Requirement` object to invoke its methods.. – axel22 Dec 06 '10 at 12:18
  • 1
    @axel22 If you use a context bound (i.e. `Foo[T : Requirement]`), you can use `implicitly[Requirement[T]]` to refer to the associated `Requirement[T]`. Personally, I prefer to write it the way you did originally and only use context bounds if I don't need an instance of the type class, as it's DRYer. – Aaron Novstrup Dec 07 '10 at 02:07
  • Apparently, you can also refer to the instance of the type class by the name of the generated compiler parameter (`evidence$1`), since using a context bound is just sugar for adding your own implicit parameter. It's probably not a good idea, though, since that name could change in the future. Seems like a bug that you can even use that name in a method/constructor body.... – Aaron Novstrup Dec 07 '10 at 02:15
  • http://stackoverflow.com/questions/4373070/how-do-i-get-an-instance-of-the-type-class-associated-with-a-context-bound – Aaron Novstrup Dec 07 '10 at 03:37
  • @Aaron: Cool - implicitly is then the way to go. Yes, I agree that using `evidence$1` directly is bad - I'd also say that that's a bug. – axel22 Dec 07 '10 at 07:41
  • @axel22 See the SO question I linked to above -- you can write a method that functions like `implicitly` but does not require repeating the name of the type class. – Aaron Novstrup Dec 07 '10 at 15:55
  • 1
    @Aaron: Yes, I saw it - even better. Perhaps you could propose it as an enhancement to the standard library. – axel22 Dec 07 '10 at 16:27