# Part 1: Getting Started Source: https://dovetaillang.org/book/getting-started/ ## 1.1 Introduction to Dovetail ### What is Dovetail? Dovetail is a new high-level programming language designed for writing business-logic heavy applications. It compiles to WebAssembly (specifically WasmGC), prioritizing fast compilation and great tooling over raw runtime performance. Dovetail is: - **Expression-based** - Everything is an expression that returns a value - **Strongly typed** - A powerful type system catches errors at compile time - **Layout-sensitive** - Uses indentation instead of curly braces and semicolons - **Functional-first** - Encourages immutability and pure functions, while supporting imperative and object-oriented styles - **Garbage collected** - Memory management is handled automatically - **Async-first** - Built-in async/await with elegant error propagation - **No exceptions** - Errors are values using `Result` and `Option` types with railway-oriented programming ### Why Another Language? Rust is a modern language for system-level programming, written from the ground up with modern concepts and a new ecosystem. It didn't inherit old baggage from VMs or C/C++. But we don't have such a language for high-level programming. **TypeScript** - A great language with modern concepts, but it's based on JavaScript with the JavaScript ecosystem and a lot of baggage: - Both async and callbacks - Exceptions - Nulls - Inconsistency across the ecosystem (typed/untyped libraries) - No ad-hoc polymorphism (traits, typeclasses) **Python** - Popular but problematic for large codebases: - Exceptions - Nulls - Untyped by default - Both async and sync APIs - Inconsistency in the ecosystem (is it typed or not? are all dependencies typed?) - No ad-hoc polymorphism **Java** - Mature but carrying decades of baggage: - No async/await - Type erasure - Nulls - Exceptions - No standard across tools (formatter, build tool) - No tuples - No ad-hoc polymorphism **Kotlin** - Modern features on top of Java, but still inherits Java's baggage: - JVM type erasure - Exceptions - Null safety is incomplete - the JVM isn't null-safe, so you can encounter nulls at runtime from Java interop - Standard library split between Kotlin and Java APIs - No ad-hoc polymorphism **Scala** - Powerful type system but with significant drawbacks: - Inherits Java/JVM baggage - Slow compilation times - High learning curve - Null safety is incomplete - JVM interop can introduce nulls at runtime - Has ad-hoc polymorphism (implicits/givens), but at the cost of compilation performance and complexity **Go** - Simple and fast to compile, but a limited type system: - No sum types (discriminated unions) - Limited generics (added recently, still maturing) - Nulls (nil pointers) - No async/await and no way to extend the language with custom coroutines - Interfaces provide basic ad-hoc polymorphism, but more limited than traits (no associated types, can't extend types you don't own) **C#** - Modern features but carrying .NET legacy: - Standard library supports multiple models for backward compatibility - Nullable reference types are opt-in and incomplete - Exceptions - No ad-hoc polymorphism **Rust** - Amazing but low-level: - No garbage collection - Not optimized for compile times - Steep learning curve for business logic developers **What is ecosystem baggage?** When we say "ecosystem baggage," we mean: - Legacy constructs in the language that exist for backward compatibility - Standard libraries supporting multiple patterns (sync/async, exceptions/results, nullable/non-nullable) - Half-baked null safety solutions where the runtime can still produce nulls even when the type system says otherwise - Inconsistent tooling where the community is split across multiple formatters, build tools, and conventions **We need a new language** - one with modern concepts and no ecosystem baggage. Dovetail is that language: garbage collected, fast to compile, with a clean type system, proper error handling, and consistent tooling from day one. ### Design Philosophy and Principles Dovetail is built around several core principles: **Fast Compilation** Compilation should feel instant. Dovetail prioritizes fast feedback loops over maximum runtime optimization. The target competition is Python and Node.js: the goal is to pair their productive development experience with the performance opportunities of compiled WebAssembly. **Beautiful Code** Dovetail is designed to produce readable code that flows like English: - String interpolation without prefixes: `"Hello, $name!"` - Named arguments for clarity: `createUser(name = "John", age = 30)` - Layout-sensitive syntax eliminates visual noise - No cryptic abbreviations - `function` not `fn`, `Comparable` not `Ord`, `Equatable` not `Eq` - Keywords instead of operators where it improves readability (`and`, `or`, `not`) **Strong Type System** The type system helps eliminate bugs: - No null values - use `Option` instead - Discriminated unions (enums) for modeling data - Traits for shared behavior - Result types for error handling - no exceptions **Great Tooling** Dovetail includes excellent tools from day one: - Integrated package manager and build tool - Language Server Protocol (LSP) support - Built-in formatter with `dovetail fmt` and format-on-save in VS Code - Built-in skills for coding agents - Guidance for writing idiomatic Dovetail, applying best practices, and using specialized sub-agents for code review (planned for the public release) - REPL for exploration (planned) ### Target Platform (WasmGC) Dovetail compiles to WebAssembly with Garbage Collection (WasmGC). This provides: - **Portability** - Target hosts that support WasmGC and the WASI interfaces your program uses - **Performance** - Designed for near-native execution through WebAssembly and Wasmtime. - **Safety** - WebAssembly's sandboxed execution model - **Interoperability** - Use WASI for system access Dovetail targets WebAssembly 3.0 and WASI Preview 3. `dovetail run` uses Wasmtime for local execution; you can also deploy the compiled component to other hosts that support the required WebAssembly features and WASI interfaces. Dovetail uses traits for generic behavior and interfaces for contracts that can also be stored and passed as values. See [Interfaces and Interface Types](https://dovetaillang.org/book/type-system.md#611-interfaces-and-interface-types) and [Traits and Implementations](https://dovetaillang.org/book/traits.md). ### Comparison with Other Languages This is a high-level comparison of the design choices motivating Dovetail. Fast compilation is a design goal, not a benchmark result; ecosystem consistency is the direction we are building toward. | Feature | Dovetail | TypeScript | Python | Java | Kotlin | Go | C# | Rust | |---------|--------|------------|--------|------|--------|-----|-----|------| | No Nulls | Yes | No | No | No | Partial | No | Partial | Yes | | No Exceptions | Yes | No | No | No | No | Yes | No | Yes | | Consistent Async | Yes | No | No | No | Yes | No | Yes | Yes | | Ad-hoc Polymorphism (Traits) | Yes | No | No | No | No | Partial | No | Yes | | Sum Types | Yes | Yes | No | No | Yes | No | No | Yes | | Layout-Sensitive | Yes | No | Yes | No | No | No | No | No | | Garbage Collected | Yes | Yes | Yes | Yes | Yes | Yes | Yes | No | | Fast Compilation | Yes | Yes | Yes | No | No | Yes | No | No | | No Ecosystem Baggage | Yes | No | No | No | No | No | No | Yes | --- ## 1.2 Installation and Setup ### Installing prebuilt binaries No Rust toolchain is needed. The CLI includes the runtime used by `dovetail run`. On macOS (Intel or Apple Silicon) and Linux (x86_64 or ARM64, glibc): ```sh curl -fsSL https://dovetaillang.org/install.sh | sh ``` On Windows x64, use PowerShell: ```powershell irm https://dovetaillang.org/install.ps1 | iex ``` The scripts install the website's release and verify the executable's SHA-256 checksum before replacing an existing installation. macOS and Linux require `curl` and either `sha256sum` or `shasum`. Linux binaries use glibc and need a system compatible with the Ubuntu 24.04 build environment; Alpine/musl is not supported. The default directory is `~/.local/bin` on macOS/Linux and `%LOCALAPPDATA%\Dovetail\bin` on Windows. Add it to your `PATH` as printed by the installer. The scripts do not edit your shell profile or Windows environment. For a specific release or installation directory, download the script first: ```sh curl -fsSL https://dovetaillang.org/install.sh -o install.sh sh install.sh --version v0.1.4 --install-dir "$HOME/.local/bin" ``` PowerShell scripts accept `-Version v0.1.4` and `-InstallDir PATH`. Both scripts are served by this website and download binaries from the matching [GitHub release](https://github.com/somdoron/dovetail/releases). ### Installing with Cargo With a [Rust toolchain](https://rustup.rs/) installed, build the Dovetail CLI from crates.io with: ```bash cargo install dovetail-lang --locked ``` `dovetail-lang` is the package name; the command you run is `dovetail`. Cargo builds and installs the executable in its bin directory (usually `~/.cargo/bin/`). Make sure that directory is on your `PATH`. The CLI includes the runtime used by `dovetail run`, so you don't need to install Wasmtime separately. `cargo install dovetail-lang` also works; `--locked` uses the dependency versions recorded for the release. ### Installing from Source To try the current development version or work on the compiler, install from a repository checkout instead: 1. **Clone the repository:** ```bash git clone https://github.com/somdoron/dovetail.git cd dovetail ``` 2. **Build the project:** ```bash cargo build --release ``` 3. **Install the Dovetail CLI:** ```bash cargo install --path dovetail --locked ``` This installs the `dovetail` command to your Cargo bin directory (usually `~/.cargo/bin/`). ### Verifying the Installation Verify Dovetail is installed correctly: ```bash dovetail --version dovetail --help ``` The help output lists commands including `init`, `projects`, `deps`, `fmt`, `build`, `check`, `run`, `test`, and `lsp-server`. Use `dovetail --help` for details about a particular command. When working on the compiler itself, use `cargo run -- ` from its checkout to test the current source. See [Tool Commands](https://dovetaillang.org/book/tool-commands.md) for more details. --- ## 1.3 Editor Setup ### VS Code Extension Dovetail provides a VS Code extension for the best development experience. #### Installing the Extension Download `dovetail-language-.vsix` from the matching [GitHub release](https://github.com/somdoron/dovetail/releases). In VS Code, run **Extensions: Install from VSIX...** from the Command Palette and select the file. Install the compiler separately using the installation instructions above. To build the extension from a repository checkout: 1. Open VS Code 2. Navigate to the `vscode-dovetail` directory in the Dovetail repository 3. Run: ```bash npm ci ``` 4. Package the extension: ```bash npm run package ``` 5. Install the generated `.vsix` file in VS Code: - Open Command Palette (Cmd/Ctrl + Shift + P) - Select "Extensions: Install from VSIX..." - Choose the generated `.vsix` file ### Extension Features The Dovetail VS Code extension provides: - **Syntax highlighting** - Colorized Dovetail code - **LSP support** - Real-time error diagnostics, autocomplete, go-to-definition - **Hover documentation** - View type information on hover - **Find references** - Find all usages of a symbol - **Workspace symbols** - Quick navigation to types and functions - **Formatting** - Format a document or format automatically on save ### Configuration Options The extension can be configured in VS Code settings: | Setting | Default | Description | |---------|---------|-------------| | `dovetail.serverPath` | `"dovetail"` | Path to the Dovetail executable | | `dovetail.serverConnection` | `"stdio"` | LSP transport (`stdio` or `tcp`) | | `dovetail.serverPort` | `9257` | TCP port for LSP when dovetail.serverConnection is tcp | | `dovetail.trace.server` | `"off"` | Trace communication with LSP server | To enable format-on-save: ```json { "[dovetail]": { "editor.defaultFormatter": "dovetail-lang.dovetail-language", "editor.formatOnSave": true } } ``` See [Formatting](https://dovetaillang.org/guides/formatting.md) for the formatter's style and options. --- ## 1.4 Your First Dovetail Program ### Creating a Project with `dovetail init` Create a new Dovetail project using the `init` command: ```bash mkdir my-project cd my-project dovetail init hello ``` This creates: - `Dovetail.toml` - The project manifest - `.gitignore` - Excludes the local `.dovetail/` cache - `hello/src/main.dove` - The main source file For a library project, use `dovetail init mylib` and replace the generated `main` with your library declarations. A library has no `main` entry point; `init` does not have a separate `--lib` flag. ### The `Dovetail.toml` Manifest File The generated `Dovetail.toml` looks like this (the compiler version matches the CLI you installed): ```toml compiler-version = "0.1.4" [[project]] name = "hello" root_package = "hello" packages = ["."] ``` Key fields: - `compiler-version` - The exact compiler version required by the workspace - `[[project]]` - Project definition (can have multiple) - `name` - Project name - `root_package` - The root package name for this project - `packages` - Package directories relative to the project's `src/` directory ### Project Directory Structure After running `dovetail init hello`, your directory looks like: ``` my-project/ ├── .gitignore ├── Dovetail.toml └── hello/ └── src/ └── main.dove ``` Convention: - Each project has its own directory matching the project name - Source files go in `src/` - Test files go in `test/` (for integration tests) - The directory structure under `src/` determines package names ### Writing "Hello, World!" Open `hello/src/main.dove` and replace its contents with this complete example: ```dovetail package hello function greeting(name: String): String = "Hello, $name!" function main(): Unit = debug(greeting("World")) test "greets the supplied name" = assert greeting("Dovetail") == "Hello, Dovetail!" ``` Let's break this down: - `package hello` - Declares this file belongs to the `hello` package - `function greeting(...)` - Builds a greeting using string interpolation - `function main(): Unit` - The entry point for an application - `=` followed by indented code - The function body - `debug(...)` - Prints a value and a newline for a quick diagnostic - `test "greets the supplied name"` - Checks the greeting with an assertion This **complete example is checked in CI**. See [Book validation](https://dovetaillang.org/guides/book-validation.md) for what is checked; other snippets in the book may show only part of a program. For application console output with typed I/O errors, see [`Console.writeLine` in the library guide](https://dovetaillang.org/book/stdlib.md#225-io). ### Building and Running Run these commands from the directory containing `Dovetail.toml`. **Format your code:** ```bash dovetail fmt ``` Use `dovetail fmt --check` to check formatting without changing files. **Build the project:** ```bash dovetail build ``` This compiles your code and produces `build/hello.wasm`. **Build and run:** ```bash dovetail run ``` Output: ``` Hello, World! ``` **Type check without building:** ```bash dovetail check ``` This verifies your code is correct without generating a WebAssembly file - useful for quick feedback during development. **Run the test:** ```bash dovetail test ``` The test checks that `greeting("Dovetail")` returns `"Hello, Dovetail!"`. You'll learn more about testing in [Part 15](https://dovetaillang.org/book/testing.md). --- ## Summary You now have Dovetail installed and know how to: - Create a new project with `dovetail init` - Understand the project structure and `Dovetail.toml` - Write and run a simple program - Use the VS Code extension for a better development experience In the next part, we'll explore the Dovetail CLI commands in more detail. --- # Part 2: Dovetail Tool Commands Source: https://dovetaillang.org/book/tool-commands/ ## 2.1 The Dovetail CLI Run `dovetail --help` for commands and `dovetail --help` for that command's arguments. `dovetail --version` reports the compiler version. From a compiler checkout, use `cargo run -- ` to avoid a stale installed binary. | Command | Behavior | |---|---| | `dovetail init hello` | Create a workspace and optionally install coding-agent support | | `dovetail ai install` | Install or refresh workspace-local coding-agent guidance | | `dovetail projects add model` | Add a project and starter `main` to an existing workspace | | `dovetail query ` | Discover typed declarations in projects, dependencies, and the prelude | | `dovetail check [project]` | Type-check all projects or one selected project and its dependencies | | `dovetail build [project]` | Compile; writes artifacts under `build/` by default | | `dovetail image build [-p project]` | Build precompiled Linux AMD64/ARM64 OCI images for configured projects | | `dovetail image push [-p project]` | Publish existing image archives using Docker credentials | | `dovetail run [project]` | Build and run an application's `main` | | `dovetail test [project]` | Build and execute discovered tests | | `dovetail fmt [files...]` | Format local workspace sources/tests, or explicit `.dove` files | | `dovetail deps fetch` | Fetch declared Git selections and their transitive dependencies | | `dovetail deps update [alias]` | Explicitly update all dependencies or one selected alias | | `dovetail lsp-server` | Start the editor language server over stdio | Run manifest-based commands from the workspace root. `fmt` and `query` also find a manifest in an ancestor directory; explicit file formatting needs no manifest. Specify the project when running a multi-project workspace. ### Coding-agent support `dovetail ai install` installs a progressive language skill and two review profiles. On first interactive use, select generic agents (including Codex), Claude, or both: ```sh dovetail ai install --agent generic,claude dovetail ai install --agent claude --dry-run dovetail init hello --ai generic,claude dovetail init hello --no-ai ``` Generic guidance installs under `.agents/skills/dovetail`; Claude uses `.claude/skills/dovetail` and two `.claude/agents` wrappers. Reviews run only when requested. Consumer `AGENTS.md` and `CLAUDE.md` files are left untouched. Commit installed files, including their installation metadata, to share with a team. The skill assets ship inside the `dovetail-lang` Cargo package and are embedded in the executable, so `cargo install dovetail-lang` includes AI support. Installation is offline and uses the executing compiler's bundled guidance. A later bare `dovetail ai install` refreshes remembered targets; the skill recommends it if its version differs from the compiler. Edited managed files cause a conflict report before writing: preserve or resolve those edits and rerun. Explicit targets add or refresh those targets without removing others. `--dry-run` previews changes. The command finds the nearest ancestor workspace. Noninteractive first installation requires `--agent`; noninteractive `init` skips the optional prompt unless `--ai` is provided. Interactive `init` offers support after workspace creation. If optional installation fails, the project remains created and the error gives a retry command. ### Checking, building, and testing ```sh dovetail check hello dovetail build hello --output-dir build dovetail test hello --filter "greets" dovetail test hello --file hello/src/main.dove ``` `--output-dir` also has the short form `-o`. Test filters match the fully qualified test name; repeated `--filter` arguments are OR'd. `--file` selects a source path (workspace-relative paths are the least ambiguous). Test output already includes individual results; there is no test `--verbose` option. Tests can live in `src/` or the project's `test/` directory; see [Testing](https://dovetaillang.org/book/testing.md). Compiler warnings appear during checking, building, running, testing, and in the editor. They do not fail compilation. Handle discarded `Result`, `Async`, and `Resource` values or acknowledge an intentional discard with `let _ = ...`. That acknowledgement does not execute deferred work. ### Container images Configure `[project.image]` in `Dovetail.toml`, then use `dovetail image build` and `dovetail image push`. Both select all image-configured local projects unless `-p` selects one. Building produces local archives; pushing never rebuilds. See the [container image guide](https://dovetaillang.org/guides/container-images.md) for base images, runtime releases, project settings, and GitHub Actions publishing. Image WASI grants (`allow-network`, `inherit-env`, and `allow-path` under `[project.image.wasi]`) default to denied and are fixed at image build time. There are no startup flag or environment-variable overrides; change the manifest and rebuild. Mounts, port mappings, and environment values still need corresponding WASI grants. See [Production Deployment and CI](https://dovetaillang.org/book/production-deployment.md) for the complete workflow. ### Formatting ```sh dovetail fmt dovetail fmt --check dovetail fmt hello/src/main.dove ``` The formatter uses four spaces and a 100-column target, preserves comments and literal text, and checks the structure of its output before accepting it. It does not fetch or format Git dependencies. It does not insert named arguments or rewrite synchronous functions into async functions. See [Formatting](https://dovetaillang.org/guides/formatting.md). ### Runtime permissions `run` denies filesystem, network, and inherited environment access unless granted. Standard input/output are available. Grant only the capabilities the program uses: ```sh dovetail run hello --allow-cwd dovetail run server --allow-network dovetail run worker --env MODE=development -- first-argument second-argument ``` | Run option | Grants | |---|---| | `--allow-cwd` | Current directory mounted at `.`; useful for relative file paths | | `--allow-path PATH` | A specific host path, mounted at that path; repeatable | | `--allow-root` | Entire host filesystem root | | `--allow-network` | TCP, UDP, and DNS access | | `--env KEY=VALUE` | One environment entry; repeatable | | `--inherit-env` | All host environment entries; explicit `--env` entries override them | | `-- ARGS...` | Application arguments | These are options of `run`, not of `test`. The test runner supplies its own host configuration. Built components also need a compatible host and that host's permission configuration when executed outside `dovetail run`. ### Discover APIs with `query` Use compiler-backed declaration queries to explore local projects, dependencies, and the embedded prelude: ```bash dovetail query search Connection dovetail query search transaction --package standard.sqlite dovetail query package dovetail query package standard.sqlite dovetail query definition standard.sqlite.Connection dovetail query definition standard.sqlite.Connection.transaction ``` `search` returns matching declaration/member names, kinds, projects, and source locations. Matching is case-insensitive, with exact matches ranked first. Use `--limit` (default 50) and `--offset` to page results. `--package` restricts search to that exact package, including members. `package` lists available packages by project. With a name, it lists immediate declarations and child packages. `definition` accepts a fully qualified name and shows documentation and Dovetail signatures; a type and its same-name module are shown together, and a member query includes its overloads. Use `--project ` to select a local consumer's dependency context. Local private/internal declarations are included; dependencies show public APIs and protected subclass members by default. `--all` also exposes nonpublic dependency declarations for inspection without changing their accessibility. If multiple contexts provide different versions of a definition, select the intended consumer with `--project`. Definition output is a declaration view, **not compilable source**: executable bodies, initializers, and superclass argument expressions are omitted. It preserves bounds, modifiers, aliases, and associated types. Type definitions include related implementation signatures and links to named extensions; importing an extension remains necessary. Conditional implementation bounds still need to be satisfied. Read source and tests when behavior cannot be established from signatures. The embedded prelude can be queried outside a workspace. Dependency queries use normal resolution, honor `--offline` and `--locked`, and do not request upgrades. Normal resolution can populate caches and lockfiles. Queries do not emit WASM. On analysis errors, recoverable declarations are marked `INCOMPLETE`, diagnostics are printed to stderr, and the exit status is nonzero. Unavailable inferred types are marked explicitly. A healthy library definition does not require its consumer application to typecheck. Empty searches succeed; missing targets and ambiguous or incomplete results fail. ### Language server Editors normally launch `dovetail lsp-server` over stdio. For a TCP client, use `dovetail lsp-server --tcp --port 9257`. `--verbose` enables server logging to stderr; it is a language-server option, not a test option. ### Exit status Successful commands return zero. Check/build failures return nonzero. Tests return 1 for test failures and 2 for compilation/setup failures; no matching tests returns zero, so do not use an unchecked filter as proof that tests ran. `fmt --check` returns 1 when files need formatting; formatter input or processing errors return 2. CLI usage errors also return nonzero. Preserve exit status in CI rather than swallowing failures with `|| echo ...`. ## 2.2 Project Structure A workspace holds projects. Each project lists its source packages and project dependencies. The compiler supplies the prelude automatically. ```text my-project/ Dovetail.toml hello/ src/main.dove test/greetingTest.dove model/ src/types.dove ``` To add a library, run `dovetail projects add model`, replace its generated `main` with library declarations, and add `"model"` to the consuming project's `depends`: ```toml compiler-version = "0.1.4" [[project]] name = "model" root_package = "example.model" packages = ["."] [[project]] name = "hello" root_package = "hello" packages = ["."] depends = ["model"] ``` `model/src/types.dove` starts with `package example.model`. A declaration used by `hello` must be `public` and imported there. A library has no `main`; there is no application/library `type` field. Packages within a project are listed in dependency order. See [Packages and Modules](https://dovetaillang.org/book/packages.md) for visibility and dependency rules. ## 2.3 Dependency Management `compiler-version` is an exact version, not a range. The installed compiler and every dependency manifest must agree. The compiler and its bundled prelude form one compatibility unit. To add standard libraries, set `standard-tag` to an **existing tag with the matching compiler version**, then select project names through `depends`: ```toml compiler-version = "0.1.4" standard-tag = "" [[project]] name = "hello" root_package = "hello" packages = ["."] depends = ["standard-json", "standard-io"] ``` `` is a placeholder, not a published version promise. For an arbitrary Git library, use an explicit dependency declaration: ```toml [[dependencies]] git = "https://github.com/acme/libraries.git" rev = "" projects = ["validation"] ``` Use a repository and revision you can access; add `"validation"` to your project's `depends` to consume it. Git selections can use a branch, tag, or revision, and can alias selected projects. See [GitHub Dependencies](https://dovetaillang.org/book/packages.md#115-github-dependencies) for the full syntax. ```sh dovetail deps fetch dovetail check --locked dovetail deps update dovetail check --locked --offline ``` Commit `Dovetail.toml` and the generated `Dovetail.lock`. `--locked` forbids a lockfile change but may download the already-pinned revision. `--offline` forbids network access and needs cached sources. Combining them requires both an unchanged lock and sufficient local cache. Updating a dependency is deliberate work: run `deps update`, review the lock change, and check/test consumers before committing it. Generated sources, dependency checkouts, and caches live under `.dovetail/`. Build artifacts live under `build/` unless overridden. Neither directory replaces the lockfile as the reproducibility record. ## 2.4 CI and Local Compiler Development For an installed, version-matched compiler, a minimal consumer CI sequence is: ```sh dovetail deps fetch --locked dovetail fmt --check dovetail check --locked dovetail test --locked ``` Cache `.dovetail/deps/` if useful, but retain the lockfile checks on cached builds. This is a command sequence, not a dedicated setup action; CI must install the compiler first. To run a local compiler checkout against a separate workspace, enter the consumer workspace and use Cargo's manifest option: ```sh cargo run --manifest-path /absolute/path/to/dovetail/Cargo.toml -- check cargo run --manifest-path /absolute/path/to/dovetail/Cargo.toml -- test ``` Cargo selects the compiler repository; Dovetail reads `Dovetail.toml` in the current working directory. No installed executable is used. Book maintainers can run `python3 tools/check-book.py` from the compiler checkout. It checks local book links and runs the explicitly marked complete examples through the local compiler. See [Book validation](https://dovetaillang.org/guides/book-validation.md) for its scope and how to add examples. --- # Part 3: Language Basics Source: https://dovetaillang.org/book/language-basics/ This part covers the fundamental syntax and building blocks of Dovetail. --- ## 3.1 Layout-Sensitive Syntax Dovetail uses indentation to define code blocks - no curly braces or semicolons needed. If you've used Python, you'll feel right at home. ### The Basics - Use **spaces** for indentation (tabs cause a compile error) - **Four spaces** is the formatter standard - Code at the same indentation level belongs to the same block - Lines can be continued by indenting more than the current block ### Where Blocks Are Used Indented blocks appear in many places in Dovetail: **After `=` in definitions:** ```dovetail function greet(name: String): String = let message = "Hello, $name!" message let result = let x = 10 let y = 20 x + y ``` **After `then` and `else` in conditionals:** ```dovetail if x > 0 then debug("positive") x * 2 else debug("non-positive") 0 ``` **After `with` in match expressions:** ```dovetail match color with case Red => "red" case Green => "green" case Blue => "blue" ``` **After `do` in loops:** ```dovetail for item in list do debug(item) ``` **After `=>` in lambdas:** ```dovetail let process = x => let doubled = x * 2 doubled + 1 ``` ### Single-Line Expressions For short expressions, you can keep everything on one line: ```dovetail function double(x: Int32): Int32 = x * 2 if x > 0 then x else -x ``` ### Multi-Line Continuations Lines indented more than the block continue the previous expression: ```dovetail let result = someFunction( arg1, arg2, arg3) ``` ### Explicit Delimiters You can use explicit `begin`/`end` and `;` instead of relying on layout: ```dovetail // Using layout (implicit) function foo() = let x = 5 x + 1 // Using explicit delimiters function foo() = begin let x = 5; x + 1 end ``` ### Record Construction Record fields use semicolons as separators: ```dovetail let point = Point { x = 10; y = 20 } ``` Or with layout: ```dovetail let point = Point { x = 10 y = 20 } ``` --- ## 3.2 Comments and Documentation ### Single-Line Comments Use `//` for regular comments: ```dovetail // This is a comment let x = 5 // Inline comment ``` ### Documentation Comments Use `///` for documentation comments. They support Markdown formatting: ```dovetail /// Creates a greeting message for the given name. /// /// Returns a friendly greeting string that can be displayed /// to the user. /// /// ## Example /// /// ``` /// greet("Bob") // "Hello, Bob!" /// ``` function greet(name: String): String = "Hello, $name!" ``` Documentation comments can be attached to: - Functions - Types (records, enums, classes, traits) - Type fields - Packages (via `README.md` in package directory) ### Testing Documented Behavior Documentation comments are displayed by tooling, but their code blocks are not executed by `dovetail test`. Add an explicit test for documented behavior: ```dovetail /// Adds two numbers together. function add(a: Int32, b: Int32): Int32 = a + b test "documented addition" = assert add(2, 3) == 5 ``` The book separately checks marked complete examples in CI; see [Book validation](https://dovetaillang.org/guides/book-validation.md). The synchronous `debug(value)` calls in examples print diagnostic output. For application console I/O, see [the async Console API](https://dovetaillang.org/book/stdlib.md#225-io). --- ## 3.3 Variables and Bindings ### Immutable Variables (Default) Variables are immutable by default using `let`: ```dovetail let name = "Alice" let age = 30 // This would be a compile error: // name = "Bob" ``` ### Mutable Variables Use `let mutable` when you need to reassign a variable: ```dovetail let mutable counter = 0 counter = counter + 1 counter = counter + 1 // counter is now 2 ``` ### Variable Shadowing You can shadow a variable by declaring a new one with the same name: ```dovetail let x = 5 let x = x + 1 // shadows the previous x, now x = 6 let x = "hello" // shadows again, x is now a String ``` Shadowing creates a new variable rather than mutating the existing one. ### Type Annotations Types are usually inferred, but you can add explicit annotations: ```dovetail let count: Int32 = 0 let name: String = "Alice" let ratio: Float64 = 3.14 ``` --- ## 3.4 Primitive Types Dovetail provides the following primitive types: | Type | Description | |------|-------------| | `Int8` | Signed 8-bit integer | | `Int16` | Signed 16-bit integer | | `Int32` | Signed 32-bit integer | | `Int64` | Signed 64-bit integer | | `Uint8` | Unsigned 8-bit integer | | `Uint16` | Unsigned 16-bit integer | | `Uint32` | Unsigned 32-bit integer | | `Uint64` | Unsigned 64-bit integer | | `Float32` | 32-bit floating point | | `Float64` | 64-bit floating point | | `Bool` | Boolean (`true` or `false`) | | `Char` | Unicode character | | `String` | UTF-8 encoded text | | `Unit` | Empty type (like `void`) | | `Never` | Bottom type (no values) | | `Any` | Top type (all values) | ### Literals ```dovetail // Integers let decimal = 42 let hex = 0xFF let binary = 0b1010 let octal = 0o755 let byte = 255u8 // with type suffix let long = 1000000i64 // Floats let pi = 3.14159 let scientific = 1.5e10 let typed = 3.14f32 // Exact numbers (available from the prelude) let large = 123456789012345678901234567890big let amount = 19.99dec let exactScientific = 1.25e3dec // Booleans let active = true let disabled = false // Characters let letter = 'A' let emoji = '🎉' let escaped = '\n' // Strings let greeting = "Hello, World!" let multiline = """ This is a multi-line string """ // Unit let nothing: Unit = () ``` `BigInt` literals use the `big` suffix and accept integer digits in any supported base, including `0xFFbig`. `Decimal` literals use `dec` and accept base-10 integers, fractions, and scientific notation. Both are constructed exactly from source digits, without floating-point conversion. Suffixes are lowercase and must touch the number; digit separators are not supported. These prelude types support `+`, `-`, `*`, unary `-`, equality, and ordering. Operands must have the same type: write `amount * 2dec`, not `amount * 2`. Unsuffixed literals keep their existing types, even with a `Decimal` annotation. Literal patterns in `match` are not supported for these types; use an equality guard instead. Division and rounding APIs are not provided yet. Decimal uses a `BigInt` coefficient and a nonnegative `Int32` scale. Values are normalized: `1.200dec` formats as `"1.2"`, and `-0.00dec` as `"0"`. Scientific notation adjusts scale exactly: `1.25e3dec` is `1250dec`. Use `Decimal.of(123big, 2)` for explicit construction, or `fromInt32`/`fromInt64`. Negative constructor scales and arithmetic scale overflow panic. Invalid literal scales are compile-time errors. Compiler literal construction has a resource limit of 1,000,000 coefficient digits, including exponent expansion; this does not limit runtime BigInt arithmetic. `BigInt` and `Decimal` have moved from `standard.math` to `standard.prelude`; remove their old imports. `Decimal.unscaled` and the first argument to `Decimal.of` now use `BigInt` instead of `Int64`. `RoundingMode` remains in `standard.math`. ### The Never Type `Never` is special - it represents computations that never complete normally: ```dovetail function fail(message: String): Never = panic message ``` It's useful for functions that always panic, unreachable code branches, and infallible async operations (`Async`). ### The Any Type `Any` is the top type — every type is assignable to `Any`: ```dovetail let a: Any = "Hello" let b: Any = 42 let c: Any = true ``` To use a value of type `Any`, you need to test or cast it back to a concrete type using `is` and `as` (see [Type Testing and Casting](https://dovetaillang.org/book/type-system.md#610-the-any-type-and-type-casting)). --- ## 3.5 Operators ### Arithmetic Operators | Operator | Description | Example | |----------|-------------|---------| | `+` | Addition | `a + b` | | `-` | Subtraction | `a - b` | | `*` | Multiplication | `a * b` | | `/` | Division | `a / b` | | `%` | Modulo | `a % b` | | `-` | Negation (unary) | `-x` | ### Comparison Operators | Operator | Description | Example | |----------|-------------|---------| | `==` | Equal | `a == b` | | `!=` | Not equal | `a != b` | | `<` | Less than | `a < b` | | `<=` | Less or equal | `a <= b` | | `>` | Greater than | `a > b` | | `>=` | Greater or equal | `a >= b` | ### Logical Operators Dovetail uses keywords for logical operators to improve readability: | Operator | Description | Example | |----------|-------------|---------| | `and` | Logical AND | `a and b` | | `or` | Logical OR | `a or b` | | `not` | Logical NOT | `not x` | Short-circuit evaluation applies: `and` stops if the left side is false, `or` stops if the left side is true. ### Bitwise Operators | Operator | Description | Example | |----------|-------------|---------| | `&` | Bitwise AND | `a & b` | | `\|` | Bitwise OR | `a \| b` | | `^` | Bitwise XOR | `a ^ b` | | `~` | Bitwise NOT | `~x` | | `<<` | Left shift | `a << 2` | | `>>` | Right shift | `a >> 2` | ### Sequence Operators | Operator | Description | Example | |----------|-------------|---------| | `::` | Prepend one element to a list | `x :: xs` | | `++` | Join two sequences | `xs ++ ys` | `::` is right-associative, so `1 :: 2 :: rest` prepends both in order. `++` concatenates; it works on `String` natively and on any type implementing the `Concat` trait, including `List`. ```dovetail let xs = 1 :: [2, 3] // [1, 2, 3] let ys = [1, 2] ++ [3, 4] // [1, 2, 3, 4] ``` ### Operator Precedence From lowest to highest: 1. `or` (logical or) 2. `and` (logical and) 3. binary `~` ([tuple extension, advanced](https://dovetaillang.org/book/tuple-extension.md)) 4. `==`, `!=` (equality) 5. `<`, `<=`, `>`, `>=` (comparison) 6. `::` (cons — **right**-associative) 7. `|` (bitwise or) 8. `^` (bitwise xor) 9. `&` (bitwise and) 10. `<<`, `>>` (shift) 11. `+`, `-`, `++` (additive and concatenation) 12. `*`, `/`, `%` (multiplicative) 13. `not`, `-`, `~` (unary prefix) 14. Function calls, field access, indexing, `is`, `as` (postfix) So `x :: xs ++ ys` groups as `x :: (xs ++ ys)`, and `x :: xs == ys` as `(x :: xs) == ys`. Use parentheses when precedence is unclear: ```dovetail let result = (a + b) * c let check = (x > 0) and (y < 10) ``` --- ## 3.6 String Interpolation Dovetail supports string interpolation in all strings - no special prefix needed. ### Simple Variable Interpolation Use `$` followed by a variable name: ```dovetail let name = "Alice" let greeting = "Hello, $name!" // "Hello, Alice!" ``` ### Expression Interpolation Use `${}` for complex expressions: ```dovetail let x = 10 let y = 20 let message = "Sum: ${x + y}" // "Sum: 30" let user = getUser() let info = "User ${user.name} is ${user.age} years old" ``` ### Escape Sequences | Sequence | Meaning | |----------|---------| | `\n` | Newline | | `\r` | Carriage return | | `\t` | Tab | | `\\` | Backslash | | `\"` | Double quote | | `\$` | Literal dollar sign | | `\0` | Null character | | `\u{XXXX}` | Unicode code point | ```dovetail let escaped = "Line 1\nLine 2" let price = "Cost: \$99.99" // Literal $, not interpolation let heart = "\u{2764}" // ❤ ``` ### Prefixed Literals A string may carry an identifier on its opening quote — `sql"SELECT * FROM t WHERE id = $id"`. The prefix hands the literal to a library, which decides what interpolation means: in a `sql"..."` literal, `$id` binds a parameter rather than splicing text. Prefixed literals also add a spread form, `$..values`, which exists nowhere else. See [Part 24](https://dovetaillang.org/book/prefixed-literals.md). ### Multi-line Strings Use triple quotes for multi-line strings: ```dovetail let poem = """ Roses are red, Violets are blue, Dovetail is great, And so are you! """ ``` Interpolation works in multi-line strings too: ```dovetail let name = "World" let html = """

