Getting Started
Effectie
| Project | Maven Central | JVM | Scala.js |
|---|---|---|---|
| effectie-cats-effect3 | ✅ | ✅ | |
| effectie-cats-effect2 | ✅ | ✅ | |
| effectie-monix3 | ✅ | ✅ |
Supported Scala Versions:
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.2.0"
For Scala.js,
"io.kevinlee" %%% "effectie-cats-effect3" % "2.2.0"
In build.sbt,
libraryDependencies += "io.kevinlee" %% "effectie-cats-effect3" % "2.2.0"
For Scala.js,
libraryDependencies += "io.kevinlee" %%% "effectie-cats-effect3" % "2.2.0"
//> using dep "io.kevinlee::effectie-cats-effect3:2.2.0"
For Cats Effect 2
- sbt
- sbt (with libraryDependencies)
- scala-cli
In build.sbt,
"io.kevinlee" %% "effectie-cats-effect2" % "2.2.0"
For Scala.js,
"io.kevinlee" %%% "effectie-cats-effect2" % "2.2.0"
In build.sbt,
libraryDependencies += "io.kevinlee" %% "effectie-cats-effect2" % "2.2.0"
For Scala.js,
libraryDependencies += "io.kevinlee" %%% "effectie-cats-effect2" % "2.2.0"
//> using dep "io.kevinlee::effectie-cats-effect2:2.2.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.2.0"
For Scala.js,
"io.kevinlee" %%% "effectie-monix3" % "2.2.0"
In build.sbt,
libraryDependencies += "io.kevinlee" %% "effectie-monix3" % "2.2.0"
For Scala.js,
libraryDependencies += "io.kevinlee" %%% "effectie-monix3" % "2.2.0"
//> using dep "io.kevinlee::effectie-monix3:2.2.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@6c1afb2f
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@15f41121
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@51cd6c44
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@6a3526d1
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