• Stars
    star
    110
  • Rank 316,770 (Top 7 %)
  • Language
    Scala
  • License
    MIT License
  • Created over 7 years ago
  • Updated over 7 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

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

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

More Repositories

1

frunk

Funktional generic type-level programming in Rust: HList, Coproduct, Generic, LabelledGeneric, Validated, Monoid and friends.
Rust
1,280
star
2

enumeratum

A type-safe, reflection-free, powerful enumeration implementation for Scala with exhaustive pattern match warnings and helpful integrations.
Scala
1,163
star
3

schwatcher

File-watching library for Scala. Built on Java 7's WatchService, RxScala and Akka actors.
Scala
104
star
4

slim-play

Slim Play app
Scala
76
star
5

rhodddoobie

My little sandbox for playing around with the FP + OOP + DDD combination, in particular using Rho, doobie, Docker, testing, etc in a project.
Scala
43
star
6

ansible-kafka-cluster

Zookeeper quorum + Kafka cluster on CentOS via Vagrant + Ansible
42
star
7

rusqbin

A server that stashes your requests for later retrieval so you can do end-to-end testing of code that makes HTTP calls. Available as a binary, a Docker image, and a library.
Rust
26
star
8

zipkin-futures

Zipkin tracing for Scala Futures and non-Futures (synchronous operations)
Scala
21
star
9

push_to_devices

Ruby-based ( Padrino / Sinatra ) server for pushing notifications to iOS and Android devices via Apple Push Notifications (APN) and Google Cloud Message (GCM) respectively.
Ruby
18
star
10

todddo-openapi-rs

Survey of the Rust web scene in mid-2019, covering async/await, DDD-esque structure, testing, mocking, OpenAPI, and Actix
Rust
17
star
11

freAST

Fast, simple Free Monads using ScalaMeta macro annotations. Port of Freasy-Monad.
Scala
14
star
12

sbt-opencv

Start using OpenCV in your JVM project in just 1 line, no separate compiling, installing OpenCV, or fussing with your system required.
Scala
13
star
13

sparkka-streams

Power a Spark Stream from anywhere in your Akka Stream Flow
Scala
12
star
14

metascraper-service

A completely non-blocking RESTful Play2 based API application that uses the Metascraper library to scrape URLs for metadata.
Scala
12
star
15

http4s-doobie-docker-scratchpad

Playing around with http4s + doobie + docker
Scala
11
star
16

ltsv-logger

Convenient and performant logging in LTSV for Scala
Scala
10
star
17

tasques

Background Tasks system backed by Elasticsearch with support for language agnostic Workers.
Go
9
star
18

finnhub-ws-rs

UI for finnhub.io live trades websocket API, written in Rust, compiled to WASM
Rust
8
star
19

scala-akka-cv-part1

Scala, Akka, Streams, JavaCV, OpenCV O MY! Part 1
Scala
7
star
20

scala-akka-cv-part2

Scala, Akka, Streams, JavaCV, OpenCV O MY! Part 2
Scala
7
star
21

chase-rs

Async + Sync file-following for people who care about file rotations and line numbers.
Rust
7
star
22

ansible-thumbor-centos

Ansible playbook for CentOS.
Shell
7
star
23

spray-servlet-scratchpad

Where I try to play around with Spray+Servlet
JavaScript
6
star
24

jhhi

Java Heap Histogram Ingest, written in Rust. Sends jmap heap histograms to Elasticsearch.
Rust
6
star
25

unless-when

`unless` and `when` macros for Scala 2.10+.
Scala
5
star
26

gol-rs

Conway's Game of Life in Rust + OpenGL
Rust
5
star
27

push_to_devices_rb

