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
}
mod Clock {
    /// Runs `f` handling the `Clock` effect using `IO`.
    def runWithIO(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 effect in the standard library comes with handle and runWithIO functions.

Example: Using Clock

def main(): Unit \ IO = 
    run {
        let timestamp = Clock.currentTime(TimeUnit.Milliseconds);
        println("${timestamp} ms since the epoc")
    } with Clock.runWithIO

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
}

Example: Using Console

def main(): Unit \ IO = 
    run {
        Console.println("Please enter your name: ");
        let name = Console.readln();
        Console.println("Hello ${name}")
    } with Console.runWithIO

FileReadWithResult

Flix defines a FileReadWithResult effect to read from the file system:

eff FileReadWithResult {
    /// Returns `true` if the given file `f` exists.
    def exists(f: String): Result[IoError, Bool]

    /// Returns `true` is the given file `f` is a directory.
    def isDirectory(f: String): Result[IoError, Bool]

    /// Returns `true` if the given file `f` is a regular file.
    def isRegularFile(f: String): Result[IoError, Bool]

    /// Returns `true` if the given file `f` is readable.
    def isReadable(f: String): Result[IoError, Bool]

    /// Returns `true` if the given file `f` is a symbolic link.
    def isSymbolicLink(f: String): Result[IoError, Bool]

    /// Returns `true` if the given file `f` is writable.
    def isWritable(f: String): Result[IoError, Bool]

    /// Returns `true` if the given file `f` is executable.
    def isExecutable(f: String): Result[IoError, Bool]

    /// Returns the last access time of the given file `f` in milliseconds since the epoch.
    def accessTime(f: String): Result[IoError, Int64]

    /// Returns the creation time of the given file `f` in milliseconds since the epoch.
    def creationTime(f: String): Result[IoError, Int64]

    /// Returns the last-modified timestamp of the given file `f` in milliseconds since the epoch.
    def modificationTime(f: String): Result[IoError, Int64]

    /// Returns the size of the given file `f` in bytes.
    def size(f: String): Result[IoError, Int64]

    /// Returns a string of all lines in the given file `f`.
    def read(f: String): Result[IoError, String]

    /// Returns a list of all lines in the given file `f`.
    def readLines(f: String): Result[IoError, List[String]]

    /// Returns a vector of all the bytes in the given file `f`.
    def readBytes(f: String): Result[IoError, Vector[Int8]]

    /// Returns a list with the names of all files and directories in the given directory `d`.
    def list(f: String): Result[IoError, List[String]]
}

Example: Using FileReadWithResult

def main(): Unit \ IO = 
    run {
        match FileReadWithResult.readLines("Main.flix") {
            case Result.Ok(lines) => 
                lines |> List.forEach(println)
            case Result.Err(err) => 
                println("Unable to read file. Error: ${err}")
        }
    } with FileReadWithResult.runWithIO

FileWriteWithResult

Flix defines a FileWriteWithResult effect to write to the file system:

eff FileWriteWithResult {
    /// Writes `str` to the given file `f`.
    def write(data: {str = String}, f: String): Result[IoError, Unit]

    /// Writes `lines` to the given file `f`.
    def writeLines(data: {lines = List[String]}, f: String): Result[IoError, Unit]

    /// Writes `data` to the given file `f`.
    def writeBytes(data: Vector[Int8], f: String): Result[IoError, Unit]

    /// Appends `str` to the given file `f`.
    def append(data: {str = String}, f: String): Result[IoError, Unit]

    /// Appends `lines` to the given file `f`.
    def appendLines(data: {lines = List[String]}, f: String): Result[IoError, Unit]

    /// Appends `data` to the given file `f`.
    def appendBytes(data: Vector[Int8], f: String): Result[IoError, Unit]

    /// Truncates the given file `f`.
    def truncate(f: String): Result[IoError, Unit]

    /// Creates the directory `d`.
    def mkDir(d: String): Result[IoError, Unit]

    /// Creates the directory `d` and all its parent directories.
    def mkDirs(d: String): Result[IoError, Unit]

    /// Creates a new temporary directory with the given prefix.
    def mkTempDir(prefix: String): Result[IoError, String]
}

Example: Using FileWriteWithResult

def main(): Unit \ IO = 
    run {
        let data = List#{"Hello", "World"};
        match FileWriteWithResult.writeLines(lines = data, "data.txt"){
            case Result.Ok(_)    => ()
            case Result.Err(err) => 
                println("Unable to write file. Error: ${err}")
        }
    } with FileWriteWithResult.runWithIO

HttpWithResult

Flix defines a HttpWithResult effect to communicate over HTTP:

eff HttpWithResult {
    def request(method: String, 
                url: String, 
                headers: Map[String, List[String]], 
                body: Option[String])
        : Result[IoError, Http.Response]
}

The HttpWithResult companion module provides several convenience functions:

mod HttpWithResult {
    /// Send a `GET` request to the given `url` with the given `headers` 
    /// and wait for the response.
    def get(url: String, headers: Map[String, List[String]])
        : Result[IoError, Http.Response] \ HttpWithResult

