Skip to main content
Version: 2.0.0-beta13

Getting Started

Effectie Logo Effectie

Build Status Release Status Latest version

ProjectMaven Central
effectie-cats-effect3Maven Central
effectie-cats-effect2Maven Central
effectie-monix3Maven Central
  • Supported Scala Versions: 3, 2.13 and 2.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

In build.sbt,

"io.kevinlee" %% "effectie-cats-effect3" % "2.0.0-beta13"

For Cats Effect 2

In build.sbt,

"io.kevinlee" %% "effectie-cats-effect2" % "2.0.0-beta13"

For more details, check out Effectie for Cats Effect.

For Monix

In build.sbt,

"io.kevinlee" %% "effectie-monix3" % "2.0.0-beta13"

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@5bb37653

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@62f56e04
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@6dca7565
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@2d8b8761
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