Ruby library for interfacing with Push to Devices server (https://github.com/lloydmeta/push_to_devices)
Ruby
5
star
28

centos-spark-cluster

CentOS w/ Zookeeper quorum and Spark cluster
5
star
29

seed-scala.g8

A basic skeleton Scala project
Scala
4
star
30

cogs

WIP client for MS Congitive services using async Hyper
Rust
4
star
31

actix-jwt-authc

JWT authentication middleware for Actix with support for JWT invalidation and custom Claims structs.
Rust
4
star
32

hash_walker

A simple gem that allows you to traverse/walk a Hash (perhaps obtained from doing JSON::parse on a JSON string) according to a set of keys (also a hash), passing in a block to perform actions.
Ruby
4
star
33

provide

@provide Scala annotation so you can annotate abstract method implementations safely instead of abusing `override`
Scala
4
star
34

aoc2021-rs

Advent of Code 2021 solutions in Rust
Rust
3
star
35

scalameta.g8

sbt/Giter8 starter template for Scala.meta.
Scala
3
star
36

play-csv

CSV Path, Query, and Form binders for Play 2 framework apps.
Scala
3
star
37

bloxi

A Blockchain implementation in Rust, following the "Learn Blockchains by Building One" tutorial.
Rust
3
star
38

redis-cleaner

A simple way of cleaning up a large number of Redis keys via [pattern matching](http://redis.io/commands/keys)
Ruby
2
star
39

pseudo_encrypt-rs

A native Rust generic implementation of the pseudo_encrypt function from Psql
Rust
2
star
40

Olivetti-PNG

Olivetti face set in PNG form
2
star
41

ctdi-play.g8

Compile-time DI Play template
Scala
2
star
42

sloword2vec-rs

A naive (read: slow) implementation of Word2Vec. Uses BLAS behind the scenes for speed.
Rust
2
star
43

todddo-openapi

Exploring DDD, dependency-management, testing, web in the world of Golang.
Go
2
star
44

hystrix-scala-scratchpad

Lloyd's Hystrix playground. Exploring Scala + Hystrix.
Scala
2
star
45

Risp

Reduced-Lisp in Scala Processor, Risp, is a simplified, type-safe, stack-safe Lisp written in Scala.
Scala
2
star
46

todddo-openapi-java

An exploration of modern Java API dev by building a reactive non-trivial app with DDD-esque structuring.
Java
2
star
47

mune

Scala
1
star
48

fib-hs

Playing around with Haskell Stack toolbelt, asking questions
Haskell
1
star
49

elasticsearch-rs

Elasticsearch Rust Client
Rust
1
star
50

iBabble

Mobile client for Babble
Objective-C
1
star
51

aoc2020-rs

Advent of Code 2020 solutions in Rust
Rust
1
star
52

lloydmeta.github.io

HTML
1
star
53

bloop-with-gradle-and-silencer

Min repro
Kotlin
1
star
54

bloop-gradle-subprojects

Repro
Kotlin
1
star
55

bloop-on-gradle-with-its

A min repro of the bloop Gradle plugin not adding test frameworks to an integration test configuration
Java
1
star
56

slim-play.g8

Giter8 template for a slim Play app
Scala
1
star
57

ngrams-enabler

A simple way of getting ngrams out of any given String object. Supports CJK (Chinese, Japanese, Korean) as well as alphabet based languages.
Ruby
1
star
58

ip-parsing-hs

Playing around with parsers in Haskell
Haskell
1
star
59

slim-play-activator

Activator template for a Slim Play project
Scala
1
star
60

play-chatroom

Super simple websockets, Play framework chatroom w/ actors.
Scala
1
star
61

reculture-shields

Super simple shields for Re:Culture.
Scala
1
star
62

gander

An HTML content extractor. Forked from a fork of Goose.
Scala
1
star
63

thumbor-intro

HTML
1
star
64

sbt-javacpp

Use JavaCPP and JavaCPP presets with ease. Base plugin for JavaCPP-related projects.
Scala
1
star
65

akanori-thrift

Thrift server version of akanori
Java
1
star
66

huggingface_elasticsearch_rag

Cookbook for building an Elasticsearch and Gemma-powered RAG via Huggingface models and APIs
Jupyter Notebook
1
star
67

opa-gungnamstyle-scratchpad

OPA exploration
Open Policy Agent
1
star