Why it doesn't work?
The reduce method of type Seq
has a following declaration:
reduce[A1 >: A](op: (A1, A1) => A1): A1
That means reducing function op
needs to have a type
(A1, A1) => A1)
For some A1
. Now let us look at the types of intermediate results of the reduction we wanted to perform (x is a value of type Reads[String]
)
x and x : FunctionalBuilder[Reads]#CanBuild2[String,String]
x and x and x : FunctionalBuilder[Reads]#CanBuild3[String,String,String]
You can find these types defined here: http://www.playframework.com/documentation/api/2.1.x/scala/index.html#play.api.libs.functional.FunctionalBuilder
The fact is, that and
method does not have a nice polymorphic type reduce method needs, so we need to find ourselves some other method to solve our problem.
This problem is a very similar analogue to a problem of processing a tuple instead of a list - there is no very easy way to write a function that handles arbitrary length tuples generically. But still, there is a lot of things we can do.
Using implicits
One way to solve this problem is by using specialized implicit declaration for every size of tuple. If you don't have to handle a lot of different cases, it is a nice and clean idiomatic scala solution:
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)
}
}
In the above code we create a different reducer for different tuple size, inject it as an implicit argument to our make
function and as a last step use map to make Reads[(String,String,String)]
a Reads[BetterNamed]
.
Here is a usage example:
import TupleReducer._
implicit val read : Reads[BetterNamed] = TupleReducer.make(
("fieldA", "fieldB", "fieldC"),
BetterNamed.apply _ tupled
)
Trickier way
But what if we really want the freedom of Lists (or other sequence types, I justed used lists as an example)? Idea how to do that is taken from this stackoverflow thread: Is there way to create tuple from list(without codegeneration)? from which I have borrowed method for creating tuple from list in a generic way. Armed with that gear we can write 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))
}
}
This code divides the problem into several steps:
- Firstly, it creates
List[Reads[String]]
from the list of string, extracting each field separately in the methodmap
- Next, it creates
Reads[List[String]]
fromList[Reads[String]]
applying each of the reads separately and merging results. - It uses
toTuple
method to pack theList[String]
into the corresponding tuple typle, that can be fed into BetterNamed.apply function that constructs BetterNamed object.
Here is a usage example:
implicit val read = ListReducer.make(
List("fieldA", "fieldB", "fieldC"),
BetterNamed.apply _ tupled
)
I hope that helps.