Skip to main content
zio-cats

R/E/A Signature

Step 1 of 15

R/E/A Signature

The fundamental difference between ZIO and Cats Effect starts with the type signature.

The Type Signatures

Cats Effect
import cats.effect.IO

// IO has ONE type parameter
// Error is always Throwable
val ceEffect: IO[Int] = IO.pure(42)

IO[A] - Success type only

ZIO
import zio._

// ZIO has THREE type parameters with variance:
// ZIO[-R, +E, +A] = Environment, Error, Success
//   -R  : Contravariant environment (requires)
//   +E  : Covariant error (can fail with)
//   +A  : Covariant success (can produce)
val zioEffect: ZIO[Any, Nothing, Int] =
  ZIO.succeed(42)

ZIO[-R, +E, +A] - Variance annotations improve type inference

The Parameters

ParameterZIOCats EffectPurpose
RZIO[-R, E, A]N/AEnvironment/Dependencies
EZIO[R, +E, A]Always ThrowableError type
AZIO[R, E, +A]IO[A]Success type

Mental Model

TIP:

Mental model: ZIO[R, E, A] is like R => Either[E, A] — a function that requires an environment R and produces either an error E or a success A.

Key Insight

ZIO has an explicit environment parameter R for dependency injection, and a typed error channel E with variance annotations for better type inference.

Cats Effect uses Throwable for all errors. Dependencies are handled via Kleisli, Reader, or passed as parameters.

TIP:

Cats Effect's IO[A] is roughly equivalent to ZIO's ZIO[Any, Throwable, A] (also known as Task[A]).

Common Type Aliases

Cats Effect
import cats.effect.IO

// IO[A] is the only effect type
// Error handling uses MonadError[IO, Throwable]
val program: IO[Int] = IO.pure(42)

// For "can't fail" semantics, use types
// but IO still allows Throwable internally
val safe: IO[Int] = IO.pure(42)

IO[A] - single effect type

ZIO
import zio._

// UIO[A] = ZIO[Any, Nothing, A] (can't fail)
val uio: UIO[Int] = ZIO.succeed(42)

// Task[A] = ZIO[Any, Throwable, A]
val task: Task[String] = ZIO.attempt {
  scala.io.Source.fromFile("f").mkString
}

// IO[E, A] = ZIO[Any, E, A] (custom error)
val io: IO[String, Int] = ZIO.fail("oops")

// RIO[R, A] = ZIO[R, Throwable, A]
// Has environment, can fail with Throwable
trait Database
val rio: RIO[Database, Int] = ZIO.service[Database].as(1)

// URIO[R, A] = ZIO[R, Nothing, A]
// Has environment, cannot fail
val urio: URIO[Database, Int] = ZIO.service[Database].as(1)

UIO, Task, IO, RIO, URIO - type aliases for common patterns

TIP: Type alias reference:
  • IO[+E, +A] = ZIO[Any, E, A] — No environment, custom error
  • Task[+A] = ZIO[Any, Throwable, A] — No environment, Throwable error
  • RIO[-R, +A] = ZIO[R, Throwable, A] — Has environment, Throwable error
  • UIO[+A] = ZIO[Any, Nothing, A] — No environment, cannot fail
  • URIO[-R, +A] = ZIO[R, Nothing, A] — Has environment, cannot fail

Next Steps

Now that you understand the signature difference, let's look at creating effects in both libraries.

Next: Creating Effects →