Skip to content

Instantly share code, notes, and snippets.

@mrange
Last active June 16, 2018 13:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mrange/1b725ddd2c4598e740ac9e167d962222 to your computer and use it in GitHub Desktop.
Save mrange/1b725ddd2c4598e740ac9e167d962222 to your computer and use it in GitHub Desktop.
Scala Transform
import scala.reflect._
sealed trait TransformResult[+T]
case class TransformContext(path: List[String]) {
def append(p: String) = TransformContext(p::path)
override def toString = path.reverse.mkString(".")
}
object TransformContext {
val empty = TransformContext(List())
}
case class Success[+T](v: T) extends TransformResult[T]
case class Failure(context: TransformContext, message: String) extends TransformResult[Nothing]
trait Transform[+T] {
def run(context: TransformContext, current: Any): TransformResult[T]
def flatMap[U](uf: (T) => Transform[U]) = Transform.flatMap(this, uf)
def map[U](m: T => U) = Transform.map(this, m)
def and[U](u: Transform[U]) = Transform.and(this, u)
def named(name: String) = Transform.named(this, name)
def optional = Transform.optional(this)
def transform(m: Map[String, Any]): TransformResult[T] = run(TransformContext.empty, m)
}
object Implicits {
implicit class ImplicitFlattenTransform[T](val t: Transform[Transform[T]]) extends AnyVal {
def flatten = Transform.flatten(t)
}
implicit class ImplicitOrTransform[T](val f: Transform[T]) extends AnyVal {
def or(s: Transform[T]) = Transform.or(f, s)
}
implicit class ImplicitApplyTransform[T, U](val f: Transform[T => U]) extends AnyVal {
def apply(t: Transform[T]) = Transform.apply(f, t)
}
}
object Transform {
def create[T](m: (TransformContext, Any) => TransformResult[T]) = new Transform[T] {
def run(context: TransformContext, current: Any): TransformResult[T] = {
m(context, current)
}
}
def value[T](v: T) = new Transform[T] {
def run(context: TransformContext, current: Any): TransformResult[T] = {
Success(v)
}
}
def failWith(message: String) = new Transform[Nothing] {
def run(context: TransformContext, current: Any): TransformResult[Nothing] = {
Failure(context, message)
}
}
def flatMap[T, U](t: Transform[T], uf: (T) => Transform[U]) = new Transform[U] {
def run(context: TransformContext, current: Any): TransformResult[U] = {
t.run(context, current) match {
case Success(tv) =>
val u = uf(tv)
u.run(context, current)
case Failure(tctx, tmsg) => Failure(tctx, tmsg)
}
}
}
def flatten[T](t: Transform[Transform[T]]) = t.flatMap(tv => tv)
def apply[T, U](f: Transform[T => U], t: Transform[T]) = f.flatMap(fv => t.flatMap(tv => Transform.value(fv(tv))))
def map[T, U](t: Transform[T], m: T => U) = t.flatMap(tv => value(m(tv)))
def and[T, U](t: Transform[T], u: Transform[U]) = t.flatMap(tv => u.flatMap(uv => value((tv, uv))))
def or[T](f: Transform[T], s: Transform[T]) = new Transform[T] {
def run(context: TransformContext, current: Any): TransformResult[T] = {
f.run(context, current) match {
case Success(tv) => Success(tv)
case Failure(tctx, tmsg) => s.run(context, current)
}
}
}
def optional[T](t: Transform[T]) = new Transform[Option[T]] {
def run(context: TransformContext, current: Any): TransformResult[Option[T]] = {
t.run(context, current) match {
case Success(tv) => Success(Some(tv))
case Failure(_, _) => Success(None)
}
}
}
def named[T](t: Transform[T], name: String) = new Transform[T] {
def run(context: TransformContext, current: Any): TransformResult[T] = {
current match {
case null => Failure(context, "current is null")
case m : Map[String,_] =>
m.get(name) match {
case Some(next) =>
val nctx = context.append(name)
t.run(nctx, next)
case None => Failure(context, s"Map does not contain key: $name")
}
case _ => Failure(context, "current is not a Map")
}
}
}
def cast[T : ClassTag] = new Transform[T] {
def run(context: TransformContext, current: Any): TransformResult[T] = {
current match {
case tv : T => Success(tv)
case _ => Failure(context, s"Cannot cast current to T") // TODO: Improve
}
}
}
val asString = new Transform[String] {
def run(context: TransformContext, current: Any): TransformResult[String] = {
current match {
case null => Success("")
case s : String => Success(s)
case v => Success(v.toString)
}
}
}
val asNumber = new Transform[Number] {
def run(context: TransformContext, current: Any): TransformResult[Number] = {
current match {
case null => Success(0)
case n : Number => Success(n)
case _ => Failure(context, "Not a number")
}
}
}
}
case class MyInnerModel(xx: Either[Int, String])
case class MyModel(x: Number, y: String,z: MyInnerModel)
import Implicits._
object Program {
def main(args: Array[String]): Unit = {
def intVal(v: Int): Either[Int, String] = Left(v)
def stringVal(v: String): Either[Int, String] = Right(v)
def string(key: String) = Transform.asString.named(key)
def number(key: String) = Transform.asNumber.named(key)
def either(key: String) = Transform.cast[Int].map(intVal(_))
.or(Transform.cast[String].map(stringVal(_)))
.or(Transform.failWith("Expected either int or string"))
.named(key)
def map(kvs: (String, Any)*) = Map(kvs:_*)
val tmyInnermodel = either("xx")
.map(MyInnerModel(_))
val tmyModel1 = Transform
.value(MyModel.curried)
.apply(number("x"))
.apply(string("y"))
.apply(tmyInnermodel.named("z"))
val tmyModel3 = Transform
.value(MyModel.curried)
.apply(number("x"))
.apply(string("y"))
.apply(Transform.value(MyInnerModel(Left(0))))
val versions = Map(1 -> tmyModel1, 3 -> tmyModel3)
def tmyModel = Transform.cast[Int].flatMap { v =>
versions.get(v) match {
case Some(vv) => Transform.value(vv)
case None => Transform.failWith(s"Unrecognized version: $v")
}
}.named("v").flatten
def test(map: Map[String, Any]) = {
val result = tmyModel.transform(map)
println(map)
println(result)
}
test(map(
"v" -> 1,
"x" -> 1,
"y" -> "a",
"z" -> map("xx" -> 11)))
test(map(
"v" -> 1,
"x" -> 1,
"y" -> "a",
"z" -> map("xx" -> "XX")))
test(map(
"v" -> 1,
"x" -> 1,
"y" -> "a",
"z" -> map("xx" -> true)))
test(map(
"v" -> 1,
"x" -> 1,
"y" -> "a",
"z" -> map()))
test(map(
"v" -> 1,
"x" -> 1,
"y" -> "a",
"z" -> 1))
test(map(
"v" -> 1,
"x" -> 1,
"y" -> "a"))
test(map(
"v" -> 1,
"x" -> "a",
"y" -> "a"))
test(map(
"v" -> 2))
test(map(
"v" -> ""))
test(map(
"v" -> 3,
"x" -> 1,
"y" -> "a"))
test(map())
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment