Adding a validity check dependent on a typeclass (Optional implicits)
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
&l开发者_C百科t;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
class My2Col[A](a1 : A, a2 : A)(implicit num: Numeric[A] = null){
for{check <- Option(num); if(check.gteq(a1, a2))}
throw new IllegalArgumentException
}
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)
}
}
精彩评论