Base trait for any user-defined effect.
An instance of Effect serves as a bridge, between effects request and effect handlers.
-
For effect requests, Effect provides syntax for invoking effect's operations. The syntax is defined using perform method.
-
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 members
Inherited classlikes
Base class for user-defined Proxy Interpreter for this effect.
Base class for user-defined Proxy Interpreter for this effect.
- Inherited from:
- CanInterpret
Base class for user-defined Stateful Interpreter for this effect.
Base class for user-defined Stateful Interpreter for this effect.
- Inherited from:
- CanInterpret
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
Types
Inherited types
Alias for Handler, specialized to eliminate this effect.
Alias for Handler, specialized to eliminate this effect.
- Inherited from:
- CanInterpret
Value members
Concrete methods
Inherited methods
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
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