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