Skip to content

Commit 9476e18

Browse files
authored
Feature/monadic dsl (#2)
* Improve for-comprehension implicits for cats and scalaz. Now no longer requires an interpreter to be in scope and produces a DSL that is _run_ by an interpreter that produces monadic results. * Add more doctests * Add to examples
1 parent 0a55e2b commit 9476e18

File tree

13 files changed

+659
-133
lines changed

13 files changed

+659
-133
lines changed
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package diesel.implicits
2+
3+
import cats.Monad
4+
import diesel.Dsl
5+
6+
import scala.language.higherKinds
7+
import scala.language.implicitConversions
8+
9+
/**
10+
* Implicitly converts DSLs to MonadicDSl so that you can use them in for-comprehensions.
11+
*
12+
* The catch is that the interpreter that you use at the end must be written for a Monad
13+
*
14+
* Example:
15+
*
16+
* {{{
17+
* scala> import _root_.diesel._
18+
*
19+
* // Wrapper is only for the sake of sbt-doctest and is unnecessary in real-life usage
20+
* scala> object Wrapper {
21+
* | // Declare our DSLs
22+
* | @diesel
23+
* | trait Maths[G[_]] {
24+
* | def int(i: Int): G[Int]
25+
* | def add(l: G[Int], r: G[Int]): G[Int]
26+
* | }
27+
* | @diesel
28+
* | trait Applicative[F[_]] {
29+
* | def map2[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C]
30+
* | def pure[A](a: A): F[A]
31+
* | } }
32+
*
33+
* // Import the stuff we've just built
34+
* scala> import Wrapper._
35+
* scala> import Maths._
36+
* scala> import Applicative._
37+
* scala> import cats.Monad
38+
* scala> import cats.implicits._
39+
*
40+
* // Our combined algebra type and our program that uses it
41+
* scala> type PRG[A[_]] = Applicative.Algebra[A] with Maths.Algebra[A]
42+
* scala> val op = { (a: Int, b: Int, c: Int) =>
43+
* | import monadic._
44+
* | // Note the use of for comprehensions in here
45+
* | for {
46+
* | i <- add(int(a), int(b)).withAlg[PRG]
47+
* | j <- pure(c).withAlg[PRG]
48+
* | k <- add(int(i), int(j)).withAlg[PRG]
49+
* | } yield k
50+
* | }
51+
52+
* // Write our interpreter
53+
* scala> implicit def interp[F[_]](implicit F: Monad[F]) = new Applicative.Algebra[F] with Maths.Algebra[F] {
54+
* | def int(i: Int) = F.pure(i)
55+
* | def add(l: F[Int], r: F[Int]) =
56+
* | for {
57+
* | x <- l
58+
* | y <- r
59+
* | } yield x + y
60+
* | def map2[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C] = F.map2(fa, fb)(f)
61+
* | def pure[A](a: A): F[A] = F.pure(a)
62+
* | }
63+
*
64+
* // Now we can use our DSL
65+
* scala> val program = op(1, 2, 3)
66+
*
67+
* scala> program[Option]
68+
* res0: Option[Int] = Some(6)
69+
* }}}
70+
*/
71+
object monadic extends monadic
72+
73+
trait monadic {
74+
75+
implicit def toMonadicDsl[Alg[_[_]], A](dsl: Dsl[Alg, A]): MonadicDsl[Alg, A] =
76+
new MonadicDsl[Alg, A] {
77+
def apply[F[_]: Monad](implicit interpreter: Alg[F]): F[A] = dsl.apply(interpreter)
78+
}
79+
80+
}
81+
82+
trait MonadicDsl[Alg[_[_]], A] { self =>
83+
84+
import cats.implicits._
85+
86+
/**
87+
* Evaluate this Dsl to a F[A]
88+
*/
89+
def apply[F[_]: Monad](implicit interpreter: Alg[F]): F[A]
90+
91+
def map[B](f: A => B): MonadicDsl[Alg, B] = new MonadicDsl[Alg, B] {
92+
def apply[F[_]: Monad](implicit interpreter: Alg[F]): F[B] = {
93+
self[F].map(f)
94+
}
95+
}
96+
97+
/**
98+
* Combines Alg with AlgB
99+
*
100+
* Useful for flatmapping and for-comprehensions in general
101+
*/
102+
def withAlg[AlgB[_[_]]]
103+
: MonadicDsl[({ type Combined[X[_]] = Alg[X] with AlgB[X] })#Combined, A] =
104+
new MonadicDsl[({ type Combined[X[_]] = Alg[X] with AlgB[X] })#Combined, A] {
105+
def apply[F[_]: Monad](implicit interpreter: Alg[F] with AlgB[F]): F[A] = {
106+
self[F]
107+
}
108+
}
109+
110+
def flatMap[B](f: A => MonadicDsl[Alg, B]): MonadicDsl[Alg, B] =
111+
new MonadicDsl[Alg, B] {
112+
def apply[F[_]: Monad](implicit interpreter: Alg[F]): F[B] = {
113+
self[F].flatMap(r => f(r)[F])
114+
}
115+
}
116+
117+
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package diesel.implicits
2+
3+
import cats.MonadFilter
4+
import diesel.Dsl
5+
6+
import scala.language.higherKinds
7+
8+
/**
9+
* Implicitly converts DSLs to MonadicPlusDsl so that you can use them in for-comprehensions.
10+
*
11+
* Note, this is more powerful than importing from monadic because this requires a MonadPlus instance
12+
* for the F[_] in the eventual interpreter that you use to get your results.
13+
*
14+
* Example:
15+
*
16+
* {{{
17+
* scala> import _root_.diesel._
18+
*
19+
* // Wrapper is only for the sake of sbt-doctest and is unnecessary in real-life usage
20+
* scala> object Wrapper {
21+
* | // Declare our DSLs
22+
* | @diesel
23+
* | trait Maths[G[_]] {
24+
* | def int(i: Int): G[Int]
25+
* | def add(l: G[Int], r: G[Int]): G[Int]
26+
* | }
27+
* | @diesel
28+
* | trait Applicative[F[_]] {
29+
* | def map2[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C]
30+
* | def pure[A](a: A): F[A]
31+
* | } }
32+
*
33+
* // Import the stuff we've just built
34+
* scala> import Wrapper._
35+
* scala> import Maths._
36+
* scala> import Applicative._
37+
* scala> import cats.Monad
38+
* scala> import cats.implicits._
39+
*
40+
* // Our combined algebra type and our program that uses it
41+
* scala> type PRG[A[_]] = Applicative.Algebra[A] with Maths.Algebra[A]
42+
* scala> val op = { (a: Int, b: Int, c: Int) =>
43+
* | import monadicplus._
44+
* | // Note the use of for comprehensions in here
45+
* | for {
46+
* | i <- add(int(a), int(b)).withAlg[PRG]
47+
* | if i > 3
48+
* | j <- pure(c).withAlg[PRG]
49+
* | k <- add(int(i), int(j)).withAlg[PRG]
50+
* | } yield k
51+
* | }
52+
53+
* // Write our interpreter
54+
* scala> implicit def interp[F[_]](implicit F: Monad[F]) = new Applicative.Algebra[F] with Maths.Algebra[F] {
55+
* | def int(i: Int) = F.pure(i)
56+
* | def add(l: F[Int], r: F[Int]) =
57+
* | for {
58+
* | x <- l
59+
* | y <- r
60+
* | } yield x + y
61+
* | def map2[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C] = F.map2(fa, fb)(f)
62+
* | def pure[A](a: A): F[A] = F.pure(a)
63+
* | }
64+
*
65+
* // Now we can use our DSL
66+
* scala> val program1 = op(1, 2, 3)
67+
* scala> val program2 = op(4, 5, 6)
68+
*
69+
* scala> program1[Option]
70+
* res0: Option[Int] = None
71+
*
72+
* scala> program2[Option]
73+
* res1: Option[Int] = Some(15)
74+
* }}}
75+
*/
76+
object monadicplus extends monadicplus
77+
78+
trait monadicplus {
79+
80+
implicit def dslToMonadicFilterDsl[Alg[_[_]], A](dsl: Dsl[Alg, A]): MonadPlusDsl[Alg, A] =
81+
new MonadPlusDsl[Alg, A] {
82+
def apply[F[_]: MonadFilter](implicit interpreter: Alg[F]): F[A] = dsl[F]
83+
}
84+
85+
}
86+
87+
/**
88+
* Allows for the full span of for-comprehension options to be used, including
89+
* filtering in between.
90+
*
91+
* However, requires there to be a MonadFilter instance for the F[_] that your eventual
92+
* interpreter will use.
93+
*/
94+
trait MonadPlusDsl[Alg[_[_]], A] { self =>
95+
96+
import cats.implicits._
97+
98+
/**
99+
* Evaluate this Dsl to a F[A]
100+
*/
101+
def apply[F[_]: MonadFilter](implicit interpreter: Alg[F]): F[A]
102+
103+
def map[B](f: A => B): MonadPlusDsl[Alg, B] = new MonadPlusDsl[Alg, B] {
104+
def apply[F[_]: MonadFilter](implicit interpreter: Alg[F]): F[B] = {
105+
self[F].map(f)
106+
}
107+
}
108+
109+
/**
110+
* Combines Alg with AlgB
111+
*
112+
* Useful for flatmapping and for-comprehensions in general
113+
*/
114+
def withAlg[AlgB[_[_]]]
115+
: MonadPlusDsl[({ type Combined[X[_]] = Alg[X] with AlgB[X] })#Combined, A] =
116+
new MonadPlusDsl[({ type Combined[X[_]] = Alg[X] with AlgB[X] })#Combined, A] {
117+
def apply[F[_]: MonadFilter](implicit interpreter: Alg[F] with AlgB[F]): F[A] = {
118+
self[F]
119+
}
120+
}
121+
122+
def flatMap[B](f: A => MonadPlusDsl[Alg, B]): MonadPlusDsl[Alg, B] =
123+
new MonadPlusDsl[Alg, B] {
124+
def apply[F[_]: MonadFilter](implicit interpreter: Alg[F]): F[B] = {
125+
self[F].flatMap(r => f(r)[F])
126+
}
127+
}
128+
129+
def filter(f: A => Boolean): MonadPlusDsl[Alg, A] = new MonadPlusDsl[Alg, A] {
130+
def apply[F[_]: MonadFilter](implicit interpreter: Alg[F]): F[A] = {
131+
implicitly[MonadFilter[F]].filter(self[F])(f)
132+
}
133+
}
134+
135+
def withFilter(f: A => Boolean): MonadPlusDsl[Alg, A] = filter(f)
136+
137+
}

cats/src/main/scala/diesel/implicits/package.scala

Lines changed: 0 additions & 23 deletions
This file was deleted.

0 commit comments

Comments
 (0)