Last active
November 20, 2018 00:54
-
-
Save blast-hardcheese/7e506b9618d13f3ae336f5a37807b1ac to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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