Getting Started
Effectie
Project | Maven Central |
---|---|
effectie-cats-effect3 | |
effectie-cats-effect2 | |
effectie-monix3 |
- Supported Scala Versions:
3
,2.13
and2.12
A set of type-classes and utils for functional effect libraries (i.e. Cats Effect, Monix and Scalaz's Effect).
Why Effectie? Please read "Why?" section.
Getting Started
For Cats Effect 3
- sbt
- sbt (with libraryDependencies)
- scala-cli
In build.sbt
,
"io.kevinlee" %% "effectie-cats-effect3" % "2.0.0"
In build.sbt
,
libraryDependencies += "io.kevinlee" %% "effectie-cats-effect3" % "2.0.0"
//> using dep "io.kevinlee::effectie-cats-effect3:2.0.0"
For Cats Effect 2
- sbt
- sbt (with libraryDependencies)
- scala-cli
In build.sbt
,
"io.kevinlee" %% "effectie-cats-effect2" % "2.0.0"
In build.sbt
,
libraryDependencies += "io.kevinlee" %% "effectie-cats-effect2" % "2.0.0"
//> using dep "io.kevinlee::effectie-cats-effect2:2.0.0"
For more details, check out Effectie for Cats Effect.
For Monix
- sbt
- sbt (with libraryDependencies)
- scala-cli
In build.sbt
,
"io.kevinlee" %% "effectie-monix3" % "2.0.0"
In build.sbt
,
libraryDependencies += "io.kevinlee" %% "effectie-monix3" % "2.0.0"
//> using dep "io.kevinlee::effectie-monix3:2.0.0"
For more details, check out Effectie for Monix.
Why?
Tagless final gives us power to defer the decision of the implementations of contexts we're binding and functional effect libraries like Cats Effect and Monix give us referential transparency (and more). There might be an issue though with writing implementation for the abstraction which is supposed to support not only effect libraries like Cats Effect but also Future
. You may end up writing exactly the same code with only an exception to how you construct effect data type (e.g. IO
vs Future
).
Let's check out some code examples.
Problem: Duplicate Implementations
If you use Cats Effect, you may write code like this.
import cats.syntax.all._
import cats.effect._
trait Foo[F[_]] {
def foo(a: Int, b: Int): F[Int]
}
object Foo {
def apply[F[_]: Sync](): Foo[F] = new Foo[F] {
def foo(a: Int, b: Int): F[Int] =
for {
n1 <- bar(a)
n2 <- bar(b)
} yield n1 + n2
private def bar(n: Int): F[Int] = for {
n2 <- Sync[F].delay(math.abs(n))
result <- if (n2 < 0)
Sync[F].raiseError(
new IllegalArgumentException("n is Int.MinValue so abs doesn't work for it.")
)
else
Sync[F].pure(n2)
} yield result
}
}
val foo = Foo[IO]()
// foo: Foo[IO] = repl.MdocSession$App0$Foo$$anon$1@155968be
foo.foo(1, 2).unsafeRunSync()
// res1: Int = 3
Then for some reason, your company uses another tech stack with Future
so you need to repeat the same logic with Future
like this.
import scala.concurrent._
import scala.concurrent.ExecutionContext.Implicits.global
class FooFuture extends Foo[Future] {
def foo(a: Int, b: Int): Future[Int] =
for {
n1 <- bar(a)
n2 <- bar(b)
} yield n1 + n2
private def bar(n: Int): Future[Int] = for {
n2 <- Future(math.abs(n))
result <- if (n2 < 0)
Future.failed(
new IllegalArgumentException("n is Int.MinValue so abs doesn't work for it.")
)
else
Future.successful(n2)
} yield result
}
import scala.concurrent.duration._
val foo = new FooFuture
// foo: FooFuture = repl.MdocSession$App0$FooFuture$1@2bb1c449
Await.result(
foo.foo(1, 2),
Duration.Inf
)
// res2: Int = 3
Now you need to continuously spend more time to maintain two code bases for the same operation.
No More Duplicates with Effectie
This issue can be solved easily with Effectie.
import cats._
import cats.syntax.all._
import effectie.core._
import effectie.syntax.all._
trait Foo[F[_]] {
def foo(a: Int, b: Int): F[Int]
}
object Foo {
def apply[F[_]: Fx: Monad](): Foo[F] = new Foo[F] {
def foo(a: Int, b: Int): F[Int] =
for {
n1 <- bar(a)
n2 <- bar(b)
} yield n1 + n2
private def bar(n: Int): F[Int] = for {
n2 <- effectOf(math.abs(n))
result <- if (n2 < 0)
errorOf(
new IllegalArgumentException("n is Int.MinValue so abs doesn't work for it.")
)
else
pureOf(n2)
} yield result
}
}
With just one code base above, you can use IO
or Future
as you wish like this.
import cats.effect._
import effectie.instances.ce2.fx._
val foo = Foo[IO]()
// foo: Foo[IO] = repl.MdocSession$App3$Foo$$anon$2@1e09c681
foo.foo(1, 2).unsafeRunSync()
// res4: Int = 3
import scala.concurrent._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import effectie.instances.future.fx._
val foo2 = Foo[Future]()
// foo2: Foo[Future] = repl.MdocSession$App3$Foo$$anon$2@404b5166
Await.result(
foo2.foo(1, 2),
Duration.Inf
)
// res5: Int = 3
As you can see, you can use the same Foo
for both IO
and Future
.
Check out
- Effectie for Cats Effect 2
- Effectie for Cats Effect 3 (Writing docs WIP)
- Effectie for Monix 3