Hello, $name!

""" ``` ### String Concatenation Use `++` to concatenate strings: ```dovetail let first = "Hello" let second = "World" let combined = first ++ ", " ++ second ++ "!" ``` For building strings dynamically, consider using `StringBuilder` from the standard library for better performance. --- ## Summary You now understand Dovetail's basic syntax: - **Layout-sensitive** - Indentation defines blocks, no braces or semicolons needed - **Comments** - `//` for regular, `///` for documentation with Markdown - **Variables** - Immutable by default (`let`), use `let mutable` for mutation - **Primitive types** - Integers, floats, booleans, characters, strings, unit, never, any - **Operators** - Standard arithmetic/comparison, keywords for logical (`and`, `or`, `not`) - **String interpolation** - `$variable` and `${expression}` in any string In the next part, we'll explore control flow with if expressions, pattern matching, and loops. --- # Part 4: Control Flow Source: https://dovetaillang.org/book/control-flow/ This part covers how to control the flow of execution in Dovetail programs. --- ## 4.1 If Expressions In Dovetail, `if` is an expression - it always returns a value. This makes it more powerful than traditional if statements. ### Basic Syntax ```dovetail if condition then expression1 else expression2 ``` ### Examples **Simple if/else:** ```dovetail let max = if a > b then a else b ``` **Multi-line blocks:** ```dovetail let result = if user.isActive then debug("User is active") user.score * 2 else debug("User is inactive") 0 ``` **Single-line form:** ```dovetail let sign = if x > 0 then "positive" else "non-positive" ``` ### Else If Chains Use `else if` for multiple conditions: ```dovetail let grade = if score >= 90 then "A" else if score >= 80 then "B" else if score >= 70 then "C" else if score >= 60 then "D" else "F" ``` ### If Without Else When you don't need a value, you can omit `else`. The expression returns `Unit`: ```dovetail if shouldLog then debug("Logging enabled") ``` ### Nested If If expressions can be nested: ```dovetail let category = if age < 13 then "child" else if age < 20 then "teenager" else "adult" ``` But prefer `else if` chains or `match` for better readability. --- ## 4.2 Match Expressions `match` is Dovetail's powerful pattern matching construct. It's like `switch` in other languages, but much more expressive. ### Basic Syntax ```dovetail match value with case pattern1 => result1 case pattern2 => result2 case _ => defaultResult ``` ### Matching Literals ```dovetail let dayName = match dayNumber with case 1 => "Monday" case 2 => "Tuesday" case 3 => "Wednesday" case 4 => "Thursday" case 5 => "Friday" case 6 => "Saturday" case 7 => "Sunday" case _ => "Invalid day" ``` ### Matching Enums Pattern matching shines with enums: ```dovetail enum Color = Red Green Blue RGB(Uint8, Uint8, Uint8) let description = match color with case Red => "Pure red" case Green => "Pure green" case Blue => "Pure blue" case RGB(r, g, b) => "RGB($r, $g, $b)" ``` ### Matching Option ```dovetail let message = match maybeUser with case Some(user) => "Hello, ${user.name}!" case None => "Hello, guest!" ``` ### Matching Result ```dovetail match fetchData() with case Ok(data) => processData(data) case Error(err) => debug("Error: $err") ``` ### Destructuring Records ```dovetail record Point = x: Int32 y: Int32 let quadrant = match point with case Point { x; y } if x > 0 and y > 0 => "Q1" case Point { x; y } if x < 0 and y > 0 => "Q2" case Point { x; y } if x < 0 and y < 0 => "Q3" case Point { x; y } if x > 0 and y < 0 => "Q4" case _ => "Origin or on axis" ``` Commas are also accepted between record-pattern fields, but `dovetail fmt` normalizes them to semicolons. In a multiline pattern, semicolons appear between fields but not after the last field. ### Matching Tuples ```dovetail let description = match (x, y) with case (0, 0) => "Origin" case (0, _) => "On Y axis" case (_, 0) => "On X axis" case (a, b) if a == b => "On diagonal" case _ => "Somewhere else" ``` ### Matching Lists A list matches either as a fixed set of elements or as a head and a tail: ```dovetail let description = match xs with case [] => "empty" case [only] => "exactly one" case [a, b] => "exactly two" case h :: t => "at least one more" ``` `h :: t` binds `h` to the first element and `t` to the rest, which makes recursion over a list read directly: ```dovetail function sum(xs: List): Int32 = match xs with case [] => 0 case h :: t => h + sum(t) ``` `[]` and `h :: t` together cover every list, so the pair needs no wildcard: ```dovetail let first = match xs with case [] => "none" case h :: t => h.format() ``` Fixed-length patterns do **not** cover everything, though — `case []` plus `case [a, b]` leaves a one-element list unmatched, and the compiler says so: ``` non-exhaustive match: missing case for [_] ``` These patterns match a `List` only. Arrays have no pattern form; index them or iterate them instead. ### Guard Conditions Add `if` after a pattern for additional conditions: ```dovetail let category = match age with case n if n < 0 => "Invalid" case n if n < 13 => "Child" case n if n < 20 => "Teenager" case n if n < 65 => "Adult" case _ => "Senior" ``` ### The Wildcard Pattern Use `_` to match anything you don't care about: ```dovetail match triple with case (first, _, _) => first // Only care about first element ``` ### Variable Binding Patterns can bind values to names: ```dovetail match result with case Ok(value) => debug("Got: $value") case Error(e) => debug("Error: $e") ``` ### Exhaustiveness The compiler ensures all cases are covered. This won't compile: ```dovetail enum Direction = North | South | East | West // Error: non-exhaustive match let name = match dir with case North => "N" case South => "S" // Missing East and West! ``` Use `_` as a catch-all if needed: ```dovetail let name = match dir with case North => "N" case South => "S" case _ => "Other" ``` --- ## 4.3 Loops Dovetail provides `for` and `while` loops for iteration. ### For Loops Iterate over a value that implements `Iterable`, including arrays, lists, slices, and collection ranges: ```dovetail for item in list do debug(item) ``` **Over a slice:** ```dovetail let values = [|10, 20, 30, 40|] let mutable total = 0 for value in values[|1..3|] do total = total + value assert total == 50 ``` See [Lists, Arrays, and Slices](https://dovetaillang.org/book/type-system.md#67-lists-arrays-and-slices) for slicing forms and shared-mutation behavior. The `..` notation is only valid inside slicing brackets; use the collection `Range` API for a numeric loop. **With index using enumerate:** ```dovetail for (index, item) in list.zipWithIndex() do debug("$index: $item") ``` **Over a range:** ```dovetail for i in Range.new(0, 10) do debug(i) // 0 to 9 for i in Range.inclusive(0, 10) do debug(i) // 0 to 10 (inclusive) ``` **Iterating over maps:** ```dovetail for (key, value) in map do debug("$key -> $value") ``` ### While Loops Execute while a condition is true: ```dovetail let mutable count = 0 while count < 10 do debug(count) count = count + 1 ``` ### Loop as Expression Loops return `Unit`: ```dovetail let result: Unit = for i in [|0, 1, 2, 3, 4|] do debug(i) ``` ### Early Exit Use `break` to exit a loop early: ```dovetail let mutable found = false for item in list do if item == target then found = true break ``` Use `continue` to skip to the next iteration: ```dovetail for item in list do if item < 0 then continue // Skip negative numbers debug(item) ``` ### Functional Alternatives For many use cases, functional methods are cleaner than loops: ```dovetail // Instead of a loop to transform let doubled = numbers.map(x => x * 2) // Instead of a loop to filter let positive = numbers.filter(x => x > 0) // Instead of a loop to sum let total = numbers.fold(0, (acc, x) => acc + x) // Instead of a loop to find let found = numbers.find(x => x > 100) ``` These functional approaches are often more readable and eliminate the need for mutable variables. --- ## Summary Dovetail provides expressive control flow constructs: - **If expressions** - Always return a value, can be used anywhere an expression is expected - **Match expressions** - Powerful pattern matching with destructuring, guards, and exhaustiveness checking - **Loops** - `for` and `while` for iteration, with `break` and `continue` for control A key principle: prefer pattern matching over long if/else chains, and prefer functional methods (`map`, `filter`, `fold`) over imperative loops when it makes the code clearer. In the next part, we'll explore functions in depth. For restrictions on loops that suspend, see [Async Programming](https://dovetaillang.org/book/async.md). --- # Part 5: Functions Source: https://dovetaillang.org/book/functions/ Functions are the primary building blocks in Dovetail. They are first-class values, meaning you can pass them around, return them from other functions, and store them in variables. --- ## 5.1 Function Definitions ### Basic Syntax Functions are defined using the `function` keyword: ```dovetail function greet(): String = "Hello, World!" ``` The function body follows the `=` sign. Since Dovetail is expression-based, the last expression in the body is the return value. ### Parameters and Return Types Functions can take parameters with type annotations. The return type follows the parameter list: ```dovetail function add(a: Int32, b: Int32): Int32 = a + b function formatUser(name: String, age: Int32): String = "$name is $age years old" ``` ### Unit Return Type Functions that don't return a meaningful value return `Unit`: ```dovetail function greet(name: String): Unit = debug("Hello, $name!") ``` You can omit the return type annotation for `Unit`: ```dovetail function greet(name: String) = debug("Hello, $name!") ``` ### Multi-Line Function Bodies Function bodies can span multiple lines. Each line is an expression, and the last expression is returned: ```dovetail function calculateTotal(price: Float64, quantity: Int32): Float64 = let subtotal = price * quantity.toFloat64() let tax = subtotal * 0.08 subtotal + tax ``` ### Visibility Top-level functions default to `internal` (package visibility). Use `public` to make them accessible from other packages, or `private` for file-local helpers: ```dovetail public function add(a: Int32, b: Int32): Int32 = a + b ``` --- ## 5.2 Named and Default Arguments ### Named Arguments When calling a function, you can use named arguments for clarity: ```dovetail function createUser(name: String, age: Int32, email: String): User = User { name = name; age = age; email = email } // Using named arguments let user = createUser(name = "Alice", age = 30, email = "alice@example.com") ``` Named arguments can be in any order: ```dovetail let user = createUser(email = "bob@example.com", name = "Bob", age = 25) ``` You can mix positional and named arguments, but positional arguments must come first: ```dovetail let user = createUser("Charlie", age = 28, email = "charlie@example.com") ``` Every parameter must receive exactly one argument. Unknown names, duplicate arguments (including a positional and named argument for the same parameter), missing arguments, and positional arguments after named ones are errors. Named arguments also work with methods and class constructors. The receiver is evaluated first, then eager arguments are evaluated once in the order written, even when that differs from parameter order. `ByName` arguments remain deferred. Accepted names come from the declaration visible at the call site. Calls through a trait bound or interface use that contract's names; calls through a base class use its visible method declaration. Calls through a concrete class use its own method declaration, or the inherited declaration when it does not override the method. An implementation may rename parameters, but those names are not aliases for the contract's names. Runtime dispatch does not affect argument binding. Renaming a public parameter can therefore break callers that use its name. Function values, including stored function and bound-method references, accept only positional arguments: their types do not contain parameter names. Positional enum payloads and newtype constructors also have no argument labels. Record construction continues to use its existing field-initializer syntax. ### Default Parameter Values (Planned) Default parameter values are planned but are not implemented yet. For optional settings today, use a configuration record and a function that constructs it: ```dovetail record Config = host: String port: Int32 function defaultConfig(host: String): Config = Config { host = host; port = 8080 } let local = defaultConfig("localhost") let custom = local with { port = 3000 } ``` Record field assignments are distinct from function arguments. The formatter preserves that distinction; it does not introduce named arguments. The proposed default-parameter syntax is: ```dovetail function greet(name: String, greeting: String = "Hello"): String = "$greeting, $name!" greet("Alice") // "Hello, Alice!" greet("Bob", "Hi") // "Hi, Bob!" greet("Charlie", greeting = "Hey") // "Hey, Charlie!" ``` Parameters with defaults must come after required parameters: ```dovetail function createConfig( host: String, port: Int32 = 8080, timeout: Int32 = 30, debug: Bool = false ): Config = Config { host = host; port = port; timeout = timeout; debug = debug } // Use defaults let config1 = createConfig("localhost") // Override some defaults let config2 = createConfig("localhost", port = 3000, debug = true) ``` ### Future Linter Rule for Named Arguments The formatter preserves the caller's choice of positional or named arguments. Adding argument names requires resolving the called declaration and belongs in a semantic linter. Dovetail does not yet have a linter. A planned rule would suggest named arguments when the called declaration has more than two explicit parameters, excluding the method receiver: ```dovetail // You write: createUser("John", 30, "john@example.com") // A future linter could suggest this fix: createUser(name = "John", age = 30, email = "john@example.com") ``` Additional planned lint rules require named arguments for Boolean and numeric literals, even in calls with only one or two parameters. For example, `retry(3)` would be written as `retry(attempts = 3)`. These lint rules are not implemented yet. See the [future linter design and rule backlog](https://github.com/somdoron/dovetail/blob/main/docs/linter-design.md). --- ## 5.3 Closures and Lambdas ### Basic Closure Syntax Closures (anonymous functions) use the `=>` arrow: ```dovetail // With type annotations (via expected type) let multiply: (Int32, Int32) => Int32 = (a, b) => a * b // Single parameter - no parentheses needed let double = (x: Int32) => x * 2 // Multiple parameters - parentheses required let add = (a: Int32, b: Int32) => a + b ``` ### Multi-Line Closures Closure bodies can span multiple lines using indentation: ```dovetail let process = (x: Int32) => let y = x + 1 let z = y * 2 z + 10 ``` ### Environment Capture Closures can capture variables from their surrounding scope: ```dovetail let multiplier = 3 let scale = (x: Int32) => x * multiplier scale(5) // 15 ``` Closures capture variables by reference: ```dovetail function makeCounter(): () => Int32 = let mutable count = 0 () => count = count + 1 count let counter = makeCounter() counter() // 1 counter() // 2 counter() // 3 ``` ### Function Types Function types describe the signature of a function: ```dovetail // A function that takes two Int32s and returns an Int32 let operation: (Int32, Int32) => Int32 = (a, b) => a + b // A function that takes no arguments and returns Unit let action: () => Unit = () => debug("Hello!") // A function that takes a String and returns a Bool let predicate: (String) => Bool = s => s.length > 0 ``` --- ## 5.4 First-Class Function References Named functions can be used as values wherever a function type is expected. Simply use the function name without calling it: ```dovetail function double(x: Int32): Int32 = x * 2 let f: Int32 => Int32 = double f(5) // 10 ``` ### Passing Functions as Arguments ```dovetail function identity(x: Int32): Int32 = x function apply(f: Int32 => Int32, value: Int32): Int32 = f(value) apply(identity, 42) // 42 ``` ### Module Functions Module functions work the same way: ```dovetail module Math = function square(x: Int32): Int32 = x * x let f: Int32 => Int32 = Math.square f(4) // 16 ``` ### Generic Functions Generic functions can be used as values when the expected type provides enough information to instantiate the type parameters: ```dovetail function identity(x: T): T = x let f: Int32 => Int32 = identity // T inferred as Int32 f(42) // 42 ``` ### Overload Disambiguation When multiple overloads exist, the expected type disambiguates: ```dovetail function convert(x: Int32): String = "$x" function convert(x: String): Int32 = 0 let f: Int32 => String = convert // selects first overload ``` --- ## 5.5 Method References ### Bound Method References When you access a method on an instance without calling it, you get a **bound method reference** — a function value with `self` already captured: ```dovetail record Point = x: Int32 y: Int32 module Point = function getX(self): Int32 = self.x function addX(self, dx: Int32): Int32 = self.x + dx let p = Point { x = 42; y = 0 } // Bound reference — self (p) is captured let f = p.getX // type: Unit => Int32 f() // 42 let g = p.addX // type: Int32 => Int32 g(10) // 52 ``` The resulting function type excludes `self` from the parameter list since it is already bound to the object. ### Unbound Method References You can also reference a method via the type name. This produces a function that takes `self` as its first parameter: ```dovetail let f: Point => Int32 = Point.getX let p = Point { x = 42; y = 0 } f(p) // 42 ``` ### Class Methods Method references work with class instance methods too: ```dovetail class Counter(public value: Int32) = public function getValue(self: Counter): Int32 = self.value let c = Counter(99) let f = c.getValue f() // 99 ``` ### Using Method References with Higher-Order Functions Method references are especially useful when passing to higher-order functions: ```dovetail function apply(f: Int32 => Int32, arg: Int32): Int32 = f(arg) let p = Point { x = 42; y = 0 } apply(p.addX, 10) // 52 ``` --- ## 5.6 Higher-Order Functions Higher-order functions are functions that take other functions as parameters or return functions. ### Functions as Parameters ```dovetail function applyTwice(f: (Int32) => Int32, x: Int32): Int32 = f(f(x)) let double = (x: Int32) => x * 2 applyTwice(double, 5) // 20 (5 * 2 * 2) ``` ### Common Higher-Order Functions Dovetail's standard library provides common higher-order functions for collections: **map** - Transform each element: ```dovetail let numbers = [1, 2, 3, 4, 5] let doubled = numbers.map(x => x * 2) // [2, 4, 6, 8, 10] ``` **filter** - Keep elements matching a predicate: ```dovetail let numbers = [1, 2, 3, 4, 5] let evens = numbers.filter(x => x % 2 == 0) // [2, 4] ``` **foldLeft** - Reduce to a single value: ```dovetail let numbers = [1, 2, 3, 4, 5] let sum = numbers.foldLeft(0, (acc, x) => acc + x) // 15 ``` ### Returning Functions Functions can return other functions: ```dovetail function makeMultiplier(factor: Int32): (Int32) => Int32 = x => x * factor let triple = makeMultiplier(3) triple(7) // 21 ``` ### Pipeline Style Higher-order functions enable a clean pipeline style: ```dovetail let result = numbers .filter(x => x > 0) .map(x => x * 2) .foldLeft(0, (acc, x) => acc + x) ``` This style is often more readable than nested function calls: ```dovetail // Equivalent but harder to read let result = numbers.filter(x => x > 0).map(x => x * 2).foldLeft(0, (acc, x) => acc + x) ``` --- ## Summary - Functions are defined with `function name(params): ReturnType = body` - Use named arguments for clarity: `createUser(name = "Alice", age = 30)` - Default parameter values are planned; use configuration records for defaults today - Closures use `=>` syntax: `x => x * 2` or `(a, b) => a + b` - Closures capture variables from their environment - Function types describe signatures: `(Int32, Int32) => Int32` - Named functions can be used as values: `let f: Int32 => Int32 = double` - Bound method references capture self: `let f = point.getX` - Unbound method references include self: `let f: Point => Int32 = Point.getX` - Higher-order functions take or return other functions - Use `map`, `filter`, and `foldLeft` for collection processing --- # Part 6: Type System Source: https://dovetaillang.org/book/type-system/ Dovetail has a powerful type system that catches errors at compile time while keeping your code clean through type inference. --- ## 6.1 Type Inference Dovetail infers types whenever possible, so you don't need to write them explicitly: ```dovetail let name = "Alice" // inferred as String let age = 30 // inferred as Int32 let price = 19.99 // inferred as Float64 let active = true // inferred as Bool ``` You can add explicit type annotations when needed: ```dovetail let count: Int64 = 100 // explicitly Int64, not Int32 let ratio: Float32 = 0.5 // explicitly Float32, not Float64 ``` Function return types must be explicit (for readability), except for `Unit`: ```dovetail function double(x: Int32): Int32 = x * 2 function calculateTotal(price: Float64, quantity: Int32): Float64 = price * quantity.toFloat64() // Unit return type can be omitted function greet(name: String) = debug("Hello, $name!") ``` --- ## 6.2 Records Records are data-only types with named fields. They're similar to structs in other languages. ### Defining Records ```dovetail record Point = x: Int32 y: Int32 record User = name: String email: String age: Int32 ``` ### Creating Records Use the record name followed by field initializers in braces: ```dovetail let origin = Point { x = 0; y = 0 } let user = User { name = "Alice"; email = "alice@example.com"; age = 30 } ``` Multi-line syntax: ```dovetail let user = User { name = "Alice" email = "alice@example.com" age = 30 } ``` ### Accessing Fields Use dot notation: ```dovetail let userName = user.name let userAge = user.age ``` ### Updating Records with `with` Records are immutable. Use `with` to create a modified copy: ```dovetail let older = user with { age = user.age + 1 } let renamed = user with { name = "Bob" } ``` ### Private Construction Put `private` after the record name to reserve construction and `with` updates for its associated module: ```dovetail public record Account private = balance: Int32 module Account = public function make(balance: Int32): Result = if balance < 0 then Error("balance must not be negative") else Ok(Account { balance = balance }) public function deposit(self, amount: Int32): Result = if amount < 0 then Error("deposit must not be negative") else Ok(self with { balance = self.balance + amount }) ``` Any caller that can see `Account` can read `account.balance` and destructure an account in a pattern. Only `module Account` in the defining package can write `Account { ... }` or `account with ...`. Sharing the package or file is not enough; trait implementations and extensions must call the module's functions too. Leading `public` controls visibility of the type itself. For generic records, write `record Box private where T: Equatable = ...`: the construction modifier follows type parameters and precedes constraints. Empty private records use `record Token private`, with the modifier on the same line as the name. ### Records are Equatable Records with equatable fields can opt into equality with `@derive(Equatable)` on the record declaration: ```dovetail @derive(Equatable) record Point = x: Int32 y: Int32 let p1 = Point { x = 1; y = 2 } let p2 = Point { x = 1; y = 2 } p1 == p2 // true ``` --- ## 6.3 Enums (Discriminated Unions) Enums represent values that can be one of several variants. Each variant can optionally carry data. ### Simple Enums ```dovetail enum Color = Red Green Blue let color = Red ``` ### Enums with Data Variants can carry payload data: ```dovetail enum Shape = Circle(Float64) // radius Rectangle(Float64, Float64) // width, height Point let circle = Circle(5.0) let rect = Rectangle(10.0, 20.0) let point = Point ``` ### Pattern Matching on Enums Use `match` to handle different variants: ```dovetail function area(shape: Shape): Float64 = match shape with case Circle(radius) => 3.14159 * radius * radius case Rectangle(w, h) => w * h case Point => 0.0 ``` ### Named Record Payloads A variant with exactly one record payload supports both positional and record syntax: ```dovetail record Placement = placedAt: Int32 total: Int32 enum OrderStatus = Draft Placed(Placement) let placement = Placement { placedAt = 1; total = 100 } let first = OrderStatus.Placed(placement) let second = OrderStatus.Placed { placedAt = 1; total = 100 } ``` The brace construction is shorthand for `OrderStatus.Placed(Placement { placedAt = 1; total = 100 })`. The payload remains a `Placement` value that can be passed around independently. Patterns can bind that whole value or inspect its fields: ```dovetail function total(status: OrderStatus): Int32 = match status with case Placed { total } => total case Draft => 0 ``` `case Placed { total }` means `case Placed(Placement { total })`; `case Placed(placement)` binds the whole record. Ordinary record pattern rules apply, including omitted fields and nested patterns. Extracting an inline record variant into a named payload can therefore preserve its brace construction and patterns. Braces unwrap exactly one record payload. They do not unwrap newtypes, classes, or multiple positional payloads. Generic payloads work when their record type is known: `Some { total = 100; placedAt = 1 }` needs an expected type such as `Option`. Fields alone do not identify a nominal record type. Custom construction uses the enum qualifier; patterns can infer it from the matched value. Construction checks both the enum's and the payload record's construction permissions. A private record constructor does not prevent inspecting its public fields through a variant pattern or wrapping an existing record value in a publicly constructible enum. ### Private Construction of Enums The same modifier reserves every variant's construction for the enum's associated module: ```dovetail public enum Status private = Open Closed module Status = public function open(): Status = Status.Open public function close(self): Status = Status.Closed function isClosed(status: Status): Bool = match status with case Status.Open => false case Status.Closed => true ``` Outside `module Status`, `Status.Open` is allowed in a pattern but rejected as a value expression, because that would construct a value. This applies to variants with no payload, tuple payloads, and named record payloads. Patterns still expose their payloads, and exhaustiveness checking is unchanged. The `with` expression remains a record operation; this does not add enum updates. ### Option Type The prelude's `Option` type represents optional values. It is available without an import: ```dovetail enum Option = Some(T) None function findUser(id: Int32): Option = if id == 1 then Some(User { name = "Alice"; email = "a@b.com"; age = 30 }) else None ``` See [Option Type in chapter 10](https://dovetaillang.org/book/error-handling.md#102-option-type) for its methods and guidance on handling missing values. ### Result Type The prelude's `Result` type represents success or failure. It is also available without an import: ```dovetail enum Result<+T, +E> = Ok(T) Error(E) function divide(a: Int32, b: Int32): Result = if b == 0 then Error("Division by zero") else Ok(a / b) ``` See [Result Type in chapter 10](https://dovetaillang.org/book/error-handling.md#103-result-type) for its methods and [the `try` expression](https://dovetaillang.org/book/error-handling.md#try-and-orreturn) for propagating failures. --- ## 6.4 Tuples Tuples group multiple values of different types. ### Creating Tuples ```dovetail let pair = (1, "hello") let triple = (10, 20.5, true) ``` ### Accessing Tuple Elements Use pattern matching or numbered accessors: ```dovetail let (x, y) = pair // x is 1, y is "hello" let first = triple._0 // 10 let second = triple._1 // 20.5 ``` ### Tuples vs Records - Use tuples for quick grouping of a few values - Use records when you need meaningful field names - Records are better for public APIs For generic tuple construction, shape constraints, and implementations across tuple arities, see [Part 25: Tuple Extension (Advanced)](https://dovetaillang.org/book/tuple-extension.md). --- ## 6.5 Newtypes Newtypes create distinct types from existing types, preventing accidental mixing. ### Defining Newtypes ```dovetail newtype UserId = Int32 newtype OrderId = Int32 newtype Email = String ``` ### Using Newtypes ```dovetail let userId = UserId(123) let orderId = OrderId(456) // This won't compile - different types! // let wrong: UserId = orderId // Error! ``` ### Why Newtypes? Newtypes prevent bugs by making the type system work for you: ```dovetail function getUser(id: UserId): Option = ... function getOrder(id: OrderId): Option = ... let userId = UserId(123) let orderId = OrderId(456) getUser(userId) // OK getUser(orderId) // Compile error! Can't pass OrderId where UserId expected ``` ### Private Newtypes A private newtype reserves both construction and inspection for its associated module: ```dovetail public newtype Password private = String module Password = public function parse(value: String): Result = if value.length < 8 then Error("Password must be at least 8 characters") else Ok(Password(value)) public function matches(self, other: String): Bool = self.value == other ``` Only `module Password` in the defining package may call `Password(...)`, access `.value`, or unwrap a `Password(value)` pattern. Other code can store, pass, and compare passwords and call the module's public functions. Trait implementations and extensions have no special access, even in the same package. Private records and enums expose their fields and patterns; private newtypes keep the wrapped value opaque. All three use `Name private where ... = ...`, but their inspection rules differ. The old `newtype Name = private Type` syntax is rejected. --- ## 6.6 Type Aliases Type aliases create alternative names for existing types. Unlike newtypes, they're interchangeable with the original type. ### Defining Type Aliases ```dovetail type Name = String type Age = Int32 type Coordinate = (Int32, Int32) type UserMap = Map ``` ### Using Type Aliases ```dovetail function greet(name: Name): String = "Hello, $name!" let name: Name = "Alice" greet(name) // OK greet("Bob") // Also OK - Name is just String ``` ### When to Use Type Aliases vs Newtypes - **Type aliases**: For convenience and readability (great for tuples and function types) - **Newtypes**: When you need type safety and distinction ```dovetail // Type aliases make complex types readable type Coordinate = (Int32, Int32) type Handler = (Request) => Response type Callback = (T) => Unit // Newtypes create distinct types newtype UserId = Int32 newtype OrderId = Int32 ``` --- ## 6.7 Lists, Arrays, and Slices `List`, `Array`, and `Slice` are available from the prelude without imports. Lists are immutable linked sequences; arrays are fixed-size mutable collections; slices are writable views into arrays. ### Lists and List Literals Square brackets create a `List`. The element type is inferred from the contents or the surrounding context: ```dovetail let numbers: List = [1, 2, 3] let names = ["Alice", "Bob"] let empty: List = [] let nested: List> = [[1, 2], [3]] assert numbers.length == 3 assert empty.isEmpty ``` A list is an immutable singly linked sequence, defined as `List = Nil | Cons(T, List)` in the prelude and used as the standard library's default sequence type. It is represented by the enum variants `Nil` (empty) and `Cons(head, tail)`. `[]` is the literal spelling of `Nil`; `[1, 2]` is equivalent to `Cons(1, Cons(2, Nil))`. `List.empty` also creates an empty list. Use `::` to prepend an element or `++` to concatenate lists. These operations leave the original lists unchanged. Prepending shares the existing tail and takes constant time; concatenation copies the left list's nodes and takes time proportional to its length. `::` associates to the right: ```dovetail let original = [2, 3] let extended = 1 :: original assert extended == [1, 2, 3] assert original == [2, 3] assert 1 :: 2 :: [] == [1, 2] assert [1, 2] ++ [3, 4] == [1, 2, 3, 4] ``` `List` is covariant (`out T`), so an empty list fits wherever a `List` is expected without an annotation at the call site: ```dovetail function total(xs: List): Int32 = xs.foldLeft(0, (a, b) => a + b) total([]) ``` `::` binds looser than `+` and `++`, and tighter than comparison — so `x :: xs ++ ys` is `x :: (xs ++ ys)`, and `x :: xs == ys` is `(x :: xs) == ys`. The list structure is immutable; objects stored as elements may still have mutable state. ### Accessing and Matching Lists `head` and `tail` return the first element and the remaining list, and panic on an empty list. `headOption` and `get(index)` return `Option` instead; `get` returns `None` for a negative or out-of-bounds index. Lists use `.get(i)` for indexed lookup. Both indexed lookup and `length` traverse the list, so use an array when frequent random access matters. ```dovetail let numbers = [10, 20, 30] assert numbers.head == 10 assert numbers.tail == [20, 30] assert numbers.headOption.or(0) == 10 assert numbers.get(1).or(0) == 20 assert numbers.get(3).isNone ``` Patterns can match an empty list, an exact number of elements, or a head and tail: ```dovetail function describeList(values: List): String = match values with case [] => "empty" case [only] => "one element" case first :: rest => "multiple elements" ``` ### Transforming and Iterating Lists `map`, `filter`, `take`, `drop`, and `reverse` return lists without changing the original. `foldLeft` combines the elements into a single value. `toArray()` copies the elements into a new mutable array: ```dovetail let numbers = [1, 2, 3] assert numbers.map(x => x * 2) == [2, 4, 6] assert numbers.filter(x => x > 1) == [2, 3] assert numbers.take(2) == [1, 2] assert numbers.drop(1) == [2, 3] assert numbers.reverse() == [3, 2, 1] assert numbers.find(n => n > 1) == Some(2) assert numbers.contains(2) assert numbers.foldLeft(0, (total: Int32, value: Int32) => total + value) == 6 let array = numbers.toArray() array[0] = 99 assert numbers.head == 1 let mutable sum = 0 for number in numbers do sum = sum + number assert sum == 6 ``` Lists compare by contents with `==` and `!=` when their elements implement `Equatable`. They compare lexicographically when their elements implement `Comparable`. ### Choosing Between Array and List | | `List` | `Array` | |---|---|---| | Literal | `[1, 2]` | `[\| 1, 2 \|]` | | Prepend | O(1) | O(n) | | Index / length | O(n) | O(1) | | Mutable | no | yes | | Variance | covariant (`out T`) | invariant | | Pattern matching | yes | no | Reach for a **list** when you build a sequence once and read it front to back, which is most business logic. Reach for an **array** when you index it, mutate it, know its size up front, or are holding bytes. ### Creating Arrays Arrays are fixed-size, mutable, indexed collections. Array literals use `[| ... |]`; list literals use `[ ... ]`: ```dovetail let numbers = [| 1, 2, 3, 4, 5 |] let names = [| "Alice", "Bob", "Charlie" |] let empty: Array = [||] ``` The plain `[ ... ]` form is a *list* literal (§6.7). Arrays get the marked syntax because they are the specialised choice — contiguous, mutable, indexed — while a list is the everyday sequence. An empty array literal needs a type annotation. `Array` is invariant, so there is nothing for the compiler to infer the element type from. Use `Array.fill(length, value)` to create an array of a given size filled with a value: ```dovetail let zeros = Array.fill(10, 0) // [|0, 0, 0, ..., 0|] let greetings = Array.fill(3, "hi") // [|"hi", "hi", "hi"|] ``` ### Accessing Elements Use the index operator `arr[i]` or the `.get(i)` method: ```dovetail let first = numbers[0] // 1 let second = numbers.get(1) // 2 ``` ### Setting Elements Use the index assignment `arr[i] = value` or the `.set(i, value)` method: ```dovetail numbers[0] = 99 numbers.set(1, 42) ``` ### Array Length ```dovetail let count = numbers.length // 5 ``` ### Cloning `arr.clone()` creates an independent copy of the array: ```dovetail let a = [|1, 2, 3|] let b = a.clone() b[0] = 99 assert a[0] == 1 // original unchanged ``` ### Array Type Syntax The type of an array is written as `Array`: ```dovetail function sum(numbers: Array): Int32 = numbers[0] + numbers[1] + numbers[2] ``` ### Nested Arrays Arrays can contain other arrays: ```dovetail let matrix: Array> = [| [| 1, 2 |], [| 3, 4 |] |] let value = matrix[0][1] // 2 ``` ### Equality and Comparison Primitive-element arrays support `==`, `!=`, `<`, `>`, `<=`, `>=` (lexicographic comparison): ```dovetail assert [| 1, 2, 3 |] == [| 1, 2, 3 |] assert [| 1, 2 |] < [| 1, 3 |] ``` ### Iterating Over Arrays ```dovetail for item in numbers do debug(item) ``` ### Slices: Shared Array Views `Slice` is available from the prelude without an import. It is a writable view of part of an `Array`. Creating or re-slicing a view does not copy its elements. Slicing uses `[| ... |]`, matching the array literal delimiters. Element access and assignment still use `[i]`: ```dovetail let values = [|10, 20, 30, 40, 50|] let middle: Slice = values[|1..4|] assert middle.length == 3 assert middle[0] == 20 middle[0] = 99 assert values[1] == 99 // writes through to the original array values[2] = 77 assert middle[1] == 77 // observes writes through the array too let tail = middle[|1..|] assert tail[0] == 77 // bounds are relative to middle ``` All slicing forms work on both arrays and slices: | Form | Elements included | |---|---| | `values[\|i..j\|]` | From `i` up to, but excluding, `j` | | `values[\|i..\|]` | From `i` through the end | | `values[\|..j\|]` | From zero up to, but excluding, `j` | | `values[\|..\|]` | The whole array or slice | | `values[\|i..=j\|]` | From `i` through `j`, including `j` | | `values[\|..=j\|]` | From zero through `j`, including `j` | `values[|i|]` and `values[i..j]` are invalid. Use `values[i]` for one element. Range assignment is unsupported; use `copyTo` to copy several elements. The range notation belongs to slicing syntax: `let range = 1..3` is not a valid expression. ### Slice Bounds and Constructors Bounds are `Int32` values. A half-open interval requires `0 <= start <= end <= length`. An inclusive end must refer to an existing element. Invalid construction, re-slicing, or element access panics; negative indices do not count back from the end. Empty half-open slices are valid, including one at the array's end: ```dovetail let empty = values[|values.length..|] assert empty.isEmpty() let whole = Slice.full(values) let window = Slice.make(values, 1, 3) // start and length assert window == whole.slice(1, 4) // start and exclusive end assert window == whole.sliceInclusive(1, 3) assert window == whole.drop(1).take(3) ``` `make` takes a **length**; `slice` takes an **exclusive end**. The backing array, start, and length are private. Copying a slice value shares the same view of the data. Ordinary locals, parameters, and returns carry the view directly; storing it at an erased or boxed boundary, such as an array element or `Option>` payload, can allocate a box for the view. ### Copying, Comparing, and Iterating `toArray()` produces an independent copy. `copyTo(destination)` copies all source elements into a destination slice whose length must be at least the source length. It also works when the views overlap: ```dovetail let copied = middle.toArray() copied[0] = 0 assert middle[0] == 99 let shifted = [|1, 2, 3, 4|] shifted[|..3|].copyTo(shifted[|1..|]) assert shifted == [|1, 1, 2, 3|] ``` Read-only views use the private tuple newtype `ReadonlySlice`: ```dovetail let array = [|10, 20, 30|] let view = array[|1..|].readonly assert view[0] == 20 let subview = view[|..1|] // also ReadonlySlice let copy = view.toArray() // independent mutable array array[1] = 99 assert view[0] == 99 // read-only does not freeze other aliases assert copy[0] == 20 ``` Arrays and slices expose `.readonly` without copying elements. Read-only views support the same checked ranges, iteration, equality, and display as mutable slices. `map` returns fresh backing storage as a read-only view; `copyTo` accepts a mutable destination slice. Indexed writes and `.set` are unavailable. Both slice types use trait-based element access: `Index` for reads, and `IndexSet` for mutable writes. Slices compare by contents with `==` and `!=` when their elements implement `Equatable`, even if they view different arrays. They support `Display` when their elements do. Use `for` to iterate over a slice: ```dovetail let mutable total = 0 for value in middle do total = total + value assert total == 216 ``` ### Slices and Strings Strings do not support slicing syntax. Convert explicitly to a character array and then create a view. `chars()` copies the characters once; subsequent slicing shares that array: ```dovetail let characters = Slice.full("hello".chars()) let greetingStart = characters[|..2|] assert greetingStart[0] == 'h' assert greetingStart[1] == 'e' ``` --- ## 6.8 Modules A **module** is a named container that groups **functions**, **properties**, and **top-level `let`** (globals) under one name (similar to F# modules). Modules do not contain type declarations. You use its contents via the qualifier: `ModuleName.member`. There are **two kinds** of modules, depending on whether a type with the same name exists in the package. In a **generic** module (e.g. `module Array =`), **all** members and globals have the type parameter in scope, and globals are **instantiated per type argument** (like C#): each `Array`, `Array`, etc. has its own copy of each static and each top-level `let`. ### Standalone modules When **no type** in the package has the same name as the module, it's a **standalone module**. It can contain only static functions, static properties (`property name(): Type = ...`), and top-level `let` (static). No instance members. ```dovetail package standard module Math = let Pi: Float64 = 3.141592653589793 property pi(): Float64 = 3.141592653589793 function floor(x: Float64): Int32 = ... function ceil(x: Float64): Int32 = ... ``` You import the module as a whole and use qualified names: ```dovetail import standard.Math let x = Math.floor(3.14) let p = Math.Pi ``` You cannot import a single member (e.g. `import standard.Math.floor` is not allowed). ### Modules for a type When a **type with the same name** as the module exists in the package (e.g. type `Array` and module `Array`), the module is a **module for that type**. It may contain instance methods (functions with `self`), instance properties (`property name(self): Type = ...`), static methods and static properties, and top-level `let` (static/global). For a generic type, the module must declare the type parameters: `module Array =`. In a **generic** module, **all** members and top-level `let` have the type parameter `T` in scope. Static members and globals are **instantiated per type argument** (like C#): `Array.defaultCapacity` and `Array.defaultCapacity` are different storage locations. ```dovetail package standard.prelude // Type Array is declared elsewhere (e.g. intrinsic). module Array = let defaultCapacity: Int32 = 16 property empty(): Array = ... function fill(size: Int32, value: T): Array = ... function get(self, index: Int32): T = ... function set(self, index: Int32, value: T): Unit = ... property length(self): Int32 = ... ``` **Instance members are always available on the type.** You do not need to import the module to call `arr.length` or `arr.get(0)` — whenever the type is in scope, the instance methods and properties from that type's module are available. **Static methods** can infer the module's type arguments from call arguments or an expected return type: `Array.fill(5, 42)` infers `Int32`, and `let empty: Array = Array.empty()` uses the annotation. You can also supply them explicitly, as in `Array.fill(5, 42)`. For a generic global, qualify its instantiation explicitly, such as `Array.defaultCapacity`. Static use requires the module to be in scope (same package, prelude, or imported). A package may have **at most one** module for a given module name (standalone or for a type). --- ## 6.9 Extension Methods **Extension methods** let you add methods to types you don't own — especially types from another package. Every extension has a name (e.g. `UserHelpers`) and defines methods (and static methods) on a type. Extensions must be **imported** to be used; they are not attached automatically. ```dovetail package myapp import users.User // User is defined in another package extension UserHelpers for User = function displayName(self): String = "${self.firstName} ${self.lastName}" ``` Why give extensions a name and require import? 1. **Explicit imports** — You must import the extension to use its methods, so it's clear where they come from. 2. **Avoid conflicts** — Different packages can define extensions with different names for the same type. 3. **Clear ownership** — The extension name shows which package or layer provides the behavior. Using extensions: ```dovetail package app import users.User import myapp.UserHelpers // Must import the extension function main() = let user = User { firstName = "Alice"; lastName = "Smith" } debug(user.displayName()) // Works because UserHelpers is imported ``` For **types in the same package**, instance methods are usually provided by a **module for that type** (a module with the same name as the type), not by an extension. See [Modules](https://dovetaillang.org/book/type-system.md#68-modules) above. Use an **extension** when you want to add methods to a type from another package or when you want a separate, explicitly imported set of methods. --- ## 6.10 The Any Type and Type Casting ### The Any Type `Any` is the **top type** in Dovetail — every type is assignable to `Any`: ```dovetail let a: Any = "Hello" let b: Any = 42 let c: Any = true let d: Any = Point { x = 1; y = 2 } ``` This makes `Any` useful for heterogeneous collections, generic APIs that accept values of unknown type, and interop scenarios. Under the hood, primitive values (integers, floats, booleans, etc.) are automatically **boxed** when stored as `Any`, and **unboxed** when cast back to their concrete type. ### Type Testing with `is` The `is` expression checks at runtime whether a value has a specific type. It returns `Bool`: ```dovetail function describe(x: Any): String = if x is String then "a string" else if x is Int32 then "an integer" else if x is Bool then "a boolean" else "something else" ``` `is` supports concrete target types, including primitives, records, enums, generic types, and arrays: ```dovetail let x: Any = Some(42) x is Option // true x is Option // false x is String // false ``` ### Type Casting with `as` The `as` expression casts a value to a specific type. If the cast fails at runtime, it **panics** (there is no safe `as?` variant): ```dovetail let x: Any = 42 let n: Int32 = x as Int32 // succeeds, n is 42 let y: Any = "hello" let s: String = y as String // succeeds, s is "hello" ``` For primitive types, `as` automatically unboxes the value: ```dovetail let x: Any = 3.14 let f: Float64 = x as Float64 // unboxes to 3.14 ``` For records and enums, `as` returns the reference directly: ```dovetail record Point = x: Int32 y: Int32 let x: Any = Point { x = 1; y = 2 } let p: Point = x as Point assert p.x == 1 ``` ### The `is` + `as` Pattern Since `as` panics on failure, the idiomatic pattern is to test first with `is`, then cast: ```dovetail function tryGetInt(x: Any): Option = if x is Int32 then Some(x as Int32) else None ``` Or in a more complex example: ```dovetail function processValue(x: Any): String = if x is Int32 then let n = x as Int32 "integer: ${n * 2}" else if x is String then let s = x as String "string of length ${s.length}" else "unknown" ``` ### Restrictions For the concrete type recovery shown here, the subject is `Any`. These operators also support [class type tests and casts](https://dovetaillang.org/book/classes.md) and [interface upcasts](https://dovetaillang.org/book/type-system.md#611-interfaces-and-interface-types), subject to their own rules. They do not convert unrelated concrete types: ```dovetail let x: Int32 = 42 // x is String // compile error: 'is' requires subject of type Any // x as String // compile error: 'as' requires subject of type Any ``` For recovery from `Any`, the target must be concrete; `Any`, `Never`, and interface targets are unsupported: ```dovetail let x: Any = 42 // x is Any // compile error: target must be a concrete type // x is Never // compile error: target must be a concrete type ``` ### Working with Generic Types `is` and `as` work with generic type instantiations: ```dovetail let x: Any = Some(42) if x is Option then let opt = x as Option match opt with case Some(n) => assert n == 42 case None => panic "unexpected" ``` Different instantiations are distinguished at runtime: ```dovetail let a: Any = Some(42) let b: Any = Some("hello") a is Option // true a is Option // false b is Option // true ``` ### Working with Arrays Array element types are also distinguished: ```dovetail let x: Any = [|1, 2, 3|] x is Array // true x is Array // false ``` --- ## 6.11 Interfaces and Interface Types An `interface` describes behavior and can also be the type of a value. It lets a parameter, return value, field, or collection hold different implementations of the same contract. A plain `trait` describes behavior for generic bounds; it cannot appear as a value type. Both use `implement` blocks, and an interface can also be used as a generic bound. ### Declaring and Implementing Interfaces ```dovetail interface Label = function text(self): String record Person = name: String record NumberLabel = value: Int32 implement Label for Person = function text(self): String = self.name implement Label for NumberLabel = function text(self): String = "${self.value}" record Badge = label: Label function readLabel(label: Label): String = label.text() function chooseLabel(person: Bool): Label = if person then Person { name = "Ada" } else NumberLabel { value = 42 } function main(): Unit = let label: Label = Person { name = "Ada" } assert label.text() == "Ada" assert readLabel(NumberLabel { value = 42 }) == "42" assert chooseLabel(false).text() == "42" let badge = Badge { label = Person { name = "Grace" } } assert badge.label.text() == "Grace" let labels: List