Skip to content

Commit

Permalink
Add @ktrans for creating new interpreters using natural transformatio…
Browse files Browse the repository at this point in the history
…ns (#8)
  • Loading branch information
lloydmeta authored May 18, 2017
1 parent b68d688 commit 3f7a9d4
Show file tree
Hide file tree
Showing 17 changed files with 694 additions and 18 deletions.
87 changes: 82 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

Boilerplate free Tagless Final DSL macro annotation, written in [scala.meta](http://scalameta.org/) for future compatibility and other nice things (e.g. free IDE support, like in IntelliJ).

## General idea
## `@diesel`

This plugin provides an annotation that cuts out the boilerplate associated with writing composable Tagless Final DSLs.
The `@diesel` annotation that cuts out the boilerplate associated with writing composable Tagless Final DSLs.

The Dsl can be accessed directly from the companion object if you import a converter located in `ops`
(customisable by passing a name to the annotation as an argument). This are useful when you need to compose multiple DSLs in the context of `F[_]`, but do not want to name all the interpreter parameters.

Example:
### Example:

```scala
import diesel._, cats._, cats.implicits._
Expand Down Expand Up @@ -67,7 +67,7 @@ For more in-depth examples, check out:

All of the above examples use a pure KVS interpreter :)

## How it works
### How it works

```scala
@diesel
Expand Down Expand Up @@ -99,6 +99,84 @@ object Maths {

```


## `@ktrans`

There is also a handy `@ktrans` annotation that adds a `transformK` method to a trait that is parameterised by a Kind that
takes 1 type parameter. It's useful when you want to transform any given implementation of that trait for `F[_]` into one
that implements it on `G[_]`

### Example

```scala
import diesel._, cats._
import diesel.implicits._

@ktrans
trait Maths[G[_]] {
def add(l: Int, r: Int): G[Int]
def subtract(l: Int, r: Int): G[Int]
def times(l: Int, r: Int): G[Int]
}

val MathsIdInterp = new Maths[Id] {
def add(l: Int, r: Int) = l + r
def subtract(l: Int, r: Int) = l - r
def times(l: Int, r: Int) = l * r
}

// Using kind-project syntax to build our natural transformation from
// Id to Option
val idToOpt = λ[FunK[Id, Option]](Some(_))

// use the auto-generated transformK method to create a Maths[Option] from Maths[Id]
// via idToOpt
val MathsOptInterp = MathsIdInterp.transformK(idToOpt)

assert(MathsOptInterp.add(3, 10) == Some(13))
```

There are conversions from Cat's natural transformation (`FunctionK`) or Scalaz's `NaturalTransformation` in the
`diesel-cats` and `diesel-scalaz` companion projects.

### Limitations

- Parameterised by a higher kinded type with just 1 type parameter
- No unimplemented methods that return types not contained by the type parameter of the algebra
- No unimplemented type members
- No vals that are not assignments

### How it works

```scala
@ktrans
trait Maths[G[_]] {
def add(l: Int, r: Int): G[Int]
def subtract(l: Int, r: Int): G[Int]
def times(l: Int, r: Int): G[Int]
}
```

is expanded into

```scala
trait Maths[G[_]] {
def add(l: Int, r: Int): G[Int]
def subtract(l: Int, r: Int): G[Int]
def times(l: Int, r: Int): G[Int]

// Note that FunK is a really simple NaturalTransform / FunctionK
final def transformK[H[_]](natTrans: FunK[G, H]): Maths[H] = {
val curr = this
new Maths[H] {
def add(l: Int, r: Int): H[Int] = natTrans(curr.add(l, r))
def subtract(l: Int, r: Int): H[Int] = natTrans(curr.subtract(l, r))
def times(l: Int, r: Int): H[Int] = natTrans(curr.times(l, r))
}
}
}
```

## Sbt

[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.beachape/diesel-core_2.11/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.beachape/diesel-core_2.11)
Expand All @@ -124,7 +202,6 @@ scalacOptions += "-Xplugin-require:macroparadise"

```


# Credit

Learnt quite a lot about tagless final from the following resources.
Expand Down
34 changes: 32 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ lazy val root = Project(id = "diesel-root", base = file("."))
publishArtifact := false,
publishLocal := {}
)
.aggregate(coreJs, coreJvm, examplesJs, examplesJvm)
.aggregate(coreJs, coreJvm, catsJs, catsJvm, scalazJs, scalazJvm, examplesJs, examplesJvm)

lazy val core = crossProject
.crossType(CrossType.Pure)
Expand Down Expand Up @@ -52,10 +52,38 @@ lazy val examples = crossProject
publishLocal := {},
libraryDependencies += "org.typelevel" %%% "cats-core" % catsVersion
)
.dependsOn(core)
.dependsOn(cats)
lazy val examplesJs = examples.js
lazy val examplesJvm = examples.jvm

lazy val cats = crossProject
.crossType(CrossType.Pure)
.settings(
name := "diesel-cats",
commonSettings,
metaMacroSettings,
publishSettings,
testSettings,
libraryDependencies += "org.typelevel" %%% "cats-core" % catsVersion
)
.dependsOn(core)
lazy val catsJs = cats.js
lazy val catsJvm = cats.jvm

lazy val scalaz = crossProject
.crossType(CrossType.Pure)
.settings(
name := "diesel-scalaz",
commonSettings,
metaMacroSettings,
publishSettings,
testSettings,
libraryDependencies += "org.scalaz" %%% "scalaz-core" % "7.2.12"
)
.dependsOn(core)
lazy val scalazJs = scalaz.js
lazy val scalazJvm = scalaz.jvm

lazy val commonSettings: Seq[Def.Setting[_]] = Seq(
organization := "com.beachape",
version := theVersion,
Expand Down Expand Up @@ -103,6 +131,8 @@ lazy val metaMacroSettings: Seq[Def.Setting[_]] = Seq(
// new-style macros. This is similar to how it works for old-style macro
// annotations and a dependency on macro paradise 2.x.
addCompilerPlugin("org.scalameta" % "paradise" % "3.0.0-M8" cross CrossVersion.full),
// if your project uses multiple Scala versions, use this for cross building
addCompilerPlugin("org.spire-math" % "kind-projector" % "0.9.3" cross CrossVersion.binary),
scalacOptions += "-Xplugin-require:macroparadise",
// temporary workaround for https://github.com/scalameta/paradise/issues/10
scalacOptions in (Compile, console) := Seq() // macroparadise plugin doesn't work in repl yet.
Expand Down
12 changes: 12 additions & 0 deletions cats/src/main/scala/diesel/Conversions.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package diesel

import cats._
import scala.language.higherKinds

object Conversions {

def funKToFunK[F[_], G[_]](functionK: F ~> G) = new FunK[F, G] {
def apply[A](fa: F[A]): G[A] = functionK(fa)
}

}
13 changes: 13 additions & 0 deletions cats/src/main/scala/diesel/implicits/package.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package diesel

import cats._

import scala.language.implicitConversions
import scala.language.higherKinds

package object implicits {

implicit def functionKToFunK[F[_], G[_]](f: F ~> G): FunK[F, G] =
Conversions.funKToFunK(f)

}
6 changes: 6 additions & 0 deletions core/src/main/scala/diesel/Defaults.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package diesel

object Defaults {
val OpsName: String = "ops"
val TransKMethodName: String = "transformK"
}
14 changes: 14 additions & 0 deletions core/src/main/scala/diesel/FunK.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package diesel

import scala.language.higherKinds

/**
* Lite version of FunctionK (Cats) or NaturalTransformation (Scalaz).
*
* Conversions from either of those exist in diesel-cats or diesel-scalaz,
*
*/
trait FunK[F[_], G[_]] {

def apply[A](fa: F[A]): G[A]
}
8 changes: 2 additions & 6 deletions core/src/main/scala/diesel/diesel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,9 @@ import scala.annotation.compileTimeOnly
class diesel(dslName: String = Defaults.OpsName) extends scala.annotation.StaticAnnotation {

inline def apply(defn: Any): Any = meta {
val r = internal.MacroImpl.expand(this, defn)
val r = internal.DieselImpl.expand(this, defn)
// println(r.syntax)
r
}

}

object Defaults {
val OpsName: String = "ops"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import scala.meta._
import scala.collection.immutable._
import _root_.diesel.Defaults

object MacroImpl {
object DieselImpl {

def expand(self: Tree, defn: Tree): Stat = {
val opsName: Term.Name = {
Expand Down
Loading

0 comments on commit 3f7a9d4

Please sign in to comment.