All Projects → lloydmeta → Diesel

lloydmeta / Diesel

Licence: mit
Boilerplate-free, zero-overhead Tagless Final / typed-final / Finally Tagless DSLs in Scala

Programming Languages

scala
5932 projects
dsl
153 projects
macros
77 projects
scalaz
18 projects

Labels

Projects that are alternatives of or similar to Diesel

Scala Pet Store
An implementation of the java pet store using FP techniques in scala
Stars: ✭ 812 (+651.85%)
Mutual labels:  cats
Semanticsegmentation
A framework for training segmentation models in pytorch on labelme annotations with pretrained examples of skin, cat, and pizza topping segmentation
Stars: ✭ 52 (-51.85%)
Mutual labels:  cats
Monkeytail
Macro derived validation for cats.data.Validated
Stars: ✭ 87 (-19.44%)
Mutual labels:  cats
Http.cat
🐱 HTTP Cats API
Stars: ✭ 898 (+731.48%)
Mutual labels:  cats
Stm4cats
STM monad for cats-effect
Stars: ✭ 35 (-67.59%)
Mutual labels:  cats
Skunk
A data access library for Scala + Postgres.
Stars: ✭ 1,107 (+925%)
Mutual labels:  cats
Scalaz And Cats
Usage examples and benchmarks between Scalaz and Cats (w/ Haskell ground-truth).
Stars: ✭ 471 (+336.11%)
Mutual labels:  cats
Monocle
Optics library for Scala
Stars: ✭ 1,357 (+1156.48%)
Mutual labels:  cats
Ld40 Catsareassholes
A shelter simulation game made in 3 days for Ludum Dare 40. Even if the game was submitted to Jam instead of Compo, I still made everything all by myself.
Stars: ✭ 45 (-58.33%)
Mutual labels:  cats
Catsay
A program that generates pictures of a cat holding a sign with a message.
Stars: ✭ 85 (-21.3%)
Mutual labels:  cats
Catpapers
Cool vision, learning, and graphics papers on Cats!
Stars: ✭ 918 (+750%)
Mutual labels:  cats
Mewment
Kinda like a mashup of Tinder and Instagram, but for cats 🐱 🐈
Stars: ✭ 10 (-90.74%)
Mutual labels:  cats
Hands On Scalaz
Discover scalaz in a practical way
Stars: ✭ 77 (-28.7%)
Mutual labels:  cats
Es Cqrs Shopping Cart
A resilient and scalable shopping cart system designed using Event Sourcing (ES) and Command Query Responsibility Segregation (CQRS)
Stars: ✭ 19 (-82.41%)
Mutual labels:  cats
Magnolify
A collection of Magnolia add-on modules
Stars: ✭ 81 (-25%)
Mutual labels:  cats
Goebpf
Library to work with eBPF programs from Go
Stars: ✭ 666 (+516.67%)
Mutual labels:  cats
Sttp
The Scala HTTP client you always wanted!
Stars: ✭ 1,078 (+898.15%)
Mutual labels:  cats
Cats Stm
An STM implementation for Cats Effect
Stars: ✭ 106 (-1.85%)
Mutual labels:  cats
Scala Opentracing
A tracing library for Cats and Http4s, tailored for Opentracing tracers like Datadog and Jaeger
Stars: ✭ 95 (-12.04%)
Mutual labels:  cats
Trace4cats
Distributed app tracing implementation in pure scala using cats-effect
Stars: ✭ 80 (-25.93%)
Mutual labels:  cats

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
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].