From d4cc66e90be7d1ede0d7147f74f5e339aae247b2 Mon Sep 17 00:00:00 2001 From: Magnus Madsen Date: Sun, 8 Mar 2026 17:23:23 +0100 Subject: [PATCH] feat: add process.md (#298) Add a dedicated documentation page for the Process effect (Sys.Process), covering exec, reading output, waiting for completion, working directory and environment, and other operations. Remove outdated Clock, Logger, and ProcessWithResult sections from library-effects.md. Co-Authored-By: Claude Opus 4.6 --- src/SUMMARY.md | 1 + src/library-effects.md | 121 ----------------------------------------- src/process.md | 98 +++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+), 121 deletions(-) create mode 100644 src/process.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 4176710c..e4e02ede 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -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) diff --git a/src/library-effects.md b/src/library-effects.md index 50fafdbd..8b691a44 100644 --- a/src/library-effects.md +++ b/src/library-effects.md @@ -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: @@ -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 -``` diff --git a/src/process.md b/src/process.md new file mode 100644 index 00000000..ef4d5e6c --- /dev/null +++ b/src/process.md @@ -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