为什么它不起作用?
类型的 reduce 方法Seq
有如下声明:
reduce[A1 >: A](op: (A1, A1) => A1): A1
这意味着减少功能op
需要有一个类型
(A1, A1) => A1)
对于一些A1
. 现在让我们看看我们想要执行的归约的中间结果的类型(x 是 type 的值Reads[String]
)
x and x : FunctionalBuilder[Reads]#CanBuild2[String,String]
x and x and x : FunctionalBuilder[Reads]#CanBuild3[String,String,String]
您可以在此处找到定义的这些类型:http ://www.playframework.com/documentation/api/2.1.x/scala/index.html#play.api.libs.functional.FunctionalBuilder
事实是,该and
方法没有很好的多态类型 reduce 方法的需求,所以我们需要自己找一些其他的方法来解决我们的问题。
这个问题与处理元组而不是列表的问题非常相似——没有非常简单的方法可以编写一个通用地处理任意长度元组的函数。但是,我们仍然可以做很多事情。
使用隐式
解决这个问题的一种方法是对每个大小的元组使用专门的隐式声明。如果您不必处理很多不同的情况,这是一个不错且干净的惯用 scala 解决方案:
trait Reducer[T] {
def reduce(t : T) : Reads[T]
}
object TupleReducer {
implicit val reducer1 = new Reducer[String] {
def reduce(s : String) = (__ \ s).read[String]
}
implicit val reducer2 = new Reducer[(String, String)] {
def reduce(s : (String, String)) = (
(__ \ s._1).read[String] and
(__ \ s._2).read[String]
).tupled
}
implicit val reducer3 = new Reducer[(String, String, String)] {
def reduce(s : (String, String, String)) = (
(__ \ s._1).read[String] and
(__ \ s._2).read[String] and
(__ \ s._3).read[String]
).tupled
}
def make[T, Res](t : T, f : T => Res)(implicit reducer : Reducer[T]) = {
reducer.reduce(t).map(f)
}
}
在上面的代码中,我们为不同的元组大小创建了一个不同的 reducer,将其作为隐式参数注入我们的make
函数,最后一步使用 map 来创建Reads[(String,String,String)]
一个Reads[BetterNamed]
.
这是一个使用示例:
import TupleReducer._
implicit val read : Reads[BetterNamed] = TupleReducer.make(
("fieldA", "fieldB", "fieldC"),
BetterNamed.apply _ tupled
)
更棘手的方式
但是,如果我们真的想要 Lists(或其他序列类型,我刚刚以列表为例)的自由呢?如何做到这一点的想法取自这个stackoverflow线程:Is there way to create tuple from list(without codegeneration)? 我从中借用了以通用方式从列表中创建元组的方法。有了这个装备,我们可以写ListReducer
:
object ListReducer {
def map(xs : List[String]) : List[Reads[String]] = xs.map(x => (__ \ x).read[String])
def reduce(xs : List[Reads[String]]) : Reads[List[String]] = xs match {
case Nil => Reads[List[String]] { _ => JsSuccess(Nil) }
case (head::rest) => for {
h <- head
r <- reduce(rest)
} yield(h::r)
}
def toTuple[A <: Object,Product](as:List[A]) : Product = {
val tupleClass = Class.forName("scala.Tuple" + as.size)
tupleClass.getConstructors.apply(0).newInstance(as:_*).asInstanceOf[Product]
}
def createTransformer[X,Y](f : X => Y)(xs : List[String]) : Y = {
f(toTuple[String, X](xs))
}
def make[X,Y](xs : List[String], fn : X => Y) : Reads[Y] = {
reduce(map(xs)).map(createTransformer[X,Y](fn))
}
}
这段代码将问题分为几个步骤:
- 首先,它
List[Reads[String]]
从字符串列表中创建,在方法中分别提取每个字段map
- 接下来,它
Reads[List[String]]
通过List[Reads[String]]
分别应用每个读取和合并结果来创建。
- 它使用
toTuple
方法将其打包List[String]
成相应的元组类型,可以将其输入到构造 BetterNamed 对象的 BetterNamed.apply 函数中。
这是一个使用示例:
implicit val read = ListReducer.make(
List("fieldA", "fieldB", "fieldC"),
BetterNamed.apply _ tupled
)
我希望这会有所帮助。