Scala: общие неявные преобразователи?

Я хотел бы определить общий неявный преобразователь, который работает для всех подтипов типа T. Например:

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) }

Приведенный выше код выдает ошибку:

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

Что я делаю неправильно?

Обновление:

Как указал @extempore, проблема в том, что я путаю неявные преобразования ( границы представления) и границы контекста (оба используют неявные параметры). В моих общих неявных преобразователях нет ничего плохого. Проблема в том, что add использует границы контекста вместо представления. Итак, мы можем исправить это следующим образом:

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

Интересная вещь, которую @extempore демонстрирует в своем коде, заключается в том, что нам действительно не нужен общий преобразователь, если Price[A] был контравариантным. По сути, я могу заставить Price[Car] работать от имени Price[Prius], чего я и хотел. Таким образом, альтернативная версия с привязкой к контексту:

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
}

Связанные:


person Eugene Yokota    schedule 01.10.2010    source источник


Ответы (3)


Не очень понятно, чего вы на самом деле хотите. Вы действительно смешиваете неявные преобразования и неявные параметры. Вместо того, чтобы пытаться разобраться, я написал код.

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
// 
person psp    schedule 02.10.2010
comment
Я хочу 1) иметь дело с неявным преобразованием для семейства типов за один раз 2) хранить различные объекты, поддерживающие класс типов, в каком-то контейнере и использовать его позже. Я не думал об использовании контравариантности, но в этом есть смысл, так что Price[Car] может действовать как Price[Prius]. - person Eugene Yokota; 02.10.2010

Проблема в том, что вы определили свои неявные carToPrice и foodToPrice как неявные методы от значений Car и Food до Prices, но нет значений Car и Food, где вам нужны преобразования. Поскольку вы на самом деле не используете аргументы в этих неявных методах, я думаю, что вам действительно нужны неявные значения, например:

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
}
person Tom Crockett    schedule 01.10.2010
comment
Однако включение еды в конверсию, похоже, не решает эту проблему: неявное определение def foodToPrice[A ‹: Food](food: A): Price[A] = new Price[A] { def price(a: Any) = food match { case Chicken: FriedChicken =› 2 case _ =› 1 } } - person Eugene Yokota; 02.10.2010
comment
Я предлагаю, чтобы вы не зависели от аргумента о еде (я прокомментировал это в своем ответе). - person Tom Crockett; 02.10.2010
comment
Если вы хотите сопоставить шаблон, сделайте это с аргументом price. - person Tom Crockett; 02.10.2010
comment
Я вижу, что ваша версия работает, но мне только непонятно, чем моя отличается от implicit def intToString(x: Int) = x.toString. - person Eugene Yokota; 02.10.2010
comment
Я добавил то, что, по моему мнению, происходит в конце вопроса. - person Eugene Yokota; 02.10.2010

Как указал @extempore, проблема в том, что я путаю неявные преобразования ( границы представления) и границы контекста (оба используют неявные параметры). В моих общих неявных преобразователях нет ничего плохого. Проблема в том, что add использует границы контекста вместо представления. Итак, мы можем исправить это следующим образом:

def add [A, B >: A](stuff: A, list: List[(B, Price[_])])(implicit view: A => Price[A]) = (stuff, view(stuff)) :: list
person Eugene Yokota    schedule 02.10.2010