-
Notifications
You must be signed in to change notification settings - Fork 348
/
ref.scala
118 lines (98 loc) · 4.63 KB
/
ref.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
// Copyright (c) 2013-2017 Rob Norris
// This software is licensed under the MIT License (MIT).
// For more information see LICENSE or https://opensource.org/licenses/MIT
package doobie.free
import cats.~>
import cats.effect.Async
import cats.free.{ Free => FF } // alias because some algebras have an op called Free
import java.lang.String
import java.sql.Ref
import java.util.Map
@SuppressWarnings(Array("org.wartremover.warts.Overloading"))
object ref { module =>
// Algebra of operations for Ref. Each accepts a visitor as an alternatie to pattern-matching.
sealed trait RefOp[A] {
def visit[F[_]](v: RefOp.Visitor[F]): F[A]
}
// Free monad over RefOp.
type RefIO[A] = FF[RefOp, A]
// Module of instances and constructors of RefOp.
object RefOp {
// Given a Ref we can embed a RefIO program in any algebra that understands embedding.
implicit val RefOpEmbeddable: Embeddable[RefOp, Ref] =
new Embeddable[RefOp, Ref] {
def embed[A](j: Ref, fa: FF[RefOp, A]) = Embedded.Ref(j, fa)
}
// Interface for a natural tansformation RefOp ~> F encoded via the visitor pattern.
// This approach is much more efficient than pattern-matching for large algebras.
trait Visitor[F[_]] extends (RefOp ~> F) {
final def apply[A](fa: RefOp[A]): F[A] = fa.visit(this)
// Common
def raw[A](f: Ref => A): F[A]
def embed[A](e: Embedded[A]): F[A]
def delay[A](a: () => A): F[A]
def handleErrorWith[A](fa: RefIO[A], f: Throwable => RefIO[A]): F[A]
def async[A](k: (Either[Throwable, A] => Unit) => Unit): F[A]
// Ref
def getBaseTypeName: F[String]
def getObject: F[AnyRef]
def getObject(a: Map[String, Class[_]]): F[AnyRef]
def setObject(a: AnyRef): F[Unit]
}
// Common operations for all algebras.
final case class Raw[A](f: Ref => A) extends RefOp[A] {
def visit[F[_]](v: Visitor[F]) = v.raw(f)
}
final case class Embed[A](e: Embedded[A]) extends RefOp[A] {
def visit[F[_]](v: Visitor[F]) = v.embed(e)
}
final case class Delay[A](a: () => A) extends RefOp[A] {
def visit[F[_]](v: Visitor[F]) = v.delay(a)
}
final case class HandleErrorWith[A](fa: RefIO[A], f: Throwable => RefIO[A]) extends RefOp[A] {
def visit[F[_]](v: Visitor[F]) = v.handleErrorWith(fa, f)
}
final case class Async1[A](k: (Either[Throwable, A] => Unit) => Unit) extends RefOp[A] {
def visit[F[_]](v: Visitor[F]) = v.async(k)
}
// Ref-specific operations.
final case object GetBaseTypeName extends RefOp[String] {
def visit[F[_]](v: Visitor[F]) = v.getBaseTypeName
}
final case object GetObject extends RefOp[AnyRef] {
def visit[F[_]](v: Visitor[F]) = v.getObject
}
final case class GetObject1(a: Map[String, Class[_]]) extends RefOp[AnyRef] {
def visit[F[_]](v: Visitor[F]) = v.getObject(a)
}
final case class SetObject(a: AnyRef) extends RefOp[Unit] {
def visit[F[_]](v: Visitor[F]) = v.setObject(a)
}
}
import RefOp._
// Smart constructors for operations common to all algebras.
val unit: RefIO[Unit] = FF.pure[RefOp, Unit](())
def raw[A](f: Ref => A): RefIO[A] = FF.liftF(Raw(f))
def embed[F[_], J, A](j: J, fa: FF[F, A])(implicit ev: Embeddable[F, J]): FF[RefOp, A] = FF.liftF(Embed(ev.embed(j, fa)))
def delay[A](a: => A): RefIO[A] = FF.liftF(Delay(() => a))
def handleErrorWith[A](fa: RefIO[A], f: Throwable => RefIO[A]): RefIO[A] = FF.liftF[RefOp, A](HandleErrorWith(fa, f))
def raiseError[A](err: Throwable): RefIO[A] = delay(throw err)
def async[A](k: (Either[Throwable, A] => Unit) => Unit): RefIO[A] = FF.liftF[RefOp, A](Async1(k))
// Smart constructors for Ref-specific operations.
val getBaseTypeName: RefIO[String] = FF.liftF(GetBaseTypeName)
val getObject: RefIO[AnyRef] = FF.liftF(GetObject)
def getObject(a: Map[String, Class[_]]): RefIO[AnyRef] = FF.liftF(GetObject1(a))
def setObject(a: AnyRef): RefIO[Unit] = FF.liftF(SetObject(a))
// RefIO is an Async
implicit val AsyncRefIO: Async[RefIO] =
new Async[RefIO] {
val M = FF.catsFreeMonadForFree[RefOp]
def pure[A](x: A): RefIO[A] = M.pure(x)
def handleErrorWith[A](fa: RefIO[A])(f: Throwable => RefIO[A]): RefIO[A] = module.handleErrorWith(fa, f)
def raiseError[A](e: Throwable): RefIO[A] = module.raiseError(e)
def async[A](k: (Either[Throwable,A] => Unit) => Unit): RefIO[A] = module.async(k)
def flatMap[A, B](fa: RefIO[A])(f: A => RefIO[B]): RefIO[B] = M.flatMap(fa)(f)
def tailRecM[A, B](a: A)(f: A => RefIO[Either[A, B]]): RefIO[B] = M.tailRecM(a)(f)
def suspend[A](thunk: => RefIO[A]): RefIO[A] = M.flatten(module.delay(thunk))
}
}