Library Effects
The Flix Standard Library comes with a collection of algebraic effects and handlers.
Clock
Flix defines a Clock
effect to access the time since the UNIX epoch:
eff Clock {
/// Returns a measure of time since the epoch in the given time unit `u`.
def currentTime(u: TimeUnit): Int64
}
The Clock
companion module also defines the functions run
and handle
:
mod Clock {
/// Runs `f` handling the `Clock` effect using `IO`.
def run(f: Unit -> a \ ef): a \ (ef - {Clock} + IO)
/// Returns `f` with the `Clock` effect handled using `IO`.
def handle(f: a -> b \ ef): a -> b \ (ef - {Clock} + IO)
}
Every standard library effect comes with run
and handle
functions.
Console
Flix defines a Console
effect to read from and write to shell:
eff Console {
/// Reads a single line from the console.
def readln(): String
/// Prints the given string `s` to the standard out.
def print(s: String): Unit
/// Prints the given string `s` to the standard err.
def eprint(s: String): Unit
/// Prints the given string `s` to the standard out followed by a new line.
def println(s: String): Unit
/// Prints the given string `s` to the standard err followed by a new line.
def eprintln(s: String): Unit
}
Logger
Flix defines a Logger
effect for logging messages:
eff Logger {
/// Logs the given message `m` at the given severity `s`.
def log(s: Severity, m: String): Unit
}
The Logger
companion module provides several convenience functions:
mod Logger {
/// Logs the message `m` at the `Trace` level.
def trace(m: a): Unit \ Logger with ToString[a]
/// Logs the message `m` at the `Debug` level.
def debug(m: a): Unit \ Logger with ToString[a]
/// Logs the message `m` at the `Info` level.
def info(m: a): Unit \ Logger with ToString[a]
/// Logs the message `m` at the `Warn` level.
def warn(m: a): Unit \ Logger with ToString[a]
/// Logs the message `m` at the `Fatal` level.
def fatal(m: a): Unit \ Logger with ToString[a]
}
Process
Flix defines a Process
effect for running commands outside of the JVM:
eff Process {
/// Immediately executes the command `cmd` passing the arguments `args`.
def exec(cmd: String, args: List[String]): Unit
}
Random
Flix defines a Random
effect for the generation of random values:
eff Random {
/// Returns a pseudorandom boolean.
def randomBool(): Bool
/// Returns a pseudorandom 32-bit floating-point number.
def randomFloat32(): Float32
/// Returns a pseudorandom 64-bit floating-point number.
def randomFloat64(): Float64
/// Returns a pseudorandom 32-bit integer.
def randomInt32(): Int32
/// Returns a pseudorandom 64-bit integer.
def randomInt64(): Int64
/// Returns a Gaussian distributed 64-bit floating point number.
def randomGaussian(): Float64
}
Running Functions with Algebraic Effects
Every Flix Standard Library effects defines two functions: run
and handle
in
the companion module of the effect. We can use these functions to re-interpret
the effect in IO
. That is, to make the effect happen.
For example, if a we have a function that uses the Clock
effect:
def getEpoch(): Int64 \ Clock = Clock.now()
We can run it by writing:
def main(): Unit \ IO =
println(Clock.run(getEpoch))
Or we can handle it and then run the returned function:
def main(): Unit \ IO =
let f = Clock.handle(getEpoch);
println(f())
If a function has multiple effects:
def greet(name: String): Unit \ {Clock, Console} = ...
If a function has more than one effect we cannot use run
. Instead, we must
handle each effect, and call the result. For example, we can use Clock.handle
and Console.handle
:
def main(): Unit \ IO =
let f = Clock.handle(
Console.handle(
() -> greet("Mr. Bond")));
println(f())
We have to write () -> greet("Mr. Bond")
because handle
expects a function.
Using App
We have seen how to handle multiple effects using the library defined handlers.
While being explicit about which handlers are used is good programming style, it
can become cumbersome. Hence, for convenience, Flix has a App.runAll
function
which can handle all effects in the standard library:
def main(): Unit \ IO =
App.runAll(() -> greet("Mr. Bond"))