Reputation: 1887
I have this code that will repeat multiple times (more than 15 times) across my app.
val entityList = params.map(paramPair => {
//THIS LINE DEPENDS ON THE ENTITY
val entityX = new Entity1
paramPair.foreach { case (key, value) => (key, value) match {
//THIS BLOCK CHANGES DEPENDING THE ENTITY
case (KEY1, v: TYPE1) => entityX setX v
case (KEY2, v: TYPE2) => entityX setY v
...
//END BLOCK
case _ =>
}
}
entityX
})
The only code that changes are the type of entity and the pattern matching case clauses. Is there a way to create a function that receive those cases to avoid code repetition?
Upvotes: 0
Views: 522
Reputation: 39577
Just for fun. Because the point is to have fun, right?
Since I forget how to use mutable fields, I prefer to construct the immutable Dog.
Impersonator could extend AnyVal by refactoring the PartialFunction (since the anonymous nested class is not permitted for AnyVal, "This restriction is planned to be removed in subsequent releases.")
package populate
import scala.reflect._
import scala.reflect.runtime.{ currentMirror => cm }
import scala.reflect.runtime.universe._
import java.util.{ Calendar, Date }
case class Person(var name: String, var birth: Date) { def this() = this(null,null) }
case class Book(var title: String, var pub: Date) { def this() = this(null,null) }
case class Dog(val name: String, val rabies: Date, val goodBoy: Boolean = true)
object Test extends App {
def dateOf(y:Int,m:Int,d:Int) = { val c = Calendar.getInstance; c.set(1974, 2, 14); c.getTime }
val input = Seq("id" -> "Sybil", "n" -> dateOf(1974, 2, 14), "garbage" -> "error")
trait Completer[A] extends Any {
def filler: PartialFunction[A, Unit]
def complete(info: Seq[A]) = info foreach (filler orElse {
case (k, v) => println(s"Can't use $k -> $v")
})
}
type Info = (String, Any)
implicit class Impersonator(val p: Person) extends Completer[Info] {
override def filler = {
case ("id", s: String) => p.name = s
case ("n", d: Date) => p.birth = d
}
}
implicit class Booker(val b: Book) extends Completer[Info] {
override def filler = {
case ("id", s: String) => b.title = s
case ("n", d: Date) => b.pub = d
}
}
def personify(p: Person, info: Seq[(String, Any)]) = info foreach {
case ("id", s: String) => p.name = s
case ("n", d: Date) => p.birth = d
case (k, v) => println(s"Can't use $k -> $v")
}
def bookish(b: Book, info: Seq[(String, Any)]) = info foreach {
case ("id", s: String) => b.title = s
case ("n", d: Date) => b.pub = d
case (k, v) => println(s"Can't use $k -> $v")
}
def inject[A: ClassTag](a: A, info: Seq[(String, Any)]): A = {
implicitly[ClassTag[A]] match {
case ClassTag(k) if k == classOf[Person] => personify(a.asInstanceOf[Person], info)
//case ClassTag(k) if k == classOf[Book] => bookish(classOf[Book] cast a, info)
case ClassTag(k) if k == classOf[Book] => a.asInstanceOf[Book] complete info
case k => println(s"Unknown $k")
}
a
}
def entity[A: ClassTag](info: Seq[(String, Any)]): A = {
val e = implicitly[ClassTag[A]].runtimeClass.newInstance.asInstanceOf[A]
inject(e, info)
}
val v = entity[Person](input)
println(v)
Console println entity[Book](input)
Console println Defaulter((input map {
case ("id", v) => ("name", v)
case ("n", v) => ("rabies", v)
case p => p
}).toMap).newCase[Dog]
}
case class Defaulter(input: Map[String, Any]) {
def newCase[A]()(implicit t: ClassTag[A]): A = {
val claas = cm classSymbol t.runtimeClass
val modul = claas.companionSymbol.asModule
val im = cm reflect (cm reflectModule modul).instance
defaut[A](im, "apply")
}
def defaut[A](im: InstanceMirror, name: String): A = {
val at = newTermName(name)
val ts = im.symbol.typeSignature
val method = (ts member at).asMethod
// either defarg or default val for type of p
def valueFor(p: Symbol, i: Int): Any = {
val defarg = ts member newTermName(s"$name$$default$$${i+1}")
if (input contains p.name.toString) {
input(p.name.toString)
} else if (defarg != NoSymbol) {
println(s"default $defarg")
(im reflectMethod defarg.asMethod)()
} else {
println(s"def val for $p")
p.typeSignature match {
case t if t == typeOf[String] => null
case t if t == typeOf[Int] => 0
case t if t == typeOf[Date] => new Date(0L)
case x => throw new IllegalArgumentException(x.toString)
}
}
}
val args = (for (ps <- method.paramss; p <- ps) yield p).zipWithIndex map (p => valueFor(p._1,p._2))
(im reflectMethod method)(args: _*).asInstanceOf[A]
}
}
Upvotes: 1
Reputation: 62835
You can turn
paramPair.foreach { case (key, value) => (key, value) match {
//THIS BLOCK CHANGES DEPENDING THE ENTITY
case (KEY1, v: TYPE1) => entityX setX v
case (KEY2, v: TYPE2) => entityX setY v
...
//END BLOCK
case _ =>
}
}
into
paramPair.foreach { case (key, value) =>
case (KEY1, v: TYPE1) => entityX setX v
case (KEY2, v: TYPE2) => entityX setY v
...
//END BLOCK
case _ =>
}
}
If you are ok with anonymous classes, you can go further with converting whole code into:
val entityList = params.map(paramPair => {
new Entity1 {
paramPair.foreach { case (key, value) =>
//THIS BLOCK CHANGES DEPENDING THE ENTITY
case (KEY1, v: TYPE1) => setX v
case (KEY2, v: TYPE2) => setY v
...
//END BLOCK
case _ =>
}
}})
If block changes depending on entity you may, as @pst said, turn into function using so called function literal: expression with curly braces and case statements will be turned into PartialFunction, e.g.
val fillEntitityOne: PartialFunction[A,B] = {
case (KEY1, v: TYPE1) => entityX setX v
case (KEY2, v: TYPE2) => entityX setY v
}
Where A and B input and return types, e.g.
val foobar: Int => String = { case 1 => "1" }
That's why you can ommit match part in second snippet: foreach expects Function instance (and PartialFunction inherits from Function), of course with some types, so actually we can desugar
.foreach {
case x =>
}
into
.foreach({
case x =>
})
and then
val anon = new PartialFunction[A,B]{....}
.foreach(anon)
Upvotes: 1