Giter VIP home page Giter VIP logo

diesel's Introduction

Diesel Build Status Maven Central Scala.js Join the chat at https://gitter.im/diesel-k/Lobby

diesel ˈdiːz(ə)l

  • Boilerplate-free Tagless Final DSLs via macro annotations (Scastie demo), written in scala.meta for future compatibility and other nice things (e.g. free IDE support, like in IntelliJ)
  • What "DSL" sounds like when you say it five times fast
  • More torque → more fun

This library provides 2 macros:

  1. @diesel to make it easier to compose algebras together.
  2. @ktrans to make it easier to perform Kind transforms on interpreters.

To use diesel in your project, add it to your build.

@diesel

The @diesel annotation cuts out the boilerplate associated with writing composable Tagless Final DSLs.

Your 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) and you have an implementation of your DSL in scope. This is useful when you need to compose multiple DSLs in the context of F[_], but do not want to name all the interpreter parameters.

Note that this can be used along-side @ktrans (simply write the annotations next to each other).

Example:

import diesel._, cats._, cats.implicits._

object DieselDemo {

  // Declare your DSL
  @diesel
  trait Maths[F[_]] {
    def times(l: Int, r: Int): F[Int]
    def add(l: Int, r: Int): F[Int]
  }

  @diesel
  trait Logger[F[_]] {
    def info(s: String): F[Unit]
  }

  // Import companion-to-interpreter aliasing sugar
  import Maths.ops._, Logger.ops._

  def prog[F[_]: Monad: Maths: Logger](x: Int, y: Int): F[Int] = {
    for {
      p <- Maths.times(x, y)
      _ <- Logger.info(s"Product: $p")
      s <- Maths.add(x, y)
      _ <- Logger.info(s"Sum: $s")
      f <- Maths.add(p, s)
      _ <- Logger.info(s"Final: $f")
    } yield f
  }

  def main(args: Array[String]): Unit = {

    // Wire our interpreters in
    implicit val mathsInterp = new Maths[Id] {
      def times(l: Int, r: Int) = l * r
      def add(l: Int, r: Int)   = l + r
    }
    implicit val loggingInterp = new Logger[Id] {
      def info(msg: String) = println(msg)
    }

    val _ = prog[Id](3, 4)
  }

}
/*
[info] Running DieselDemo
Product: 12
Sum: 7
Final: 19
*/

For more in-depth examples, check out:

  1. examples/KVSApp: a simple single-DSL program
  2. examples/KVSLoggingApp: composing 2 DSLs in a program
  3. examples/FibApp: composing 3 DSLs in a program that calculates fibonacci numbers and caches them.

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

How it works

@diesel
trait Maths[F[_]] {
  def times(l: Int, r: Int): F[Int]
  def add(l: Int, r: Int): F[Int]
}

is expanded approximately into

// Your algebra. Implement by providing a concrete F and you have your interpreter
trait Maths[F[_]] {
    def times(l: Int, r: Int): F[Int]
    def add(l: Int, r: Int): F[Int]
}

// Helper methods will be added to the algebra's companion object (one will be created if there isn't one yet)
object Maths {

  /**
    * In charge of aliasing your singleton Maths object to an in-scope Maths[F].
    * Simply `import Maths.ops._` :)
   **/
  object ops {
    implicit def toDsl[F[_]](o: Maths.type)(implicit m: Maths[F]): Maths[F] = m
  }

  /**
    * For when you feel like calling an in-scope interpreter via
    * `Maths[F]` syntax. Also helpful when you want to import all
    * the DSL methods using something like
    *
    * ```
    * val m = Maths[F]
    * import m._
    * add(1, 3)
    * ```
   **/
  def apply[F[_]](implicit m: Maths[F]): Maths[F] = m
}

@ktrans

The @ktrans annotation adds a transformK method (customisable by passing a name to the annotation as an argument) to a trait/abstract class that is parameterised by a Kind that takes 1 type parameter. It's handy when you want to transform any given implementation of that trait on F[_] into one that implements it on G[_].

Useful because it saves you from having to write boilerplate (method forwarding and results wrapping) that you would otherwise need to deal with manually (or via an IDE) when instantiating a new trait/abstract class.

Note that this can be used along-side @diesel (simply write the annotations next to each other).

Example

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 to FunK in the diesel-cats and diesel-scalaz companion projects.

How it works

@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 roughly expanded into

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))
    }
  }
}

Limitations

Because the @ktrans annotation's goal is to generate a kind-transformation method for your trait, it is subject to a few limitations:

  • Annottee must be parameterised by a higher kinded type with just 1 type parameter (context bounds allowed though)
  • No unimplemented methods that return types parameterised by the kind parameter of the algebra
  • No unimplemented type members
  • No vals that are not assignments

A lot of attention (in fact most of the code in the macro) has been dedicated to outputting understandable compile-time errors for those cases, so please reach out if anything seems amiss.

Sbt

Maven Central

Diesel is published for Scala 2.11, 2.12 and ScalaJS.

libraryDependencies += "com.beachape" %% "diesel-core" % s"$latest_version"

// Additional ceremony for using Scalameta macro annotations

resolvers += Resolver.url(
  "scalameta",
  url("http://dl.bintray.com/scalameta/maven"))(Resolver.ivyStylePatterns)

// A dependency on macro paradise is required to both write and expand
// 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)

scalacOptions += "-Xplugin-require:macroparadise"

Credit

Learnt quite a lot about tagless final from the following resources.

  1. Free vs Tagless final talk
  2. Alternatives to GADTS in Scala
  3. Quark talk
  4. Tagless final effects à la Ermine writers
  5. EDSLs as functions

diesel's People

Contributors

gitter-badger avatar lloydmeta avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

diesel's Issues

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.