Question

I have the following generic Interval class (kindly formulated for me by user soc):

case class Interval[T](from: T, to: T)(implicit num: Numeric[T]) {
  import num.mkNumericOps // allows us to write from.toDouble and to.toDouble
  def mid: Double = (from.toDouble + to.toDouble) / 2.0
}

Typical use cases: Interval[Double] or Interval[Int]. To add binary union and intersection operators I followed a similar pattern with (implicit num: Numeric[T]) in the companion object:

object Interval {

  def union[T](interval1: Interval[T], interval2: Interval[T])(implicit num: Numeric[T]) = {
    import num.mkOrderingOps // allows interval1.from min
    Interval[T](interval1.from min interval2.from, interval1.to max interval2.to)
  }

  def intersect[T](interval1: Interval[T], interval2: Interval[T])(implicit num: Numeric[T]) = {
    import num.mkOrderingOps
    Interval[T](interval1.from max interval2.from, interval1.to min interval2.to)
  }

}  

It's ugly boilerplate to copy the (implicit num: Numeric[T]) and import num.mkOrderingOps inside both methods. Is there some way to do this just once, at the level of the Interval object itself?

Was it helpful?

Solution

Yes there is.

First with the import. You can import Ordering.Implicits._ in the scope of Interval instead.

object Interval {
   import Ordering.Implicits._

   def union[T](....)(implicit num: Numeric[T]) = {
     // do not import num.mkOrderingOps
     ...
   }
   ...
}

With those implicits, when it find an ordering operation, it will look for the implicit Ordering (Numeric is an Ordering) in scope where the operation happens. And there happens to be one proper implicit in scope in each of your routines. If you need arithmetic operations too, also import Numeric.Implicits._

Now with the implicit argument. There is a shortcut in the language for that, which is called a context bound : you can write def f[T: X](args) rather than def f[T](args)(implicit someName: X[T])

The difference is that you don't have a name for the implicit with the context bound (you can use implictly[T] but then this is hardly shorter. Fortunately, you do not need a name anymore with the import Ordering.Implicits._

So

object Interval {
   import Ordering.Implicits._
   // also import Numeric.Implicits._ if you need +,-,*,/ ...
   def union[T: Numeric] ...
   def intersection[T: Numeric] ...
}

OTHER TIPS

The uses of the Numeric type class in the Interval object have a type parameter T which must be bound somewhere in their enclosing scope. Interval, being a unique constant value, can't provide that binding.

One solution to this specific problem would be to move the definitions of your union and intersect operations into the Interval class as ordinary instance methods, in which case they would share the binding of T and the associated Numeric instance with the rest of the class,

case class Interval[T : Numeric](from: T, to: T) {
  import Numeric.Implicits._
  import Ordering.Implicits._

  def mid: Double = (from.toDouble + to.toDouble) / 2.0
  def union(interval2: Interval[T]) =
    Interval(this.from min interval2.from, this.to max interval2.to)
  def intersect(interval2: Interval[T]) =
    Interval(this.from max interval2.from, this.to min interval2.to)
}

If, however, you prefer to keep the definitions of these operations separate from the Interval class, one approach to reducing the amount of implicit boilerplate that you need to chase through your APIs is to define your own higher-level type classes in terms of Numeric[T]. For example,

// Type class supplying union and intersection operations for values
// of type Interval[T]
class IntervalOps[T : Numeric] {
  import Ordering.Implicits._

  def union(interval1: Interval[T], interval2: Interval[T]) =
    Interval[T](interval1.from min interval2.from, interval1.to max interval2.to)

  def intersect(interval1: Interval[T], interval2: Interval[T]) =
    Interval[T](interval1.from max interval2.from, interval1.to min interval2.to)
}

implicit def mkIntervalOps[T : Numeric] = new IntervalOps[T]

which in use would look like,

def use[T](i1 : Interval[T], i2 : Interval[T])(implicit ops : IntervalOps[T]) = {
  import ops._
  val i3 = union(i1, i2)
  val i4 = intersect(i1, i2)
  (i3, i4)
}

A third options combines these two, using an implicit definition to enrich the original class with the additional methods,

class IntervalOps[T : Numeric](interval1 : Interval[T]) {
  import Ordering.Implicits._

  def union(interval2: Interval[T]) =
    Interval[T](interval1.from min interval2.from, interval1.to max interval2.to)

  def intersect(interval2: Interval[T]) =
    Interval[T](interval1.from max interval2.from, interval1.to min interval2.to)
}

implicit def enrichInterval[T : Numeric](interval1 : Interval[T]) =
  new IntervalOps[T](interval1)

type Ops[T] = Interval[T] => IntervalOps[T]

Then in use,

def use[T](i1 : Interval[T], i2 : Interval[T])(implicit ops : Ops[T]) = {
  val i3 = i1 union i2
  val i4 = i1 intersect i2
  (i3, i4)
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top