    /// Send a `POST` request to the given `url` with the given `headers` 
    /// and `body` and wait for the response.
    def post(url: String, headers: Map[String, List[String]], body: String)
        : Result[IoError, Http.Response] \ HttpWithResult

    /// Send a `PUT` request to the given `url` with the given `headers` 
    /// and `body` and wait for the response.
    def put(url: String, headers: Map[String, List[String]], body: String)
        : Result[IoError, Http.Response] \ HttpWithResult
}

Example: Using HttpWithResult

def main(): Unit \ {Net, IO} =
    run {
        match HttpWithResult.get("http://example.com/", Map.empty()) {
            case Result.Ok(response) =>
                let body = Http.Response.body(response);
                println(body)
            case Result.Err(e) => println(e)
        }
    } with HttpWithResult.runWithIO

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]
}

Example: Using Logger

def main(): Unit \ IO =
    run {
        Logger.info("Hello");
        Logger.warn("World")
    } with Logger.runWithIO

ProcessWithResult

Flix defines a ProcessWithResult effect for running commands outside of the JVM:

eff ProcessWithResult {
    /// Executes the command `cmd` with the arguments `args`, by the path `cwd` 
    /// and with the environment `env`.
    def execWithCwdAndEnv(cmd: String, args: List[String], 
                          cwd: Option[String], 
                          env: Map[String, String]): ProcessHandle
}

The Process companion module provides several convenience functions:

/// Executes the command `cmd` with the arguments `args`.
pub def exec(cmd: String, args: List[String])
    : Result[IoError, ProcessHandle] \ ProcessWithResult

/// Executes the command `cmd` with the arguments `args`, by the path `cwd`.
def execWithCwd(cmd: String, args: List[String], cwd: Option[String])
    : Result[IoError, ProcessHandle] \ ProcessWithResult

/// Executes the command `cmd` with the arguments `args` and with 
/// the environment `env`.
def execWithEnv(cmd: String, args: List[String], env: Map[String, String])
    : Result[IoError, ProcessHandle] \ ProcessWithResult

Example: Using ProcessWithResult

def main(): Unit \ {Exec, IO} =
    run {
        match ProcessWithResult.exec("ls", Nil) {
            case Result.Ok(_)    => ()
            case Result.Err(err) => println("Unable to execute process: ${err}")
        }
    } with ProcessWithResult.runWithIO

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
}

Example: Using Random

def main(): Unit \ {NonDet, IO} =
    run {
        let flip = Random.randomBool();
        if (flip) 
            println("heads")
        else 
            println("tails")
    } with Random.runWithIO

Running Multiple Effects

We can easily combine multiple effects and run them:

def main(): Unit \ {NonDet, IO} =
    run {
        Console.println("Please enter your name:");
        let name = Console.readln();
        let flip = Random.randomBool();
        if (flip) 
            Console.println("Pleased to meet you, ${name}")
        else 
            Console.println("Oh no, not you, ${name}")
    } with Console.runWithIO
      with Random.runWithIO