Scala中抽象类型参数的隐式转换

时间:2013-01-13 22:26:01

标签: scala playframework playframework-2.0

我是Scala的新手,探索隐式转换和着名蛋糕模式的可能性。我尝试创建模型类,其id值列为抽象类型,以避免泄漏实现细节。我也把它混合成蛋糕模式特质包装。一切正常,除了从id到JSON的隐式转换(在Play框架内)。无论我做什么,Scala编译器都找不到隐式转换。

以下是重现问题的代码:

import anorm._
import play.api.libs.json._

trait Card {
  type Key
  val NoId: Key
  val id: Key
  val name: String
}

trait CardModelComponent {
  val cardModel: CardModel
  trait CardModel {
    def findById(id: Long): Option[Card]
    def findAll: Seq[Card]
    def delete(id: Long)
    def create(name: String): Option[Card]
  }
}

trait CardModelComponentImpl extends CardModelComponent {
  case class CardImpl(id: Pk[Long], name: String) extends Card {
    type Key = Pk[Long]

    object Key extends Writes[Key] {
      implicit def writes(key: Key): JsValue = {
        key match {
          case Id(idVal: Long) => JsNumber(idVal)
          case _ => JsNull
        }
      }
    }
    val NoId = NotAssigned
  }

  class CardModelImpl extends CardModel {
    def findById(id: Long): Option[Card] = { None }
    def findAll: Seq[Card] = { Seq(CardImpl(Id(1), "Some card"))}
    def delete(id: Long) {}
    def create(name: String): Option[Card] = { Some(CardImpl(Id(1), name)) }
  }
}

object ComponentsRegistry extends
CardModelComponentImpl {

  val cardModel = new CardModelImpl
}

val card = ComponentsRegistry.cardModel.create("Test card").get
Json.toJson(card.id)

我得到的错误输出如下:

> card: Card = CardImpl(1,Test card)
> <console>:19: error: No Json deserializer found for type card.Key. Try to implem
  ent an implicit Writes or Format for this type.
                Json.toJson(card.id)
                           ^

有什么方法可以让它发挥作用吗?看起来像蛋糕模式包装会从编译器中隐藏太多类型信息,我想从card.Key类型名称开始。

我还尝试直接为Pk创建Writer实现,结果出现相同的错误。

3 个答案:

答案 0 :(得分:2)

Writes[Key]实例应该是隐式的,而不是writes()成员方法。隐式实例也必须在调用toJson()方法的范围内。

答案 1 :(得分:1)

就像Jesper Nordenberg写的那样,你必须将Writes [Key]的实例纳入范围。这样做的一种方法是要求Card的实现发布Writer实例,然后导入你调用json的那个,如下所示:

...

trait Card {
  type Key

  val NoId: Key
  val id: Key
  val name: String
  implicit val Key: Writes[Key]
}

...

trait CardModelComponentImpl extends CardModelComponent {
  case class CardImpl(id: Pk[Long], name: String) extends Card {
    type Key = Pk[Long]

    implicit object Key extends Writes[Key] {
      def writes(key: Key): JsValue = {
        key match {
          case Id(idVal: Long) => JsNumber(idVal)
          case _ => JsNull
        }
      }
    }

    val NoId = NotAssigned
  }

  class CardModelImpl extends CardModel {
    def findById(id: Long): Option[Card] = { None }
    def findAll: Seq[Card] = { Seq(CardImpl(Id(1), "Some card"))}
    def delete(id: Long) {}
    def create(name: String): Option[Card] = { Some(CardImpl(Id(1), name)) }
  }
}

...

val card = ComponentsRegistry.cardModel.create("Test card").get
import card.Key
Json.toJson(card.id)

或明确传递:

val card = ComponentsRegistry.cardModel.create("Test card").get
Json.toJson(card.id)(card.Key)

答案 2 :(得分:1)

我稍微修改了你的代码。我希望这对你有所帮助。

package models
import anorm._
import play.api.libs.json._

trait Card[T] {
  val NoId: T
  val id: T
  val name: String
}

trait CardModelComponent[T] {
  val cardModel: CardModel
  trait CardModel {
    def findById(id: Long): Option[Card[T]]
    def findAll: Seq[Card[T]]
    def delete(id: Long)
    def create(name: String): Option[Card[T]]
  }
}

trait CardModelComponentImpl extends CardModelComponent[Pk[Long]] {
  type Key = Pk[Long]
  case class CardImpl(id: Key, name: String) extends Card[Key] {
    val NoId = NotAssigned
  }


  class CardModelImpl extends CardModel {
    def findById(id: Long): Option[Card[Key]] = { None }
    def findAll: Seq[Card[Key]] = { Seq(CardImpl(Id(1), "Some card"))}
    def delete(id: Long) {}
    def create(name: String): Option[Card[Key]] = { Some(CardImpl(Id(1), name)) }
  }
}

object ComponentsRegistry extends CardModelComponentImpl {
  val cardModel = new CardModelImpl
  implicit object KeyWrites extends Writes[Key] {
    def writes(key: Key): JsValue = {
      key match {
        case Id(idVal: Long) => JsNumber(idVal)
        case _ => JsNull
      }
    }
  }
}

然后你就可以这样使用:

import models.ComponentsRegistry
import models.ComponentsRegistry.KeyWrites
val card = ComponentsRegistry.cardModel.create("Test card").get
Json.toJson(card.id)