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).