Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Serialize arbitrary computations and run them on a different machine while keeping type safety
import shapeless._
import shapeless.record._
import shapeless.ops.record._
import syntax.singleton._
object Main extends App {
// === Your normal code base =========================================================================================
case class User(name: String)
val plusOne: Int => Int =
_ + 1
val intToString: Int => String =
_.toString
val toUser: String => User =
User.apply
val deserializeInt: String => Int =
_.toInt
// === Generated by macros ===========================================================================================
// The hash of the syntax tree of the function
val PlusOneS = "000"
val PlusOne = PlusOneS.narrow
val IntToStringS = "001"
val IntToString = IntToStringS.narrow
val ToUserS = "002"
val ToUser = ToUserS.narrow
val DeserializeIntS = "003"
val DeserializeInt = DeserializeIntS.narrow
// Code database with implementations
val sourceDB =
(PlusOneS ->> plusOne) ::
(IntToStringS ->> intToString) ::
(ToUserS ->> toUser) ::
(DeserializeIntS ->> deserializeInt) ::
HNil
// Code database with type information only
val interfaceDB =
(PlusOneS ->> (((_: Int) => ???): Int => Int)) ::
(IntToStringS ->> (((_: Int) => ???): Int => String)) ::
(ToUserS ->> (((_: String) => ???): String => User)) ::
(DeserializeIntS ->> (((_: String) => ???): String => Int)) ::
HNil
// Context with implementations (node 1)
val context = Remote(sourceDB)
// Context with interface only (node 2)
val interfaceContext = Remote(interfaceDB)
// === Node 2 ========================================================================================================
// Compose program by only using the interface with type information (type safe)
val program: interfaceContext.RemoteFun[String, User] = interfaceContext
.start(DeserializeInt)
.andThen(PlusOne)
.andThen(IntToString)
.andThen(ToUser)
// This will not compile
//val program2: context.RemoteFun[Int, String] = context.start(PlusOne)
// Transform the computation with input "10" into a string
val serialized: String = program.serialize("10")
println(serialized)
// === Node 1 ========================================================================================================
// Run a computation represented by a string using the code database with implementations
val r2: User = context.unsafeCompute[User](serialized)
println(r2)
// === Implementation using dependent types ==========================================================================
case class Remote[SourceDB <: HList](sourceDB: SourceDB)(implicit ev0: ToMap.Aux[SourceDB, String, Any]) {
val unsafeDB: Map[String, Any] = sourceDB.toMap[String, Any]
type Member[H, A0, B0] = Selector.Aux[SourceDB, H, A0 => B0]
type MemberAux[H, O] = Selector.Aux[SourceDB, H, O]
def start[H, O, A, B](hash: H)(implicit m0: MemberAux[H, O], eq: O <:< (A => B), m1: Member[H, A, B]): RemoteFun[A, B] =
Lift[H, A, B](hash, m1)
def unsafeCompute[E](str: String): E =
unsafeComputeR(str, identity).asInstanceOf[E]
private val FunReg = """^([a-z|0-9|A-Z]{3})\|\|(.+)$""".r
private val LastFunReg = """^([a-z|0-9|A-Z]{3})(\|<<\|.+)$""".r
private val AppReg = """^\|<<\|(.+)$""".r
private def unsafeComputeR(hashString: String, last: Any => Any): Any = {
hashString match {
case FunReg(hash, rest) => unsafeComputeR(rest, unsafeDB(hash).asInstanceOf[Any => Any] compose last)
case LastFunReg(hash, rest) => unsafeComputeR(rest, unsafeDB(hash).asInstanceOf[Any => Any] compose last)
case AppReg(input) => last(input.asInstanceOf[Any])
case other => throw new Exception(other + " is not a valid program")
}
}
sealed trait RemoteFun[A, B] {
def andThen[H, O, C](hash: H)(implicit m0: MemberAux[H, O], eq: O <:< (B => C), m1: Member[H, B, C]): RemoteFun[A, C] =
AndThen[H, A, B, C](hash, this, m1)
def compile: A => B =
this match {
case Lift(_, f) => f(sourceDB)
case AndThen(_, f, g) => g(sourceDB) compose f.compile
}
def serialize(input: String)(implicit ev0: A <:< String): String =
serializeR(input)
def serializeR(input: String): String =
(this, input) match {
case (Lift(hash, _), AppReg(input0)) =>
hash.toString + "|<<|" + input0
case (Lift(hash, _), LastFunReg(hash0, rest0)) =>
hash + "||" + hash0 + rest0
case (Lift(hash, _), FunReg(hash0, rest0)) =>
hash + "||" + hash0 + "||" + rest0
case (AndThen(hash, f, _), AppReg(input0)) =>
f.serializeR(hash.toString + "|<<|" + input0)
case (AndThen(hash, f, _), LastFunReg(hash0, input0)) =>
f.serializeR(hash.toString + "||" + hash0 + input0)
case (AndThen(hash, f, _), FunReg(hash0, input0)) =>
f.serializeR(hash.toString + "||" + hash0 + "||" + input0)
case (_, initHash) =>
serializeR("|<<|" + initHash)
}
}
case class Lift[H, A, B](hash: H, member: Member[H, A, B]) extends RemoteFun[A, B]
case class AndThen[H, A, B, C](hash: H, f: RemoteFun[A, B], member: Member[H, B, C]) extends RemoteFun[A, C]
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.