0

I have a case class called Point defined as follows:

case class Point(x: Double, y: Double)

and a functions that takes an array of points:

def f(coords: Array[Point]) ...

I want to be able to implictly pass an array of double arrays to my function. To achieve this I have defined the following two implicit functions:

implicit def arrayToPoint(a: Array[Double]) = new Point(a(0), a(1)) 
implicit def arraysToPoints(a: Array[Array[Double]]) = a map(p => Point(p(0), p(1)))

My questions is is there any way I can achieve this with just a single implict conversion function to simplify matters?

And as a related question what would be the best approach if I wanted to be able to pass an Array of Ints instead of Doubles?

Regards

Des

1 Answer 1

1

Your method arraysToPoints is superfluous. You could use a view bound on the array argument for your method f and add the conversion to the companion object of Point, like so:

object Point {
  implicit def arrayToPoint[A](a: Array[A])(implicit view: A => Double): Point =
    Point(a(0), a(1))
}
case class Point(x: Double, y: Double)

def f[P](coords: Array[P])(implicit view: P => Point): Unit = coords.foreach { p =>
  println(p: Point)
}

f(Array(Point(1, 2), Point(2, 3)))
f(Array(Array(1.0, 2.0), Array(3.0, 4.0)))
f(Array(Array(1, 2), Array(3, 4)))

In order to allow both arrays of Int and Double to be covered, I have used a second view bound on the arrayToPoint method. Otherwise you would need two separate conversion methods for Array[Int] and Array[Double].

You can read this definition of f as, "take an array of elements of a type P which can be viewed as type Point". One spot where the compiler looks for such views is the companion object of the target type, thus object Point. This is a good place for implicit methods.


The second possibility would be to use the magnet pattern. Instead of converting point by point with a view in f, you would create a single wrapper object at once. This is a bit prettier and for large arrays minimises the penalty on direct Array[Double] arguments (because you instantiate the wrapper once, but then do not need to call the view function any more). arrayToPoint is used whenever the array element type A once again can be viewed as a Double. This is true for Double itself of course, but also for Int which can be seen as a Double though what Scala calls numeric widening (e.g., you can say val x: Double = 33 and the integer 33 is implicitly "widened" to a double).

object Points {
  implicit def direct(a: Array[Point]): Points =
    new Points {
      val peer = a
    }

  implicit def indirect[A](a: Array[Array[A]])(implicit view: A => Double): Points =
    new Points {
      lazy val peer = a.map { c => Point(c(0), c(1)) }
    }
}
trait Points {
  def peer: Array[Point]
}

def f(coords: Points): Unit = coords.peer.foreach(println)

This looks in the companion object for an implicit method from the argument type to the special magnet type Points. I use lazy val for the non-direct arrays so that we might save the actual conversion action if the peer method is not called.

Sign up to request clarification or add additional context in comments.

2 Comments

In general, my advise would be to use view bounds rarely. I would use these conversions from arrays to Point only if you have them really all over the place. If you have only a few places where such conversion is needed, it is better to perform it explicitly (you can use the method arrayToPoint and just remove its implicit modifier). Especially when you are new to Scala, you can get quite confused with constructing too many implicit conversions, before you have fully understood when and in which scope they should be used. In Scala 2.10, the compiler even emits a warning.
And of course you can also use the <% notation rather than have to declare an explcit view bound conversion. ie ArrayToPoint[A <% Double](a: Array[A])

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.