Reputation: 1595
I'm trying to solve an exercise from the book Scala by example , chapter 15 Implicit Parameters and Conver- sions which can be found here :
and have the following code sample :
object DemoImplicitConversions {
def main(args: Array[String]) {
val xs = List(new Num(1), new Num(4), new Num(2), new Num(6),new Num(3))
val sortedXs = sort(xs)(num2ordered)
print(sortedXs.mkString(","))
}
type OrderedView[A] = A => Ordered[A]
// View bound : [A <% Ordered[A]] - means that sort is applicable to lists of type A such that there exists an
// implicit conversion from A to Ordered[A]
def sort[A: OrderedView](xs: List[A])(c: OrderedView[A]): List[A] =
if (xs.isEmpty || xs.tail.isEmpty) xs
else {
val (ys, zs) = xs.splitAt(xs.length / 2)
merge(ys, zs)(c)
}
def merge[A: OrderedView](xs: List[A], ys: List[A])(c: OrderedView[A]): List[A] =
if (xs.isEmpty) ys
else if (ys.isEmpty) xs
else if (c(xs.head) < ys.head) xs.head :: merge(xs.tail, ys)(c)
else ys.head :: merge(xs, ys.tail)(c)
implicit def num2ordered(x: Num): Ordered[Num] = new Ordered[Num] {
override def compare(y: Num): Int =
if (x.value < y.value) -1
else if (x.value > y.value) 1
else 0
}
}
case class Num(value: Int) {
override def toString: String = value.toString
}
Unfortunately I don't find a way to have implicitly the converter assigned to the method sort so that the client code can look like :
def main(args: Array[String]) {
val xs = List(new Num(1), new Num(4), new Num(2), new Num(6),new Num(3))
val sortedXs = sort(xs)
printList(sortedXs)
}
If I add the implicit keyword for the converter parameter of the methods merge and sort I get
ambiguous implicit values
compilation error message.
Upvotes: 2
Views: 208
Reputation: 16324
The type constraint you're placing on A
is called a context bound. As you correctly write in your comment, A : OrderedView
means that there is available an implicit value of A[OrderedView]
. Your mistake is how you try to get that instance. You wrote the method signature as:
def merge[A: OrderedView](xs: List[A], ys: List[A])(c: OrderedView[A]): List[A]
But when using a context bound, it should be:
def merge[A: OrderedView](xs: List[A], ys: List[A]): List[A]
And then use the implicitly
operator to get the instance you want. So your methods could look like:
def sort[A: OrderedView](xs: List[A]): List[A] = (merge _).tupled(xs.splitAt(xs.length / 2))
def merge[A: OrderedView](xs: List[A], ys: List[A]): List[A] = {
//This is how we get the c instance
val c = implicitly[OrderedView[A]]
...
}
Your other option is to get rid of the context view and use an implicit parameter instead (and then don't use implicitly
in the method body):
def merge[A](xs: List[A], ys: List[A])(implicit c: OrderedView[A]): List[A]
These are exactly equivalent. In fact, the context bound is basically translated into this version during compilation. What you had was mixing these idioms, by using a context bound and using another parameter list (although yours was not implicit, which is required).
In your example, you don't even need an explicit instance of OrderedView[A]
. Since it is available in implicit scope (as guaranteed by the context bound), it is automatically applied when necessary. So, you can even simplify things further:
def merge[A: OrderedView](xs: List[A], ys: List[A]): List[A] =
if (xs.isEmpty) ys
else if (ys.isEmpty) xs
//Here, the implicit conversion occurs on xs.head
else if (xs.head < ys.head) xs.head :: merge(xs.tail, ys)
else ys.head :: merge(xs, ys.tail)
Also, you could use a view bound here, instead of the context bound. Then, you don't even need to introduce the OrderedView
type alias:
def sort[A <% Ordered[A]](xs: List[A]): List[A]
def merge[A <% Ordered[A]](xs: List[A], ys: List[A]): List[A]
//Or, equivalently:
def sort[A](xs: List[A])(implicit ev: A => Ordered[A]): List[A]
def merge[A](xs: List[A], ys: List[A])(implicit ev: A => Ordered[A]): List[A]
Read more about context bounds (and view bounds) here.
As an unrelated note, you should also explore using match
statements, which are pretty powerful in Scala:
def merge[A <% Ordered[A]](xs: List[A], ys: List[A]): List[A] = (xs, ys) match {
case (Nil, _) => ys
case (_, Nil) => xs
case (xhd::xtl, yhd::ytl) if xhd < yhd => xhd :: merge(xtl, ys)
case (_, yhd::ytl) => yhd :: merge(xs, ytl)
}
Upvotes: 4
Reputation: 127711
This definition:
def sort[A: OrderedView](xs: List[A])(c: OrderedView[A]): List[A]
is in fact equivalent to this:
def sort[A](xs: List[A])(c: OrderedView[A])(implicit ev: OrderedView[A]): List[A]
That is, your c: OrderedView[A]
parameter is absolutely independent on the context bound, it is just another parameter.
You just need either to omit the context bound:
def sort[A](xs: List[A])(implicit c: OrderedView[A]): List[A]
or omit the parameter:
def sort[A: OrderedView](xs: List[A]): List[A]
In the latter case, however, if you want to access the implicit parameter you will have to do it with implicitly
:
implicitly[OrderedView[A]](xs.head)
Maybe you won't even need to call it explicitly as an implicit conversion will fire automatically.
Upvotes: 1