Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala: Generic implicit converters?

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:

  • [scala] Generic implicit converters?
like image 748
Eugene Yokota Avatar asked Oct 01 '10 23:10

Eugene Yokota


People also ask

What are Scala implicit conversions?

Implicit conversions in Scala are the set of methods that are apply when an object of wrong type is used. It allows the compiler to automatically convert of one type to another. Implicit conversions are applied in two conditions: First, if an expression of type A and S does not match to the expected expression type B.

Where does Scala look for Implicits?

Scala will first look for implicit definitions and implicit parameters that can be accessed directly (without a prefix) at the point the method with the implicit parameter block is called. Then it looks for members marked implicit in all the companion objects associated with the implicit candidate type.

How do you use implicit conversions?

An implicit conversion from type S to type T is defined by an implicit value which has function type S => T , or by an implicit method convertible to a value of that type. Implicit conversions are applied in two situations: If an expression e is of type S , and S does not conform to the expression's expected type T .

What is an implicit class in Scala?

An implicit class is a class marked with the implicit keyword. This keyword makes the class's primary constructor available for implicit conversions when the class is in scope. Implicit classes were proposed in SIP-13.


2 Answers

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
// 
like image 191
psp Avatar answered Oct 14 '22 12:10

psp


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
}
like image 33
Tom Crockett Avatar answered Oct 14 '22 12:10

Tom Crockett