From d4389b6f50eeafae4dd1950412d5b923dc39b17b Mon Sep 17 00:00:00 2001 From: Anthony Cerruti Date: Sun, 12 Apr 2020 19:39:49 -0700 Subject: [PATCH] Initial commit --- .gitignore | 110 ++++++++++++++++++ build.sbt | 14 +++ .../scala/tf/bug/fancadetagless/Block.scala | 19 +++ .../tf/bug/fancadetagless/Encoding.scala | 41 +++++++ .../scala/tf/bug/fancadetagless/Make.scala | 9 ++ .../scala/tf/bug/fancadetagless/Pin.scala | 29 +++++ .../scala/tf/bug/fancadetagless/Pins.scala | 3 + .../scala/tf/bug/fancadetagless/World.scala | 6 + .../scala/tf/bug/fancadetagless/package.scala | 42 +++++++ .../tf/bug/fancadetagless/test/Calc.scala | 59 ++++++++++ .../tf/bug/fancadetagless/test/Main.scala | 23 ++++ project/build.properties | 1 + 12 files changed, 356 insertions(+) create mode 100644 .gitignore create mode 100644 build.sbt create mode 100644 core/src/main/scala/tf/bug/fancadetagless/Block.scala create mode 100644 core/src/main/scala/tf/bug/fancadetagless/Encoding.scala create mode 100644 core/src/main/scala/tf/bug/fancadetagless/Make.scala create mode 100644 core/src/main/scala/tf/bug/fancadetagless/Pin.scala create mode 100644 core/src/main/scala/tf/bug/fancadetagless/Pins.scala create mode 100644 core/src/main/scala/tf/bug/fancadetagless/World.scala create mode 100644 core/src/main/scala/tf/bug/fancadetagless/package.scala create mode 100644 core/src/main/scala/tf/bug/fancadetagless/test/Calc.scala create mode 100644 core/src/main/scala/tf/bug/fancadetagless/test/Main.scala create mode 100644 project/build.properties diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4aa0937 --- /dev/null +++ b/.gitignore @@ -0,0 +1,110 @@ + +# Created by https://www.gitignore.io/api/sbt,scala,intellij+all +# Edit at https://www.gitignore.io/?templates=sbt,scala,intellij+all + +### Intellij+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij+all Patch ### +# Ignores the whole .idea folder and all .iml files +# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 + +.idea/ + +# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 + +*.iml +modules.xml +.idea/misc.xml +*.ipr + +# Sonarlint plugin +.idea/sonarlint + +### SBT ### +# Simple Build Tool +# http://www.scala-sbt.org/release/docs/Getting-Started/Directories.html#configuring-version-control + +dist/* +target/ +lib_managed/ +src_managed/ +project/boot/ +project/plugins/project/ +.history +.cache +.lib/ + +### Scala ### +*.class +*.log +*.metals + +# End of https://www.gitignore.io/api/sbt,scala,intellij+all \ No newline at end of file diff --git a/build.sbt b/build.sbt new file mode 100644 index 0000000..7f430bb --- /dev/null +++ b/build.sbt @@ -0,0 +1,14 @@ +lazy val core = (project in file("core")).settings( + organization := "tf.bug", + name := "fancadetagless", + version := "0.1.0", + scalaVersion := "2.13.1", + libraryDependencies ++= Seq( + "org.scala-graph" %% "graph-core" % "1.13.2", + "com.chuusai" %% "shapeless" % "2.3.3", + "org.typelevel" %% "cats-core" % "2.1.1", + "org.typelevel" %% "cats-effect" % "2.1.2", + "io.chrisdavenport" %% "fuuid" % "0.3.0", + ), + addCompilerPlugin("org.typelevel" %% "kind-projector" % "0.11.0" cross CrossVersion.full), +) diff --git a/core/src/main/scala/tf/bug/fancadetagless/Block.scala b/core/src/main/scala/tf/bug/fancadetagless/Block.scala new file mode 100644 index 0000000..bd1d300 --- /dev/null +++ b/core/src/main/scala/tf/bug/fancadetagless/Block.scala @@ -0,0 +1,19 @@ +package tf.bug.fancadetagless + +import cats.effect.Sync +import cats.implicits._ +import io.chrisdavenport.fuuid.FUUID + +case class Block(uuid: FUUID, make: Make, pins: Set[Pin]) + +object Block { + + def fromMake[F[_]](make: Make)(implicit s: Sync[F]): F[Block] = { + val uuid = FUUID.randomFUUID[F] + val pins = make.pins.traverse[F, Pin] { case (tpe, dir) => Pin.fromMake(tpe, dir) } + (uuid, pins).mapN { + case (u, v) => Block(u, make, v.toSet) + } + } + +} diff --git a/core/src/main/scala/tf/bug/fancadetagless/Encoding.scala b/core/src/main/scala/tf/bug/fancadetagless/Encoding.scala new file mode 100644 index 0000000..dedbe7d --- /dev/null +++ b/core/src/main/scala/tf/bug/fancadetagless/Encoding.scala @@ -0,0 +1,41 @@ +package tf.bug.fancadetagless + +import cats.effect.Sync +import shapeless._ + +trait Encoding[A] { + + def pins: Vector[PinType] + + def encodeConstant(a: A): Vector[Make] + +} + +object Encoding extends ProductTypeClassCompanion[Encoding] { + + object typeClass extends ProductTypeClass[Encoding] { + + override def product[H, T <: HList](ch: Encoding[H], ct: Encoding[T]): Encoding[H :: T] = new Encoding[H :: T] { + override def pins: Vector[PinType] = ch.pins ++ ct.pins + override def encodeConstant(a: H :: T): Vector[Make] = ch.encodeConstant(a.head) ++ ct.encodeConstant(a.tail) + } + + override def emptyProduct: Encoding[HNil] = new Encoding[HNil] { + override def pins: Vector[PinType] = Vector.empty + override def encodeConstant(a: HNil): Vector[Make] = Vector.empty + } + + override def project[F, G](instance: => Encoding[G], to: F => G, from: G => F): Encoding[F] = new Encoding[F] { + override def pins: Vector[PinType] = instance.pins + override def encodeConstant(a: F): Vector[Make] = instance.encodeConstant(to(a)) + } + + } + + implicit val floatEncoding: Encoding[Float] = new Encoding[Float] { + override def pins: Vector[PinType] = Vector(PinType.Number) + override def encodeConstant(a: Float): Vector[Make] = + Vector(Make.Builtin(s"Value[Number]($a)", pins.map((_, PinDirection.Outward)))) + } + +} diff --git a/core/src/main/scala/tf/bug/fancadetagless/Make.scala b/core/src/main/scala/tf/bug/fancadetagless/Make.scala new file mode 100644 index 0000000..fd8d8b2 --- /dev/null +++ b/core/src/main/scala/tf/bug/fancadetagless/Make.scala @@ -0,0 +1,9 @@ +package tf.bug.fancadetagless + +sealed trait Make { + def pins: Vector[(PinType, PinDirection)] +} +object Make { + case class Builtin(name: String, pins: Vector[(PinType, PinDirection)]) extends Make + case class Custom(world: World, pins: Vector[(PinType, PinDirection)]) extends Make +} diff --git a/core/src/main/scala/tf/bug/fancadetagless/Pin.scala b/core/src/main/scala/tf/bug/fancadetagless/Pin.scala new file mode 100644 index 0000000..81c3371 --- /dev/null +++ b/core/src/main/scala/tf/bug/fancadetagless/Pin.scala @@ -0,0 +1,29 @@ +package tf.bug.fancadetagless + +import cats.effect.Sync +import cats.implicits._ +import io.chrisdavenport.fuuid.FUUID + +case class Pin(uuid: FUUID, tpe: PinType, dir: PinDirection) +object Pin { + + def fromMake[F[_]](tpe: PinType, dir: PinDirection)(implicit s: Sync[F]): F[Pin] = { + val uuid: F[FUUID] = FUUID.randomFUUID[F] + uuid.map(Pin(_, tpe, dir)) + } + +} + +sealed trait PinType +object PinType { + case object Pull extends PinType + case object Number extends PinType + case object Truth extends PinType + case class Quoted[T <: PinType](v: T) extends PinType +} + +sealed trait PinDirection +object PinDirection { + case object Inward extends PinDirection + case object Outward extends PinDirection +} diff --git a/core/src/main/scala/tf/bug/fancadetagless/Pins.scala b/core/src/main/scala/tf/bug/fancadetagless/Pins.scala new file mode 100644 index 0000000..3b2592c --- /dev/null +++ b/core/src/main/scala/tf/bug/fancadetagless/Pins.scala @@ -0,0 +1,3 @@ +package tf.bug.fancadetagless + +case class Pins[A](pins: Vector[Pin])(implicit enc: Encoding[A]) diff --git a/core/src/main/scala/tf/bug/fancadetagless/World.scala b/core/src/main/scala/tf/bug/fancadetagless/World.scala new file mode 100644 index 0000000..d82ced1 --- /dev/null +++ b/core/src/main/scala/tf/bug/fancadetagless/World.scala @@ -0,0 +1,6 @@ +package tf.bug.fancadetagless + +import scalax.collection.Graph +import scalax.collection.GraphEdge.DiHyperEdge + +case class World(blocks: Set[Block], connections: Graph[Pin, DiHyperEdge]) diff --git a/core/src/main/scala/tf/bug/fancadetagless/package.scala b/core/src/main/scala/tf/bug/fancadetagless/package.scala new file mode 100644 index 0000000..d0baed0 --- /dev/null +++ b/core/src/main/scala/tf/bug/fancadetagless/package.scala @@ -0,0 +1,42 @@ +package tf.bug + +import cats.data.StateT +import cats.effect.Sync +import cats.implicits._ +import io.chrisdavenport.fuuid.FUUID +import scalax.collection.GraphEdge.DiHyperEdge + +package object fancadetagless { + + type FancadeT[F[_], A] = StateT[F, World, Pins[A]] + object FancadeT { + def make[F[_]](make: Make)(implicit s: Sync[F]): FancadeT[F, Unit] = { + val block = Block.fromMake[F](make) + StateT.apply { w => + block.map { b => + (w.copy(w.blocks + b), Pins(b.pins.toVector)) + } + } + } + + def constant[F[_], A](a: A)(implicit encoding: Encoding[A], s: Sync[F]): FancadeT[F, A] = { + val makes = encoding.encodeConstant(a) + val blocks = makes.traverse[F, Block](Block.fromMake[F]) + StateT.apply { world => + for { + b <- blocks + s = b.toSet + nw = world.copy(blocks = world.blocks ++ s) + } yield (nw, Pins(b.flatMap(_.pins))) + } + } + + def connect[F[_]](from: Pin, to: Pin)(implicit s: Sync[F]): FancadeT[F, Unit] = { + StateT.apply { w => + val newConn = DiHyperEdge(from, to) + s.pure(w.copy(connections = w.connections incl newConn), Pins(Vector.empty)) + } + } + } + +} diff --git a/core/src/main/scala/tf/bug/fancadetagless/test/Calc.scala b/core/src/main/scala/tf/bug/fancadetagless/test/Calc.scala new file mode 100644 index 0000000..5942e45 --- /dev/null +++ b/core/src/main/scala/tf/bug/fancadetagless/test/Calc.scala @@ -0,0 +1,59 @@ +package tf.bug.fancadetagless.test + +import cats.effect.Sync +import tf.bug.fancadetagless.{FancadeT, Make, PinDirection, PinType, Pins} + +trait Calc[W[_]] { + + def lift(f: Float): W[Float] + + def add(a: W[Float], b: W[Float]): W[Float] + def sub(a: W[Float], b: W[Float]): W[Float] + def mul(a: W[Float], b: W[Float]): W[Float] + def div(a: W[Float], b: W[Float]): W[Float] + +} + +object Calc { + + trait Program[R] { + def apply[F[_]](int: Calc[F]): F[R] + } + + case class FancadeCalc[F[_]]()(implicit s: Sync[F]) extends Calc[FancadeT[F, *]] { + + override def lift(f: Float): FancadeT[F, Float] = FancadeT.constant(f) + + override def add(a: FancadeT[F, Float], b: FancadeT[F, Float]): FancadeT[F, Float] = + for { + addBlock <- FancadeT.make(Make.Builtin("Add[Number]", Vector( + (PinType.Number, PinDirection.Inward), + (PinType.Number, PinDirection.Inward), + (PinType.Number, PinDirection.Outward) + ))) + ab <- a + bb <- b + _ <- FancadeT.connect(ab.pins.head, addBlock.pins(0)) + _ <- FancadeT.connect(bb.pins.head, addBlock.pins(1)) + } yield Pins(addBlock.pins.drop(2)) + + override def sub(a: FancadeT[F, Float], b: FancadeT[F, Float]): FancadeT[F, Float] = ??? + + override def mul(a: FancadeT[F, Float], b: FancadeT[F, Float]): FancadeT[F, Float] = ??? + + override def div(a: FancadeT[F, Float], b: FancadeT[F, Float]): FancadeT[F, Float] = + for { + addBlock <- FancadeT.make(Make.Builtin("Divide[Number]", Vector( + (PinType.Number, PinDirection.Inward), + (PinType.Number, PinDirection.Inward), + (PinType.Number, PinDirection.Outward) + ))) + ab <- a + bb <- b + _ <- FancadeT.connect(ab.pins.head, addBlock.pins(0)) + _ <- FancadeT.connect(bb.pins.head, addBlock.pins(1)) + } yield Pins(addBlock.pins.drop(2)) + + } + +} diff --git a/core/src/main/scala/tf/bug/fancadetagless/test/Main.scala b/core/src/main/scala/tf/bug/fancadetagless/test/Main.scala new file mode 100644 index 0000000..93775cb --- /dev/null +++ b/core/src/main/scala/tf/bug/fancadetagless/test/Main.scala @@ -0,0 +1,23 @@ +package tf.bug.fancadetagless.test + +import cats.effect.{ExitCode, IO, IOApp} +import cats.implicits._ +import scalax.collection.Graph +import tf.bug.fancadetagless.World + +object Main extends IOApp { + override def run(args: List[String]): IO[ExitCode] = { + object TwoPlusThreeOverFour extends Calc.Program[Float] { + override def apply[F[_]](int: Calc[F]): F[Float] = + int.div( + int.add( + int.lift(2.0f), + int.lift(3.0f) + ), + int.lift(4.0f) + ) + } + val ft = TwoPlusThreeOverFour(Calc.FancadeCalc[IO]) + ft.runS(World(Set.empty, Graph.empty)).flatMap(w => IO(println(w))).as(ExitCode.Success) + } +} diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 0000000..8589e99 --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version = 1.3.9 \ No newline at end of file