Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Default Handlers

Flix supports default handlers which means that an effect can declare a handler that translates the effect into the IO effect. This allows main (and any method annotated with @Test) to use that effect without explicitly providing a handler in a run-with block.

For example, we can write:

def main(): Unit \ {Clock, Env, Logger} = 
    let ts = Clock.currentTime(TimeUnit.Milliseconds);
    let os = Env.getOsName();
    Logger.info("UNIX Timestamp:   ${ts}");
    Logger.info("Operating System: ${os}")

which the Flix compiler translates to:

def main(): Unit \ IO = 
    run {
        let ts = Clock.currentTime(TimeUnit.Milliseconds);
        let os = Env.getOsName();
        Logger.info("UNIX Timestamp:   ${ts}");
        Logger.info("Operating System: ${os}")
    } with Clock.runWithIO
      with Env.runWithIO
      with Logger.runWithIO

That is, the Flix compiler automatically inserts calls to Clock.runWithIO, Env.runWithIO, and Logger.runWithIO which are the default handlers for their respective effects.

For example, Clock.runWithIO is declared as:

@DefaultHandler
pub def runWithIO(f: Unit -> a \ ef): a \ (ef - Clock) + IO = ...

A default handler is declared using the @DefaultHandler annotation. Each effect may have at most one default handler, and it must reside in the companion module of that effect.

A default handler must have a signature of the form:

def runWithIO(f: Unit -> a \ ef): a \ (ef - E) + IO

where E is the name of the effect.

We can use effects with default handlers in tests. For example:

@Test
def myTest01(): Unit \ {Assert, Logger} = 
    Logger.info("Running test!");
    Assert.assertEq(expected = 42, 42)