Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
- [Effect-Oriented Programming](./effect-oriented-programming.md)
- [Library Effects](./library-effects.md)
- [Http and Https](./http-and-https.md)
- [Process](./process.md)
- [Modules](./modules.md)
- [Declaring Modules](./declaring-modules.md)
- [Using Modules](./using-modules.md)
Expand Down
121 changes: 0 additions & 121 deletions src/library-effects.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,6 @@
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](https://en.wikipedia.org/wiki/Unix_time):

```flix
eff Clock {
/// Returns a measure of time since the epoch in the given time unit `u`.
def currentTime(u: TimeUnit): Int64
}
```

```flix
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`

```flix
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:
Expand Down Expand Up @@ -189,90 +155,3 @@ def main(): Unit \ IO =
}
} with FileWriteWithResult.runWithIO
```

## Logger

Flix defines a `Logger` effect for logging messages:

```flix
eff Logger {
/// Logs the given message `m` at the given severity `s`.
def log(s: Severity, m: RichString): Unit
}
```

The `Logger` companion module provides several convenience functions:

```flix
mod Logger {
/// Logs the message `m` at the `Trace` level.
def trace(m: a): Unit \ (Logger + Formattable.Aef[a]) with Formattable[a]

/// Logs the message `m` at the `Debug` level.
def debug(m: a): Unit \ (Logger + Formattable.Aef[a]) with Formattable[a]

/// Logs the message `m` at the `Info` level.
def info(m: a): Unit \ (Logger + Formattable.Aef[a]) with Formattable[a]

/// Logs the message `m` at the `Warn` level.
def warn(m: a): Unit \ (Logger + Formattable.Aef[a]) with Formattable[a]

/// Logs the message `m` at the `Fatal` level.
def fatal(m: a): Unit \ (Logger + Formattable.Aef[a]) with Formattable[a]
}
```

### Example: Using `Logger`

```flix
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:

```flix
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]): Result[IoError, ProcessHandle]

// ... additional operations (exitValue, isAlive, pid, stop, waitFor, waitForTimeout) ...
}
```

The `Process` companion module provides several convenience functions:

```flix
/// 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`

```flix
def main(): Unit \ IO =
run {
match ProcessWithResult.exec("ls", Nil) {
case Result.Ok(_) => ()
case Result.Err(err) => println("Unable to execute process: ${err}")
}
} with ProcessWithResult.runWithIO
```
98 changes: 98 additions & 0 deletions src/process.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Process

Flix provides `Process` as a library effect for spawning and managing OS
processes. The `Process` effect has a default handler, so no explicit
`runWithIO` call is needed in `main`. The key module is `Sys.Process`.

## Executing a Command

The simplest way to spawn an OS process is with `Process.exec`. It takes a
command and a list of arguments, and returns a
`Result[IoError, ProcessHandle]`:

```flix
use Sys.Process

def main(): Unit \ { Process, IO } =
match Process.exec("java", "-version" :: Nil) {
case Result.Ok(_) => println("Process started successfully.")
case Result.Err(err) => println("Unable to execute process: ${err}")
}
```

## Reading Process Output

After spawning a process, you can access its output streams with
`Process.stdout` and `Process.stderr`. The returned `StdOut` and `StdErr` types
implement `Readable`, so you can read bytes from them:

```flix
use Sys.Process

def main(): Unit \ { Process, IO } = region rc {
match Process.exec("java", "-version" :: Nil) {
case Result.Err(err) => println("exec failed: ${err}")
case Result.Ok(ph) =>
match Process.stderr(ph) {
case Result.Err(err) => println("stderr failed: ${err}")
case Result.Ok(err) =>
let buf = Array.repeat(rc, 1024, (0i8: Int8));
match Readable.read(buf, err) {
case Result.Err(e) => println("read failed: ${e}")
case Result.Ok(n) => println("Read ${n} bytes from stderr.")
}
}
}
}
```

> **Note:** `java -version` writes to stderr, not stdout.

## Waiting for Completion

Use `Process.waitFor` to block until a process exits. It returns the exit code
as an `Int32`:

```flix
use Sys.Process

def main(): Unit \ { Process, IO } =
match Process.exec("java", "-version" :: Nil) {
case Result.Err(err) => println("exec failed: ${err}")
case Result.Ok(ph) =>
match Process.waitFor(ph) {
case Result.Err(err) => println("waitFor failed: ${err}")
case Result.Ok(code) => println("Process exited with code: ${code}")
}
}
```

## Working Directory and Environment

`Process.execWithCwd` spawns a process with a specific working directory.
`Process.execWithEnv` passes additional environment variables:

```flix
use Sys.Process

def main(): Unit \ { Process, IO } =
match Process.execWithCwd("java", "-version" :: Nil, Some("/tmp")) {
case Result.Ok(_) => println("execWithCwd succeeded.")
case Result.Err(err) => println("execWithCwd failed: ${err}")
};
match Process.execWithEnv("java", "-version" :: Nil, Map#{"MY_VAR" => "hello"}) {
case Result.Ok(_) => println("execWithEnv succeeded.")
case Result.Err(err) => println("execWithEnv failed: ${err}")
}
```

## Other Operations

The `Sys.Process` module provides several additional operations:

- `Process.isAlive(ph)` — returns `true` if the process is still running
- `Process.pid(ph)` — returns the process ID
- `Process.stop(ph)` — terminates the process
- `Process.stdin(ph)` — returns the process's standard input stream (implements `Writable`)
- `Process.exitValue(ph)` — returns the exit code without blocking (fails if the process is still running)
- `Process.waitForTimeout(ph, time, tUnit)` — blocks until the process exits or the timeout expires