From 5e54c1cd8b425620e09c776d9ab408d65fd59019 Mon Sep 17 00:00:00 2001 From: Magnus Madsen Date: Tue, 10 Mar 2026 12:54:49 +0100 Subject: [PATCH] feat: add console.md Co-Authored-By: Claude Opus 4.6 --- src/SUMMARY.md | 1 + src/console.md | 105 ++++++++++++++++++++++++++++ src/library-effects.md | 155 ----------------------------------------- 3 files changed, 106 insertions(+), 155 deletions(-) create mode 100644 src/console.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 54f24769..184969f4 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) - [Assert](./assert.md) + - [Console](./console.md) - [Http and Https](./http-and-https.md) - [Process](./process.md) - [Modules](./modules.md) diff --git a/src/console.md b/src/console.md new file mode 100644 index 00000000..e7525d00 --- /dev/null +++ b/src/console.md @@ -0,0 +1,105 @@ +# Console + +Flix provides `Console` as a library effect for terminal I/O. The `Console` +effect has a default handler, so no explicit `runWithIO` call is needed in +`main`. The key module is `Sys.Console`. + +## The Console Effect + +The `Console` effect supports reading from standard input and writing to +standard output and standard error: + +```flix +pub 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 +} +``` + +## The Console Module + +The `Console` module provides several higher-level functions built on the +`Console` effect: + +```flix +mod Sys.Console { + /// Prints prompt `p`, reads a line, and returns `default` if the input is empty. + def readlnWithDefault(p: a, default: String): String \ Console + + /// Prints prompt `p`, reads a line, and applies `f` to the input. + /// Re-prompts on `Err(msg)`, returns `v` on `Ok(v)`. + def readlnWith(p: a, f: String -> Result[String, b]): b \ Console + + /// Prints prompt `p` with a yes/no hint and reads a boolean answer. + /// Empty or unrecognized input returns `default`. + def confirm(p: a, default: {default = Bool}): Bool \ Console + + /// Prints prompt `p` with a numbered list of choices and reads a selection. + /// Returns `None` if the input is invalid. + def pick(p: a, choices: List[b]): Option[b] \ Console + + /// Like `pick`, but re-prompts until the user makes a valid selection. + def pickWith(p: a, choices: List[b]): b \ Console +} +``` + +## Basic Console I/O + +The simplest use of `Console` is to print a prompt, read input, and respond: + +```flix +use Sys.Console + +def main(): Unit \ Console = + Console.print("What is your name? "); + let name = Console.readln(); + Console.println("Hello ${name}!") +``` + +## Confirmed Input + +The `Console.confirm` function asks a yes/no question and returns a `Bool`. You +can supply a default value that is used when the user presses Enter without +typing anything: + +```flix +use Sys.Console + +def main(): Unit \ Console = + let proceed = Console.confirm("Deploy to production?", default = true); + if (proceed) + Console.println("Deploying...") + else + Console.println("Aborted.") +``` + +## Validated Input + +The `Console.readlnWith` function repeatedly prompts the user until the input +passes a validator. The validator returns `Ok(value)` on success or +`Err(message)` to re-prompt: + +```flix +use Sys.Console + +def main(): Unit \ Console = + let n = Console.readlnWith("Enter a number (1-10): ", s -> + match Int32.fromString(s) { + case Some(i) if i >= 1 and i <= 10 => Ok(i) + case _ => Err("Please enter a number between 1 and 10.") + } + ); + Console.println("You entered: ${n}") +``` diff --git a/src/library-effects.md b/src/library-effects.md index 8b691a44..b9fa3fa6 100644 --- a/src/library-effects.md +++ b/src/library-effects.md @@ -1,157 +1,2 @@ # Library Effects -The Flix Standard Library comes with a collection of algebraic effects and -handlers. - -## Console - -Flix defines a `Console` effect to read from and write to shell: - -```flix -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` - -```flix -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: - -```flix -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` - -```flix -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: - -```flix -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` - -```flix -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 -```