繁体   English   中英

如何在Scala中表达这种类型?存在类型类(即隐式)限制?

[英]How to express this type in Scala? Existential with type class (ie, implicit) restriction?

我正在使用Play框架的JSON库,它使用类型类来实现Json.toJson函数 (我可能决定使用另一种静态类型较少的技术,比如反射;但是现在我想使用这个库,因为它帮助我学习了Scala类型系统。)

我有一些需要传递给toJson的简单case类,所以我必须为它们中的每一个实现一个隐式的Writes[T]对象。 对于每个类,第一个剪切可能看起来像这样。

// An example class
case class Foo(title: String, lines: List[String])

// Make 'Foo' a member of the 'Writes' typeclass
implicit object FooWrites extends Writes[Foo] {
  def writes(f: Foo) : JsValue = {
    val fields = Seq("title" -> toJson(f.title), 
                     "lines" -> toJson(f.lines))                        
    JsObject(fields)
  }
}  

每个类都有一个类似的隐含值,所以我可以抽象出公共部分,如下所示。 但是这不会编译,因为我不确定如何声明类型。

def makeSimpleWrites[C](fields: (String, C => T??)*) : Writes[C] = {
  new Writes[C] {
    def writes(c: C) : JsValue = {
      val jsFields = fields map { case (name, get) => (name, toJson(get(c)))}
      JsObject(jsFields)
    }
  }
}

implicit val fooWrites : Writes[Foo] = 
    makeSimpleWrites[Foo]("title" -> {_.title}, "lines" -> {_.lines})                                 
implicit val otherWrites ...

问题是我要传递给makeSimpleWrites的类型T 它不能是普通的类型参数,因为fields每个项目的T都不同。 这是一种存在主义类型吗? 我还没有使用其中之一。 语法闪烁......

def makeSimpleWrites[C](fields: (String, C=>T forSome { type T; implicit Writes[T] })*) 

这可能在Scala中吗? 如果是这样,语法是什么?

因为每个字段都有不同的类型,所以每个字段需要一个类型参数。 这是因为要编写这些字段,您需要(隐式)提供相应类型的Writes实例(方法toJson ),并且这些实例是静态解析的。

解决这个问题的一个解决方案是将过程拆分为两部分:一个方法,您为每个字段调用以提取字段访问器并使用相应的WriteS实例打包它(这甚至可以从您的桌面进行隐式转换)已经传递了),并且有一个方法可以获取整个并创建最终的WriteS实例。 像这样的东西(说明性的,未经测试的):

class WriteSFieldAccessor[C,T] private ( val title: String, val accessor: C => Any )( implicit val writes: Writes[T] )

implicit def toWriteSFieldAccessor[C,T:Writes]( titleAndAccessor: (String, C => T) ): WriteSFieldAccessor = {
  new WriteSFieldAccessor[C,T]( titleAndAccessor._1, titleAndAccessor._2 )
}
def makeSimpleWrites[C](fields: WriteSFieldAccessor[C,_]*) : Writes[C] = {
  new Writes[C] {
    def writes(c: C) : JsValue = {
      val jsFields = fields map { f: WriteSFieldAccessor => 
        val jsField = toJson[Any](f.accessor(c))(f.writes.asInstanceOf[Writes[Any]])
        (f.title, jsField)
      }
      JsObject(jsFields)
    }
  }
}

// Each pair below is implicitly converted to a WriteSFieldAccessor  instance, capturing the required information and passing it to makeSimpleWrites
implicit val fooWrites : Writes[Foo] = makeSimpleWrites[Foo]("title" -> {_.title}, "lines" -> {_.lines}) 

有趣的部分是toJson[Any](f.accessor(c))(f.writes..asInstanceOf[Writes[Any]]) 您只需将Any作为静态类型传递, 显式传递(通常是隐式的) Writes实例。

当试图解决限制时,我的第一个解决方案必须写"title" -> {s:Section => s.title}而不是"title" -> {_.title} ,我用它修改了一下,只是一直在scala的推理限制中运行。 因此,我决定尝试从另一个角度解决它,并提出了一个完全不同的解决方案。 这基本上是一个准DSL:

class ExpandableWrites[C]( val fields: Vector[(String, C => Any, Writes[_])] ) extends Writes[C] {
  def and[T:Writes](fieldAccessor: C => T)(fieldName: String): ExpandableWrites[C] = {
    new ExpandableWrites( fields :+ (fieldName, fieldAccessor, implicitly[Writes[T]]) )
  }
  def writes(c: C) : JsValue = {
    val jsFields = fields map { case (name, get, writes) => (name, toJson[Any](get(c))(writes.asInstanceOf[Writes[Any]]) )}
    JsObject(jsFields)
  }
}

class UnaryExpandableWritesFactory[C] {
  def using[T:Writes](fieldAccessor: C => T)(fieldName: String): ExpandableWrites[C] = {
    new ExpandableWrites[C]( Vector( (fieldName, fieldAccessor, implicitly[Writes[T]] ) ) )
  }
}

def makeSimpleWritesFor[C] = new UnaryExpandableWritesFactory[C]

implicit val fooWrites : Writes[Foo] = 
  makeSimpleWritesFor[Foo].using(_.title)("title") .and (_.lines)("lines") .and (_.date)("date")

这个想法是你逐步创建你的Writes实例,并逐一用新的字段来丰富它。 唯一的烦恼是你需要.and分隔符,包括点。 如果没有点(也就是使用中缀表示法),编译器似乎再次混淆并抱怨如果我们只是(_.title)而不是(s:Section => s.title)

截至2015年1月25日,play-json已经有了内置的方式来做你想做的事情:

import play.api.libs.json._
import play.api.libs.functional.syntax._

sealed case class Foo(title: String, lines: List[String])  // the `sealed` bit is not relevant but I always seal my ADTs

implicit val fooWrites = (
  (__ \ "title").write[String] ~
  (__ \ "lines").write[List[String]]
)(unlift(Foo.unapply))

事实上,这也适用于Reads[T]

implicit val fooReads = (
  (__ \ "title").read[String] ~
  (-- \ "lines").read[List[String]]
)(Foo.apply _)

Format[T]

implicit val fooFormat = (
  (__ \ "title").format[String] ~
  (-- \ "lines").format[List[String]]
)(Foo.apply _, unlift(Foo.unapply))

你也可以应用变换,例如:

implicit val fooReads = (
  (__ \ "title").read[String].map(_.toLowerCase) ~
  (-- \ "lines").read[List[String]].map(_.filter(_.nonEmpty))
)(Foo.apply _)

甚至是双向变换:

implicit val fooFormat = (
  (__ \ "title").format[String].inmap(_.toLowerCase, _.toUpperCase) ~
  (-- \ "lines").format[List[String]]
)(Foo.apply _, unlift(Foo.unapply))

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM