synapse
synapse

Reputation: 5728

Shorter syntax for context bounds?

Is there a way to use shorter syntax when using context-bound type parameters? At the moment I have something like this

case class Vector2D[a : Numeric](x: a, y: a) {
  val numA = implicitly[Numeric[a]]

  def length2 = numA.plus(numA.times(x, x), numA.times(y, y))
}

and it makes more complex formulae unreadable.

Upvotes: 1

Views: 120

Answers (2)

Didier Dupont
Didier Dupont

Reputation: 29548

Just

import Numeric.Implicits._

then for every type that for which an implicit Numeric can be found

(importing just the NumericOps conversion of one Numeric instance as suggested by @Havoc P gives you finer control as to for which types operations are available, but most of the time, Numeric.Implicits should be fine)

On the more general question is there a shorter syntax when using context bounds type parameters: in general, there is not. It is up to the typeclass to provide some sugar to make it easy to use, as Numeric does here.

For instance, it is more or less customary to have an apply method in the companion object which makes getting the instance a little easier than with implicitly

object Ordering {
   def apply[T](implicit ord: Ordering[T]): Ordering[T] = implicitly[Ordering[T]]
}

so that you can get the implicit just with e.g Ordering[Int], rather than implicitly[Ordering[Int]].

Upvotes: 2

Havoc P
Havoc P

Reputation: 8477

Try this REPL session:

scala>  case class Vector2D[T : Numeric](x: T, y: T) {
  val numeric = implicitly[Numeric[T]]
  import numeric._
  def length2 = (x*x)+(y*y)
}

defined class Vector2D

scala> Vector2D(3,4).length2
res0: Int = 25

This is because Numeric contains an implicit conversion called mkNumericOps which you can import as shown above. If it didn't come out of the box, the way you could roll this yourself would be something like:

scala> implicit class NumericOps[T](val x: T) extends AnyVal { def +(y: T)(implicit n: Numeric[T]): T = n.plus(x, y)
     | def *(y: T)(implicit n: Numeric[T]): T = n.times(x, y)
     | }
defined class NumericOps

scala> case class Vector2D[a : Numeric](x: a, y: a) { def length2 = (x*x)+(y*y) }
defined class Vector2D

scala> Vector2D(3,4).length2
res0: Int = 25

If you make NumericOps not a value class (don't extend AnyVal) then the implicit Numeric can go on the constructor instead of each method, which might be better, or not really matter.

Anyway there's no need to write your own since Numeric already has mkNumericOps.

These "ops" classes are called the "enrich my library" pattern.

Numeric.Ops is here and the implicit being imported to auto-create it is mkNumericOps on Numeric, here.

Upvotes: 2

Related Questions