diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 184969f4..0eee67b5 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -35,9 +35,15 @@ - [Effect-Oriented Programming](./effect-oriented-programming.md) - [Library Effects](./library-effects.md) - [Assert](./assert.md) + - [Clock](./clock.md) - [Console](./console.md) + - [Env](./env.md) + - [Exit](./exit.md) - [Http and Https](./http-and-https.md) + - [Logger](./logger.md) - [Process](./process.md) + - [Random](./random.md) + - [Sleep](./sleep.md) - [Modules](./modules.md) - [Declaring Modules](./declaring-modules.md) - [Using Modules](./using-modules.md) diff --git a/src/clock.md b/src/clock.md new file mode 100644 index 00000000..a98d4fbc --- /dev/null +++ b/src/clock.md @@ -0,0 +1,61 @@ +# Clock + +Flix provides `Clock` as a library effect for querying the current wall-clock +time. The `Clock` effect has a default handler, so no explicit `runWithIO` call +is needed in `main`. The key module is `Time.Clock`. + +## The Clock Effect + +The `Clock` effect has a single operation that returns the time since the epoch +in a given unit: + +```flix +pub eff Clock { + /// Returns a measure of time since the epoch in the given time unit `u`. + def currentTime(u: TimeUnit): Int64 +} +``` + +The `TimeUnit` enum determines the granularity of the result: + +```flix +pub enum TimeUnit with Eq, ToString { + case Days, + case Hours, + case Microseconds, + case Milliseconds, + case Minutes, + case Nanoseconds, + case Seconds +} +``` + +## Reading the Current Time + +The simplest use of `Clock` is to read the current time and print it: + +```flix +use Time.Clock +use Time.TimeUnit + +def main(): Unit \ { Clock, IO } = + let timestamp = Clock.currentTime(TimeUnit.Milliseconds); + println("${timestamp} ms since the epoch") +``` + +Because `Clock` has a default handler, the effect is handled automatically. + +## The `now` Function + +The `Clock.now` function is a shorthand for +`Clock.currentTime(TimeUnit.Milliseconds)`: + +```flix +use Time.Clock + +def main(): Unit \ { Clock, IO } = + let before = Clock.now(); + // ... do some work ... + let after = Clock.now(); + println("Elapsed: ${after - before} ms") +``` diff --git a/src/env.md b/src/env.md new file mode 100644 index 00000000..bc8fa81a --- /dev/null +++ b/src/env.md @@ -0,0 +1,110 @@ +# Env + +Flix provides `Env` as a library effect for accessing environment variables, +system properties, and platform information. The `Env` effect has a default +handler, so no explicit `runWithIO` call is needed in `main`. The key module is +`Sys.Env`. + +## The Env Effect + +The `Env` effect provides operations for reading the program's environment: + +```flix +pub eff Env { + /// Returns the arguments passed to the program via the command line. + def getArgs(): List[String] + + /// Returns a map of the current system environment. + def getEnv(): Map[String, String] + + /// Returns the value of the specified environment variable. + def getVar(name: String): Option[String] + + /// Returns the system property by name. + def getProp(name: String): Option[String] + + /// Returns the operating system name. + def getOsName(): Option[String] + + /// Returns the operating system architecture. + def getOsArch(): Option[String] + + /// Returns the operating system version. + def getOsVersion(): Option[String] + + /// Returns the file separator. + def getFileSeparator(): String + + /// Returns the path separator. + def getPathSeparator(): String + + /// Returns the system line separator. + def getLineSeparator(): String + + /// Returns the user's current working directory. + def getCurrentWorkingDirectory(): Option[String] + + /// Returns the default temp file path. + def getTemporaryDirectory(): Option[String] + + /// Returns the user's account name. + def getUserName(): Option[String] + + /// Returns the user's home directory. + def getUserHomeDirectory(): Option[String] + + /// Returns the number of virtual processors available to the JVM. + def getVirtualProcessors(): Int32 +} +``` + +## Directories and OS Info + +The simplest use of `Env` is to query the current directory, home directory, +or operating system: + +```flix +use Sys.Env + +def main(): Unit \ { Env, IO } = + let home = Env.getUserHomeDirectory(); + println("Home: ${home}"); + let cwd = Env.getCurrentWorkingDirectory(); + println("CWD: ${cwd}"); + let os = Env.getOsName(); + println("OS: ${os}") +``` + +## Environment Variables + +Use `getVar` to read a single environment variable, or `getEnv` to retrieve all +of them as a map: + +```flix +use Sys.Env + +def main(): Unit \ { Env, IO } = + let path = Env.getVar("PATH"); + println("PATH: ${path}"); + let all = Env.getEnv(); + println("Total env vars: ${Map.size(all)}") +``` + +## System Information + +The `Env` effect also exposes platform details such as the OS architecture and +the number of available processors: + +```flix +use Sys.Env + +def main(): Unit \ { Env, IO } = + let name = Env.getOsName(); + let arch = Env.getOsArch(); + let version = Env.getOsVersion(); + let cpus = Env.getVirtualProcessors(); + println("OS: ${name}"); + println("Arch: ${arch}"); + println("Ver: ${version}"); + println("CPUs: ${cpus}") +``` diff --git a/src/exit.md b/src/exit.md new file mode 100644 index 00000000..b67994b3 --- /dev/null +++ b/src/exit.md @@ -0,0 +1,34 @@ +# Exit + +Flix provides `Exit` as a library effect for terminating the program. The `Exit` +effect has a default handler, so no explicit `runWithIO` call is needed in +`main`. The key module is `Sys.Exit`. + +## The Exit Effect + +The `Exit` effect has a single operation that immediately stops the JVM with a +given exit code: + +```flix +pub eff Exit { + /// Immediately exits the JVM with the specified `exitCode`. + def exit(exitCode: Int32): Void +} +``` + +The return type `Void` indicates that `exit` never returns normally. + +## Exiting the Program + +The simplest use of `Exit` is to terminate with a specific exit code: + +```flix +use Sys.Exit + +def main(): Unit \ { Exit, IO } = + println("Goodbye!"); + Exit.exit(0) +``` + +A zero exit code conventionally signals success, while a non-zero code signals +an error. diff --git a/src/library-effects.md b/src/library-effects.md index 371c8be3..7d0ef5c5 100644 --- a/src/library-effects.md +++ b/src/library-effects.md @@ -4,9 +4,15 @@ Flix provides several built-in library effects for common I/O operations. These effects all have default handlers, so no explicit `runWithIO` is needed in `main`. -| Effect | Module | Description | -|---------------------------------|---------------------------|--------------------------------------------------------------------------------------------------| -| [Assert](./assert.md) | `Assert` | Runtime assertions (`assertTrue`, `assertEq`, etc.) with configurable handlers. | -| [Console](./console.md) | `Sys.Console` | Terminal I/O: reading input, printing to stdout/stderr, prompts, and menus. | -| [Http / Https](./http-https.md) | `Net.Http`, `Net.Https` | Sending HTTP requests with a fluent API, middleware (retries, rate limiting, circuit breakers). | -| [Process](./process.md) | `Sys.Process` | Spawning and managing OS processes. | +| Effect | Description | +|------------------------------------------------------|------------------------------------------------------------------------------------------------| +| [`Assert`](./assert.md) | Runtime assertions (`assertTrue`, `assertEq`, etc.) with configurable handlers. | +| [`Logger`](./logger.md) | Structured logging at five severity levels with filtering and collection. | +| [`Math.Random`](./random.md) | Generating pseudorandom numbers, with optional seeded determinism. | +| [`Net.Http` / `Net.Https`](./http-and-https.md) | Sending HTTP requests with a fluent API, middleware (retries, rate limiting, circuit breakers). | +| [`Sys.Console`](./console.md) | Terminal I/O: reading input, printing to stdout/stderr, prompts, and menus. | +| [`Sys.Env`](./env.md) | Accessing environment variables, system properties, and platform information. | +| [`Sys.Exit`](./exit.md) | Terminating the program with a specific exit code. | +| [`Sys.Process`](./process.md) | Spawning and managing OS processes. | +| [`Time.Clock`](./clock.md) | Querying the current wall-clock time in various units. | +| [`Time.Sleep`](./sleep.md) | Pausing the current thread with composable middleware (jitter, caps, logging). | diff --git a/src/logger.md b/src/logger.md new file mode 100644 index 00000000..b99aa46e --- /dev/null +++ b/src/logger.md @@ -0,0 +1,72 @@ +# Logger + +Flix provides `Logger` as a library effect for structured logging. The `Logger` +effect has a default handler, so no explicit `runWithIO` call is needed in +`main`. The key module is `Logger`. + +## The Logger Effect + +The `Logger` effect has a single operation that logs a message at a given +severity: + +```flix +pub eff Logger { + /// Logs the given message `m` at the given severity `s`. + def log(s: Severity, m: RichString): Unit +} +``` + +The `Severity` enum defines five levels, from lowest to highest: + +```flix +pub enum Severity with Eq, Order, ToString { + case Trace + case Debug + case Info + case Warn + case Fatal +} +``` + +## The Logger Module + +The `Logger` module provides convenience functions: + +```flix +mod Logger { + /// Logs the message `m` at the Trace level. + def trace(m: a): Unit \ Logger with Formattable[a] + + /// Logs the message `m` at the Debug level. + def debug(m: a): Unit \ Logger with Formattable[a] + + /// Logs the message `m` at the Info level. + def info(m: a): Unit \ Logger with Formattable[a] + + /// Logs the message `m` at the Warn level. + def warn(m: a): Unit \ Logger with Formattable[a] + + /// Logs the message `m` at the Fatal level. + def fatal(m: a): Unit \ Logger with Formattable[a] +``` + +> **Note:** The logging functions accept any type that implements the +> `Formattable` trait. Most standard types (`String`, `Int32`, `Bool`, etc.) +> implement `Formattable`, so plain values can be logged directly. The trait +> converts values into `RichString`, which supports styled terminal output +> (colors, bold, etc.). + +## Logging Messages + +The convenience functions accept any value that implements `Formattable`: + +```flix +def main(): Unit \ { Logger } = + Logger.info("Application started"); + Logger.debug("Loading configuration..."); + Logger.warn("Cache size exceeds threshold"); + Logger.fatal("Unrecoverable error") +``` + +The default handler prints each message to standard output with a colored +severity prefix, for example: `[Info] Application started`. diff --git a/src/process.md b/src/process.md index ef4d5e6c..658ff4e3 100644 --- a/src/process.md +++ b/src/process.md @@ -4,6 +4,70 @@ 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`. +## The Process Effect + +The `Process` effect supports spawning processes, accessing their I/O streams, +and waiting for completion: + +```flix +pub eff Process { + /// Executes `cmd` with `args`, working directory `cwd`, and environment `env`. + def execWithCwdAndEnv(cmd: String, args: List[String], + cwd: Option[String], env: Map[String, String]): + Result[IoError, ProcessHandle] + + /// Returns the exit value of the process `ph`. + def exitValue(ph: ProcessHandle): Result[IoError, Int32] + + /// Returns whether the process `ph` is alive. + def isAlive(ph: ProcessHandle): Result[IoError, Bool] + + /// Returns the PID of the process `ph`. + def pid(ph: ProcessHandle): Result[IoError, Int64] + + /// Returns the stdin stream of the process `ph`. + def stdin(ph: ProcessHandle): Result[IoError, StdIn] + + /// Returns the stdout stream of the process `ph`. + def stdout(ph: ProcessHandle): Result[IoError, StdOut] + + /// Returns the stderr stream of the process `ph`. + def stderr(ph: ProcessHandle): Result[IoError, StdErr] + + /// Stops the process `ph`. + def stop(ph: ProcessHandle): Result[IoError, Unit] + + /// Waits for the process `ph` to finish and returns its exit value. + def waitFor(ph: ProcessHandle): Result[IoError, Int32] + + /// Waits at most `time` (in the given `tUnit`) for the process `ph` to finish. + /// Returns `true` if the process exited, `false` if the timeout elapsed. + def waitForTimeout(ph: ProcessHandle, time: Int64, tUnit: TimeUnit): + Result[IoError, Bool] +} +``` + +## The Process Module + +The `S` module provides convenience functions built on the `Process` +effect: + +```flix +mod Process { + /// Executes the command `cmd` with the arguments `args`. + def exec(cmd: String, args: List[String]): + Result[IoError, ProcessHandle] \ Process + + /// Executes `cmd` with `args` and working directory `cwd`. + def execWithCwd(cmd: String, args: List[String], cwd: Option[String]): + Result[IoError, ProcessHandle] \ Process + + /// Executes `cmd` with `args` and environment variables `env`. + def execWithEnv(cmd: String, args: List[String], env: Map[String, String]): + Result[IoError, ProcessHandle] \ Process +} +``` + ## Executing a Command The simplest way to spawn an OS process is with `Process.exec`. It takes a @@ -86,13 +150,3 @@ def main(): Unit \ { Process, IO } = } ``` -## 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 diff --git a/src/random.md b/src/random.md new file mode 100644 index 00000000..b9b02db4 --- /dev/null +++ b/src/random.md @@ -0,0 +1,57 @@ +# Random + +Flix provides `Random` as a library effect for generating pseudorandom numbers. +The `Random` effect has a default handler, so no explicit `runWithIO` call is +needed in `main`. The key module is `Math.Random`. + +## The Random Effect + +The `Random` effect has two operations: + +```flix +pub eff Random { + /// Returns a pseudorandom 64-bit floating-point number in [0.0, 1.0]. + def randomFloat64(): Float64 + + /// Returns a pseudorandom 64-bit integer. + def randomInt64(): Int64 +} +``` + +## Generating Random Values + +The simplest use of `Random` is to generate a value and act on it: + +```flix +use Math.Random + +def main(): Unit \ { Random, IO } = + let flip = Random.randomFloat64() > 0.5; + if (flip) + println("heads") + else + println("tails") +``` + +Because `Random` has a default handler, the effect is handled automatically with +a fresh random seed. + +## Seeded Randomness + +The `runWithSeed` handler uses a fixed seed so that every run produces the same +sequence of random values. This is useful for reproducible tests and +benchmarks: + +```flix +use Math.Random + +def main(): Unit \ IO = + run { + let a = Random.randomFloat64(); + let b = Random.randomFloat64(); + println("a = ${a}, b = ${b}") + } with Random.runWithSeed(42i64) +``` + +Because a seeded random number generator is fully deterministic, `runWithSeed` +eliminates the `Random` effect without introducing `IO`. diff --git a/src/sleep.md b/src/sleep.md new file mode 100644 index 00000000..a82f4a2f --- /dev/null +++ b/src/sleep.md @@ -0,0 +1,113 @@ +# Sleep + +Flix provides `Sleep` as a library effect for pausing the current thread. The +`Sleep` effect has a default handler, so no explicit `runWithIO` call is needed +in `main`. The key module is `Time.Sleep`. + +## The Sleep Effect + +The `Sleep` effect has a single operation: + +```flix +pub eff Sleep { + /// Sleeps the current thread for the given duration `d`. + def sleep(d: Duration): Unit +} +``` + +Durations are created with helpers from the `Time.Duration` module, such as +`seconds`, `milliseconds`, `minutes`, and so on. + +## Basic Sleep + +The simplest use of `Sleep` is to pause for a fixed duration: + +```flix +use Time.Duration.seconds +use Time.Sleep + +def main(): Unit \ { Sleep, IO } = + println("Going to sleep..."); + Sleep.sleep(seconds(1)); + println("Woke up!") +``` + +## No-Op Sleep + +The `withNoOp` handler skips all sleeps, which is useful for testing code that +contains delays without actually waiting: + +```flix +use Time.Duration.seconds +use Time.Sleep + +def main(): Unit \ IO = + run { + println("Going to sleep..."); + Sleep.sleep(seconds(10)); + println("Woke up instantly!") + } with Sleep.withNoOp +``` + +Because `withNoOp` fully handles the `Sleep` effect, the result type no longer +includes `Sleep`. + +## Middleware + +The `Time.Sleep` module provides several middleware handlers that intercept and +transform sleep durations before forwarding to the underlying `Sleep` effect. +Middleware re-raises `Sleep`, so multiple layers can be composed. + +| Middleware | Description | +|---------------------|-----------------------------------------------------------| +| `withConstant` | Replaces every sleep duration with a fixed value. | +| `withScale` | Multiplies each duration by a factor. | +| `withMaxSleep` | Caps each individual sleep to a maximum. | +| `withMinSleep` | Ensures each sleep is at least a minimum. | +| `withMaxTotalSleep` | Caps the cumulative sleep across all calls to a budget. | +| `withJitter` | Adds random jitter (±factor) to each duration. | +| `withLogging` | Logs each sleep duration via the `Logger` effect. | +| `withCollect` | Collects all durations into a list instead of sleeping. | + +For example, `withMaxSleep` caps each sleep to a maximum and `withLogging` logs +each duration: + +```flix +use Time.Duration.{milliseconds, seconds} +use Time.Sleep + +def main(): Unit \ { Logger, Sleep, IO } = + run { + println("Sleeping for 2 seconds (capped to 500ms)..."); + Sleep.sleep(seconds(2)); + println("Done!") + } with Sleep.withMaxSleep(milliseconds(500)) + with Sleep.withLogging +``` + +## Composing Middleware + +Because each middleware re-raises `Sleep`, they stack naturally. The following +example adds ±20% random jitter to each sleep and logs the resulting durations: + +```flix +use Math.Random +use Time.Duration.{seconds} +use Time.Sleep + +/// Composes `withJitter` and `withLogging` to add ±20% random jitter +/// to sleep durations and log each sleep via the `Logger` effect. +def main(): Unit \ { Logger, Random, Sleep, IO } = + run { + println("Sleeping 3 times with ±20% jitter..."); + Sleep.sleep(seconds(1)); + Sleep.sleep(seconds(2)); + Sleep.sleep(seconds(3)); + println("Done!") + } with Sleep.withJitter(0.2) + with Sleep.withLogging +``` + +The order matters: `withJitter` intercepts the original durations, applies +jitter, and re-raises `Sleep`. The `withLogging` handler then sees the +jittered durations.