Skip to content

Instantly share code, notes, and snippets.

@blast-hardcheese
Last active November 20, 2018 00:54
Show Gist options
  • Select an option

  • Save blast-hardcheese/7e506b9618d13f3ae336f5a37807b1ac to your computer and use it in GitHub Desktop.

Select an option

Save blast-hardcheese/7e506b9618d13f3ae336f5a37807b1ac to your computer and use it in GitHub Desktop.
import cats._, cats.arrow._, cats.data._, cats.free._, cats.implicits._
import scala.meta.{ Import, Source }
package object foo {
type Target[T] = Either[String, T]
val Target = Monad[Target]
}
package foo {
// A structure that will contain all types that will be known about
trait Abstraction {
type Include
type Function
type Module
}
// A base language that is not type-parameterized by `Abstraction`
sealed trait LanguageStuff[A]
case class RenderFile(content: String) extends LanguageStuff[String]
class LanguageStuffs[F[_]](implicit I: InjectK[LanguageStuff, F]) {
def renderFile(content: String): Free[F, String] =
Free.inject[LanguageStuff, F](RenderFile(content))
}
object LanguageStuffs {
implicit def languageStuffTerms[F[_]](implicit I: InjectK[LanguageStuff, F]): LanguageStuffs[F] =
new LanguageStuffs[F]
}
// "Model" algebra, parameterized by `Abstraction`. `L` means "Language" in some sense.
sealed trait ModelProtocolTerm[L <: Abstraction, A]
case class InterpolateFile[L <: Abstraction](xs: List[L#Include]) extends ModelProtocolTerm[L, L#Module]
class ModelProtocolTerms[L <: Abstraction, F[_]](implicit I: InjectK[ModelProtocolTerm[L, ?], F]) {
def interpolateFile(xs: List[L#Include]): Free[F, L#Module] =
Free.inject[ModelProtocolTerm[L, ?], F](InterpolateFile[L](xs))
}
object ModelProtocolTerms {
implicit def modelProtocolTerms[L <: Abstraction, F[_]](implicit I: InjectK[ModelProtocolTerm[L, ?], F]): ModelProtocolTerms[L, F] =
new ModelProtocolTerms[L, F]
}
trait ModelProtocolTermAbstraction[L <: Abstraction] {
def ModelInterp: (ModelProtocolTerm[L, ?] ~> Target)
}
// Similar to Model abstraction, this is a Client abstraction, parameterized by language.
sealed trait ClientTerm[L <: Abstraction, A]
case class GetImports[L <: Abstraction]() extends ClientTerm[L, List[L#Include]]
class ClientTerms[L <: Abstraction, F[_]](implicit I: InjectK[ClientTerm[L, ?], F]) {
def getImports(): Free[F, List[L#Include]] =
Free.inject[ClientTerm[L, ?], F](GetImports[L])
}
object ClientTerms {
implicit def clientTerms[L <: Abstraction, F[_]](implicit I: InjectK[ClientTerm[L, ?], F]): ClientTerms[L, F] =
new ClientTerms[L, F]
}
trait ClientTermAbstraction[L <: Abstraction] {
def ClientInterp: (ClientTerm[L, ?] ~> Target)
}
// Defining the actual structures used in the language being expressed
final class ScalaAbstraction extends Abstraction {
type Include = scala.meta.Import
type Function = scala.meta.Defn.Def
type Module = scala.meta.Defn.Class
}
final class JavaAbstraction extends Abstraction {
type Include = String
type Function = String
type Module = String
}
class Foo {
// Define interpreters
object LanguageInterp extends (LanguageStuff ~> Target) {
def apply[T](value: LanguageStuff[T]): Target[T] = value match {
case RenderFile(content) =>
Monad[Target].pure(content)
}
}
// The following interpreters are partially applied, fixing which language they target
val scalaModelInterp = new (ModelProtocolTerm[ScalaAbstraction, ?] ~> Target) {
import scala.meta._
def apply[T](value: ModelProtocolTerm[ScalaAbstraction, T]): Target[T] = value match {
case InterpolateFile(xs) => Monad[Target].pure(q"""
class Whatever {
..$xs
}
""")
}
}
val scalaClientInterp = new (ClientTerm[ScalaAbstraction, ?] ~> Target) {
import scala.meta._
def apply[T](value: ClientTerm[ScalaAbstraction, T]): Target[T] = value match {
case GetImports() => {
Monad[Target].pure(List(q"import foo._"))
}
}
}
val javaModelInterp = new (ModelProtocolTerm[JavaAbstraction, ?] ~> Target) {
def apply[T](value: ModelProtocolTerm[JavaAbstraction, T]): Target[T] = value match {
case InterpolateFile(xs) => Monad[Target].pure(xs.mkString(","))
}
}
val javaClientInterp = new (ClientTerm[JavaAbstraction, ?] ~> Target) {
import scala.meta._
def apply[T](value: ClientTerm[JavaAbstraction, T]): Target[T] = value match {
case GetImports() =>
Monad[Target].pure(List("a", "b", "c"))
}
}
// Define languages, fixing which language abstraction matches each language.
// Maybe something like EitherKT could be used to
// EitherKT[ModelProtocolTerm, LanguageStuff, ScalaAbstraction, T]
// or whatnot to reduce noise
type ScalaApplicationA[T] = EitherK[ModelProtocolTerm[ScalaAbstraction, ?], LanguageStuff, T]
type ScalaApplication[T] = EitherK[ClientTerm[ScalaAbstraction, ?], ScalaApplicationA, T]
type JavaApplicationA[T] = EitherK[ModelProtocolTerm[JavaAbstraction, ?], LanguageStuff, T]
type JavaApplication[T] = EitherK[ClientTerm[JavaAbstraction, ?], JavaApplicationA, T]
def doMoreWork[G[_]](source: String)(implicit L: LanguageStuffs[G]): Free[G, String] = {
import L._
for {
f <- renderFile(source)
} yield f
}
def work[L <: Abstraction, F[_]](implicit L: LanguageStuffs[F], S: ModelProtocolTerms[L, F], T: ClientTerms[L, F]): Free[F, String] = {
import L._
import S._
import T._
for {
imports <- getImports()
source <- interpolateFile(imports)
_ <- renderFile("blep") // It's still OK to call LanguageStuff abstraction from here, even though we're returning a Free[LanguageStuff, T]
ret <- doMoreWork(source.toString)
} yield ret
}
// Actually interpret the program against the different languages
val scalaRes: Target[String] =
work[ScalaAbstraction, ScalaApplication].foldMap(scalaClientInterp.or(scalaModelInterp.or(LanguageInterp)))
val javaRes: Target[String] =
work[JavaAbstraction, JavaApplication].foldMap(javaClientInterp.or(javaModelInterp.or(LanguageInterp)))
// Different results due to different language implementations and constraints:
println(scalaRes) // Right(class Whatever { import foo._ })
println(javaRes) // Right(a,b,c)
}
object App extends Foo with App
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment