Invisible link to canonical for Microformats

ADR-021 Symbols, Map Keys, and Slice Grammar


Status

Accepted

Context

Slug is introducing symbols as a first-class value to represent names in code:

  • map keys for structured internal data
  • struct fields
  • tags for pattern matching and metadata

Slug already supports slice syntax using : inside index expressions (e.g. list[i:j], list[i:]). Introducing symbols must not introduce grammar ambiguity or hidden behavior.

Earlier exploration considered #symbol literals, but prior art (notably Julia) shows that a clean and conventional design is possible using :symbol, provided slice grammar is made explicit.

Slug also distinguishes between:

  • internal structured data (best represented with symbol keys)
  • external/dynamic data (JSON, HTTP headers, env vars; string keys)

This ADR formalizes symbols, map key semantics, slice grammar, and the associated stdlib APIs.

Decision

1. Symbols use : prefix syntax

Slug symbol literals use a leading colon:

:foo
:User
:darkMode
  • The portion following : must be a valid Slug identifier
  • Symbols are interned by the runtime
  • Equality and hashing are identity-based (interned id)

2. Slice grammar requires an explicit start expression

To avoid ambiguity between slice syntax and symbol literals, Slug adopts the Julia rule:

Slice expressions must always have an explicit start.

Allowed slice forms

list[0:n]
list[i:j]
list[i:]

Disallowed

list[:n]

This removes the only ambiguous case and aligns with Slug’s preference for explicitness over implicit defaults.

3. Map literal keys default to symbols

In map literals:

{ foo: 1, bar: 2 }
  • Bare identifiers are parsed as symbol keys
  • This is equivalent to { :foo: 1, :bar: 2 }

Explicit alternatives:

{ :foo: 1 }      // explicit symbol key
{ "foo": 1 }     // string key

4. Dot access uses symbol lookup only

Dot-walk is defined as symbol-based access:

m.foo    // desugars to m[:foo]
  • Dot access never looks up string keys
  • Bracket access (m[k]) always uses the exact key value provided

This keeps dot-walk predictable and aligned with future struct field access.

5. Maps support any hashable key type

Slug maps are general-purpose dictionaries:

  • Any hashable value may be used as a key (string, symbol, number, bool, bytes, etc.)
  • Map literal sugar only affects parsing, not map capabilities

6. Canonical symbol printing and textual form

Symbols have a canonical textual representation:

  • If the symbol text is a valid identifier:

    :foo
    
  • Otherwise (symbols created dynamically):

    :"foo bar"
    

This form is used for printing, inspection, and debugging.

7. Dynamic symbol creation via sym()

A standard-library function is provided:

sym(string) -> symbol
  • Interns the provided string as a symbol
  • Accepts any string (no identifier restriction)
  • If passed a symbol, returns it unchanged

This supports dynamic use cases and boundary normalization without complicating the language grammar.

8. Extracting symbol text via label()

A standard-library function is provided:

label(symbol) -> string
  • Returns the raw symbol text (without : or quotes)
  • Intended for reflection, debugging, and interop

9. Placement of sym and label

  • :symbol literals are core language syntax
  • sym() and label() are provided by the standard library as foreign functions, not VM builtins

Rationale:

  • keeps the core language small
  • allows evolution without VM changes
  • symbol literals and implicit symbol creation do not depend on stdlib presence

10. JSON decoding and encoding

JSON object keys are strings by definition.

  • Decoding JSON produces maps with string keys
  • Encoding JSON requires maps with string keys
  • No implicit conversion between string keys and symbol keys occurs

11. Style guidance (non-normative)

  • Use symbol keys for program-internal structured data
  • Use string keys for external/dynamic data:

    • JSON
    • HTTP headers
    • environment variables
  • Normalize at boundaries, once, explicitly

Consequences

Positive

  • Removes all grammar ambiguity between symbols and slices
  • Aligns Slug with well-established language designs (Julia-style)
  • Preserves expressive slice syntax
  • Makes internal data more explicit, readable, and intention-revealing
  • Keeps the VM and core language minimal
  • Provides clear, explicit data-boundary semantics

Negative

  • list[:n] must be rewritten as list[0:n]
  • Requires users to learn the distinction between symbol and string keys

Neutral / Future Work

  • Escaped symbol literal syntax beyond :"..." may be expanded later
  • Tooling (REPL, formatter, linter) should visually distinguish symbol vs string keys
  • Debug-mode diagnostics may suggest near-miss key types (:foo vs "foo")

Summary

This ADR establishes symbols as a foundational concept in Slug, clarifies map semantics, resolves slice ambiguity cleanly, and defines explicit boundaries between internal and external data — all while keeping the core language small, predictable, and aligned with Slug’s design philosophy.