Effect

trait Effect[Z <: Signature] extends CanPerform[Z] with CanInterpret

Base trait for any user-defined effect.

An instance of Effect serves as a bridge, between effects request and effect handlers.

  1. For effect requests, Effect provides syntax for invoking effect's operations. The syntax is defined using perform method.

  2. For effect handlers, Effect provides environment for implementing an Interpreter, which can be subsequently transformed to a Handler.

Typically, a custom defined Signature is 1-1 paired with a custom defined Effect.


Usage

Assuming the following Signature defined:

import turbolift.Signature

trait GoogleSignature extends Signature:
  def countPicturesOf(topic: String): Int !@! ThisEffect

...then, the corresponding Effect should look like this:

import turbolift.{!!, Effect}

trait Google extends Effect[GoogleSignature] with GoogleSignature:
  final override def countPicturesOf(topic: String): Int !! this.type = perform(_.countPicturesOf(topic))

⚠️ Google uses its GoogleSignature twice: first as the type parameter, and second as the super trait.

⚠️ Effect trait finally-overrides !@! as !!, and ThisEffect as this.type. The (re)definintion of countPicturesOf in Google uses those overrides. This is not necessary, but it improves readability of error messages.

Effect instance defines unique identity for the effect, both in type and value spaces. In order for our Google to be usable, such instance must be made accessible. Assuming global scope:

case object MyGoogle extends Google   // unique value
type MyGoogle = MyGoogle.type         // unique type (Scala's singleton type)

The type alias type MyGoogle is for convenience only.

Now, we can finally invoke the effect's operations:

val myComputation: Int !! MyGoogle = MyGoogle.countPicturesOf("cat")

⚠️ Unlike in most effect systems in Scala and Haskell scene, in Turbolift it is possible to have more than 1 instance of given effect, and even use them simultaneously in the same computation. Turbolift's runtime will treat them as completely separate effects, with each expecting a separate handler instance.

Related reading: Labelled Effects in Idris. In Idris, the label is optional. In Turbolift, effects are always "labelled". Scala's dependent typing can be used to write code polymorphic over effect's identity. Such as a Handler, that can handle any instance of Google effect. This is how default handlers for standard effects (Reader, State, etc.) are implemented.

Example of 2 instances of our Google effect:

case object MyGoogle1 extends Google
case object MyGoogle2 extends Google
type MyGoogle1 = MyGoogle1.type
type MyGoogle2 = MyGoogle2.type

val myComputation: Int !! (MyGoogle1 & MyGoogle2) =
  for
    a <- MyGoogle1.countPicturesOf("cat")
    b <- MyGoogle2.countPicturesOf("dog")
  yield a + b

If, for some reasons, this property is undesirable, we can hardcode the effect to be limited to 1 instance forever. All we need to do, is to replace trait Google with case object Google:

trait Google extends Effect[GoogleSignature] with GoogleSignature:
  final override def countPicturesOf(topic: String): Int !! this.type = perform(_.countPicturesOf(topic))

...with:

case object Google extends Effect[GoogleSignature] with GoogleSignature:
  final override def countPicturesOf(topic: String): Int !! Google = perform(_.countPicturesOf(topic))

type Google = Google.type

⚠️ Even though we have just limited the number of Google effect instances to 1, we can still define multiple Handlers for Google.

Type parameters:
Z

The Signature of this effect.

Companion:
object
trait HasSignature
trait CanPerform[Z]
trait Signature
class Object
trait Matchable
class Any
trait AcyclicMemoizer[K, V]
trait AutoInc
trait CyclicMemoizer[K, V]
trait MonoGraph[K, V]
trait PolyGraph[K, V]
trait Choice
object Each.type
object Fail.type
trait ErrorEffect[E, E1]
trait Error[E]
trait ErrorG[M, K, V]
trait ErrorGK[M, K, F, V]
trait ErrorK[F, E]
trait Reader[R]
trait State[S]
trait WriterEffect[W, W1]
trait Writer[W]
trait WriterG[M, K, V]
trait WriterGK[M, K, F, V]
trait WriterK[F, W]

Type members

Inherited classlikes

abstract class Proxy[Fx] extends Proxy[Fx] with ThisInterpreter

Base class for user-defined Proxy Interpreter for this effect.

Base class for user-defined Proxy Interpreter for this effect.

Inherited from:
CanInterpret
abstract class Stateful[S, F[_]] extends Stateful[S, F] with ThisInterpreter

Base class for user-defined Stateful Interpreter for this effect.

Base class for user-defined Stateful Interpreter for this effect.

Inherited from:
CanInterpret
abstract class Stateless[F[_]] extends Stateless[F] with ThisInterpreter

Base class for user-defined Stateless Interpreter for this effect.

Base class for user-defined Stateless Interpreter for this effect.

Inherited from:
CanInterpret

Defines type aliases for Handler, specialized to eliminate this effect.

Defines type aliases for Handler, specialized to eliminate this effect.

Inherited from:
CanInterpret
sealed trait ThisInterpreter extends Unsealed
Inherited from:
CanInterpret

Types

final override type ThisEffect = Effect

Inherited types

final override type !@![+A, U] = Computation[A, U]
Inherited from:
CanPerform
final type ThisHandler[F[_], N] = Handler[F, ThisEffect, N]

Alias for Handler, specialized to eliminate this effect.

Alias for Handler, specialized to eliminate this effect.

Inherited from:
CanInterpret

Value members

Concrete methods

final def &![Fx2 <: HasSignature](fx2: Fx2): Combine2[Effect[Z], Fx2]

Combines with another Effect instance, so that a single Handler for 2 effects can be created.

Combines with another Effect instance, so that a single Handler for 2 effects can be created.

Inherited methods

final def perform[A, U <: ThisEffect](f: Z & Signature { type ThisEffect = U; } => A !@! U { def apply(z: Z & Signature { type ThisEffect = U; }): A !@! U; }): Computation[A, U]

Lifts an invocation of this Signature's method into the Computation monad.

Lifts an invocation of this Signature's method into the Computation monad.

The purpose of perform is similar to:

  • send function form various effect systems (Eff monad, etc.)
  • serviceWith from ZIO 1.x.
  • serviceWithZIO from ZIO 2.x.

⚠️ Scaladoc displays the definition of perform as more complex than it actually is:

final def perform[A, U <: ThisEffect](f: (z: Z & Signature { type ThisEffect = U }) => z.!@![A, U]): A !! U
Inherited from:
CanPerform
final def pure[A](a: A): Computation[A, ThisEffect]

Like !!.pure(a), but with effect-set up-casted to ThisEffect

Like !!.pure(a), but with effect-set up-casted to ThisEffect

Inherited from:
CanPerform