Bidirectional Effects

Typically, effect’s operations are defined so that they request exactly 1 effect: the very effect they belong to. This self-reference in Turbolift is named ThisEffect:

import turbolift.{Signature, Effect}

trait FileSystemSignature extends Signature:
  def readFile(path: String): String !@! ThisEffect
  def writeFile(path: String, contents: String): Unit !@! ThisEffect

// Boilerplate:
case object FileSystem extends Effect[FileSystemSignature] with FileSystemSignature:
  final override def readFile(path: String) = perform(_.readFile(path))
  final override def writeFile(path: String, contents: String) = perform(_.writeFile(path, contents))

However, in Turbolift it’s possible for an operation to also request other effects, in addition to the self-referenced one.

Let’s have such another effect defined:

import turbolift.effects.Error

case object FileNotFound extends Error[String]
type FileNotFound = FileNotFound.type

Now we can modify readFile definition, so that it requests FileNotFound effect:

import turbolift.{Signature, Effect}

trait FileSystemSignature extends Signature:
  def readFile(path: String): String !@! (ThisEffect & FileNotFound)
  def writeFile(path: String, contents: String): Unit !@! ThisEffect

// Boilerplate:
// (unchanged, except omitted types)
case object FileSystem extends Effect[FileSystemSignature] with FileSystemSignature:
  final override def readFile(path: String) = perform(_.readFile(path))
  final override def writeFile(path: String, contents: String) = perform(_.writeFile(path, contents))

This establishes public dependency of FileSystem effect, on FileNotFound effect.

Public dependencies differ from private dependencies of interpreters/handlers:

  • Public dependency manifests early, in the interface (Signature). Private dependency manifests late, in the implementation (Interpreter delegating to other effect), or application (Handler with unsatisfied dependency).

  • Public dependency doesn’t constraint client code with respect to scopes of both effects (depenent & depenency) and the order of their handling. In private dependency, the depenent effect must be handled after all of its dependencies are handled.

  • Public dependencies can be cyclic.

See also: