views:

162

answers:

3

I would like to define a generic implicit converter that works for all subtypes of type T. For example:

abstract class Price[A] {
  def price(a: Any): Int
}

trait Car
case class Prius(year: Int) extends Car
trait Food
case class FriedChicken() extends Food

object Def {
  implicit def carToPrice[A <: Car](car: A): Price[A] = new Price[A] {
    def price(car: Any) = 100
  }

  implicit def foodToPrice[A <: Food](food: A): Price[A] = new Price[A] {
    def price(food: Any) = 5
  }

  // implicit object PriusPrices extends Price[Prius] {
  //   def price(car: Any) = 100
  // }
  // 
  // implicit object FriedChickenPrices extends Price[FriedChicken] {
  //   def price(food: Any) = 5
  // }
}

import Def._  

def add [A, B >: A](stuff: A, list: List[(B, Price[_])])(implicit p: Price[A]) = (stuff, p) :: list
val stuff = add(Prius(2000), add(FriedChicken(), Nil))
stuff map { x => x._2.price(x._1) }

The above code throws an error:

error: could not find implicit value for parameter p: Price[FriedChicken]
       val stuff = add(Prius(2000), add(FriedChicken(), Nil))
                                       ^

What am I doing wrong?

Update:

As @extempore pointed out, what's wrong is that I am confusing implicit conversions (view bounds) and context bounds (both make use of implicit parameters). There's nothing wrong with my generic implicit converters. The problem is that add is using context bounds instead of a view. So we can fix it as follows:

def add [A, B >: A](stuff: A, list: List[(B, Price[_])])(implicit view: A => Price[A]) = (stuff, view(stuff)) :: list

An interesting thing @extempore demonstrates in his code is that we don't really need a generic converter if Price[A] was contravariant. Basically, I can make Price[Car] work on behalf of Price[Prius], which is kind of what I wanted. So the alternative context bound version is:

abstract class Price[-A] {
  def price(a: Any): Int
}

implicit object CarPrice extends Price[Car] {
  def price(a: Any) = 100
}

implicit object FoodPrice extends Price[Food] {
  def price(a: Any) = 1
}

Related:

+2  A: 

The problem is that you've defined your implicit carToPrice and foodToPrice as implicit methods from a Car and Food values to Prices, yet no Car and Food values are in evidence where you need the conversions. Since you don't actually use the arguments in these implicit methods, what I think you really want is implicit values, like so:

implicit def carToPrice[A <: Car]/*(car: A)*/: Price[A] = new Price[A] {
  def price(car: Any) = 100
}

implicit def foodToPrice[A <: Food]/*(food: A)*/: Price[A] = new Price[A] {
  def price(food: Any) = 5
}
pelotom
Including food into conversion doesn't seem to solve it though:implicit def foodToPrice[A <: Food](food: A): Price[A] = new Price[A] { def price(a: Any) = food match { case chicken: FriedChicken => 2 case _ => 1 } }
eed3si9n
My suggestion is that you *don't* depend on a food argument (I commented it out in my answer).
pelotom
If you want to pattern match, do it on the argument to `price`.
pelotom
I see that your version works, but it's just not clear to me why mine is any different from `implicit def intToString(x: Int) = x.toString`.
eed3si9n
I added what I think is going on at the end of the question.
eed3si9n
+3  A: 

It's not very clear what you really want. You are indeed mixing up implicit conversions and implicit parameters. Rather than try to sort it out I wrote some code.

object Test {
  type Price = Int
  abstract class Pricable[-A] {
    def price(a: A): Price
  }

  trait Car
  case class Prius(year: Int) extends Car
  trait Food
  case class FriedChicken() extends Food

  implicit val CarPricingGun = new Pricable[Car] { 
    def price(a: Car): Price = 100
  }
  implicit val FoodPricingGun = new Pricable[Food] { 
    def price(a: Food): Price = 1
  }
  implicit def priceableItemToPrice[A: Pricable](x: A) =
    implicitly[Pricable[A]] price x

  def main(args: Array[String]): Unit = {
    val x1 = Prius(2000)
    val x2 = FriedChicken()

    println("Price of " + x1 + " is " + (x1: Price))
    println("Price of " + x2 + " is " + (x2: Price))
  }
}
// Output is:
//
// Price of Prius(2000) is 100
// Price of FriedChicken() is 1
// 
extempore
I want to 1) deal with implicit conversion for a family of type in one shot 2) store various objects supporting the typeclass in some container and use it later. I did not think of using contravariance, but it totally makes sense so `Price[Car]` can act as `Price[Prius]`.
eed3si9n
A: 

As @extempore pointed out, what's wrong is that I am confusing implicit conversions (view bounds) and context bounds (both make use of implicit parameters). There's nothing wrong with my generic implicit converters. The problem is that add is using context bounds instead of a view. So we can fix it as follows:

def add [A, B >: A](stuff: A, list: List[(B, Price[_])])(implicit view: A => Price[A]) = (stuff, view(stuff)) :: list
eed3si9n