Skip to content

Typed Literals

You’ve already met one of these: Date"2026-06-15" in Dates & Times isn’t a separate language feature, it’s regular Literal dispatch dressed up as a literal. A typed literal starts with a type name and then a quoted, triple-quoted, or raw backtick string. The body becomes a list of fragments — alternating literal text and ${…} interpolations — that the type’s handler reduces into a value.

A typed literal starts with a type name: Sql"...", Date"...", Box"...", or “Regex`\d+```. The prefix type decides how to turn the literal body into a value.

To opt in, the prefix type implements Literal by defining from_fragments. A Sql"..." use site dispatches to the Literal.from_fragments implementation for Sql, passing a list that contains literal text plus the values from ${...} slots. That is ordinary interface dispatch, so the same coherence and orphan-rule checks apply:

import {
  std/io
  std/literals.{Fragment, Literal}
}

type Sql String

impl Literal for Sql {
  fn from_fragments(fragments: List<Fragment<String>>): Sql {
    body = Iter.reduce(fragments, |acc = "", frag|
      case frag {
        .Static(s) -> acc + s
        .Dynamic(v) -> acc + "'" + v + "'"
      }
    )
    Sql(body)
  }
}

fn main() {
  user = "alice"
  Sql(text) = Sql"SELECT * FROM users WHERE name = ${user}"
  io.print(text)
}

The body of Sql"…" is split into Static(text) and Dynamic(value) fragments slots wherever ${…} appears. The handler walks the list and builds the result however it likes — here we add SQL-style quotes around dynamic values, but a real implementation might validate, parameterize, or escape.

Because the prefix is a type, you have two natural ways to spell one:

  • A dedicated distinct type, as above — type Sql String both names the literal and carries the value it produces.
  • An existing domain type, so the use site reads as a constructor. Box"…" builds an actual Box — same spelling whether you write Box{contents: "x"} or Box"x":
import {
  std/literals.{Fragment, Literal}
}

struct Box {
  contents: String
}

impl Literal for Box {
  fn from_fragments(fragments: List<Fragment<String>>): Box {
    body = Iter.reduce(fragments, |acc = "", frag|
      case frag {
        .Static(s) -> acc + s
        .Dynamic(v) -> acc + v
      }
    )
    Box{contents: body}
  }
}

fn main(): Box {
  dbg Box"hello"
  dbg Box{contents: "world"}
}

Box"hello" and Box{contents: "hello"} build the same value (an actual Box), through different syntactic doors.

Regex literals are ordinary typed literals whose prefix type is Regex. Use a backtick body when regex syntax should pass through literally. Use a quoted body when the pattern needs interpolation:

import {
  std/regex.Regex
}

fn main(): Result<Unit, String> {
  digits = try Regex`\d+`
  prefixed = try Regex"room ${Regex.pattern(digits)}"
  text = "room 42, floor 7"

  dbg Regex.pattern(digits)
  dbg Regex.match?(digits, text)
  dbg Regex.find(digits, text)
  dbg Regex.find_all(digits, text)
  dbg Regex.match?(prefixed, text)

  Ok(Unit)
}

A Regex literal is a typed literal, not a special parser rule for regular expressions. Raw backtick bodies are usually best for regex syntax, while quoted bodies can still interpolate because they use the same Literal fragment machinery. Invalid patterns stay in ordinary Result flow.

The next chapter — FFI & Dynamic — steps back to the host boundary: embedding Nomi in host programs, wrapping host libraries in Nomi modules, and using Dynamic when a boundary value’s shape is not known yet.