Relative Effects - lrytz/efftp GitHub Wiki

The effect of a higher-order function depends on the effect of its argument: for example, the method map which applies a function to all elements of a collection has the effect of the function that is passed into it.

Such functions are said to be effect-polymorphic, and their effects are annotated using relative effect annotations:

def h(f: Int => Int): Int @pure(f) = f(1)

The higher-order method h has a relative effect annotation @pure(f) which states that h is pure, but it has the effect of its argument function f.

When invoking the method h, the effect of the invocation depends on the argument function:

def pure  : Int @pure     = h(x => x + 1)
def withIo: Int @pure @io = h(x => {println(x); x + 1})

Relative effect annotations always apply to every effect domain.

Relative Effects for arbitrary Types

Relative effect annotations are not tied to function types in any way, they work on parameters of arbitrary type:

def log(a: Any): Unit @pure(a.toString) @io = {
  if (shouldLog) println(a.toString)
}

For methods that take parameters, the arguments used in the @pure annotation are ignored. For convenience, the package object annotation.effects defines a value % of type Nothing which can be used.

def f(a: A): B @pure(a.foo(%)) = {
  a.foo(1)
}

For annotations of the form @pure(a) where no method of a is selected, the method named apply is used. Therefore effect-polymorphism with function parameters can be simply annotated @pure(f), which is equivalent to @pure(f.apply(%, %, ...)).

Further Reading

More details on relative effects can be found in the technical report "Relative Effect Declarations for Lightweight Effect-Polymorphism" (pdf).