Skip to content

Named bindings, flatMap, sortBy, entries, sort/reverse, template upgrades, and pipe RFC#27

Merged
myzie merged 1 commit into
mainfrom
roadmap-bindings-templates-sort
Jun 12, 2026
Merged

Named bindings, flatMap, sortBy, entries, sort/reverse, template upgrades, and pipe RFC#27
myzie merged 1 commit into
mainfrom
roadmap-bindings-templates-sort

Conversation

@myzie

@myzie myzie commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Implements the post-v1.1.0 roadmap distilled from the Mobius Ops integration feedback. (v1.1.0 itself was tagged from main separately, with the lazy-if behavior change called out in its release notes.)

Named element bindings in higher-order forms

Every iterating form (map, filter, flatMap, any, all, find, count, sortBy) now accepts a three-arg shape that names the element binding:

filter(orders, o, o.status == "paid")
map(reviews, r, sprintf("%s: %s", r.author, join(map(r.comments, c, c), ", ")))

The named form binds only the chosen name plus index. It does not bind it, so an enclosing two-arg form's it stays reachable from inside the body, which closes the documented nested-forms scoping gap. Two-arg calls are unchanged, and a three-arg call previously failed the arity check at eval time, so no working program changes behavior. it, index, true, false, nil, map, and if are rejected as binding names with clear errors. Identifiers(), the did-you-mean suggester, and the streamed filter(xs, p)[n] fast path all honor the new form.

New forms and builtins

  • flatMap(xs, body) / flatMap(xs, x, body): like map, but list body results splice element-by-element, nil splices as nothing (mirroring the nil-is-empty-list rule), and any other value appends as a single element. Strings never split into runes; splicing is one level deep.
  • sortBy(xs, key) / sortBy(xs, x, key): stable sort of a copy by a per-element key expression. Keys must be all numbers (int/int compares as int64, mixed promotes to float64, same as <) or all strings; anything else is an ErrEvaluate naming the offending element.
  • entries(m): new default builtin next to keys; sorted [{"key": k, "value": v}, ...], making maps iterable through the forms.
  • sort(xs) / reverse(xs): new opt-in builtins in CollectionFuncs(). Both return copies; sort reorders original elements without converting them.

Template batch

  • Composite values render as compact JSON (HTML escaping disabled, so & < > survive in webhook payloads and prompts): ${config} now produces {"retries":3,"timeout":"30s"} instead of map[retries:3 timeout:30s]. Composites that marshal to a JSON string (e.g. time.Time) render unquoted. Unmarshalable values fall back to the old formatting. This is a rendering behavior change, called out in the spec and guides.
  • WithTemplateDelimiters("${{", "}}"): GHA-style delimiters so shell/JS text like echo ${HOME} passes through literally. Opener must end in a { run with the matching } closer; $$ escaping applies only to $-prefixed openers.
  • WithTemplateFormatter(fn): per-template rendering escape hatch; returning false falls through to the default chain.
  • Template errors now report 1-based line:column (offset kept as supplementary detail), for both parse-time and render-time failures.
  • New Template.Segments() returns []TemplateSegment{Literal, Source, Offset, Line, Column, Program}, so hosts get editor hints, highlighting, and per-segment Identifiers() without re-implementing the template scanner (the Mobius parse.go use case).
  • Template-only options passed to Compile now fail with ErrCompile instead of being silently ignored. NewTemplate resolves options once and reuses the config across segments.

Pipe operator RFC (no implementation)

docs/rfcs/0001-pipe-operator.md covers the proposed a | f(x)f(a, x) desugar: precedence rules with worked examples, optional-access interaction, error taxonomy, the Go-subset identity question argued both ways, and alternatives. Recommendation: if adopted, ship opt-in behind WithPipeOperator(). No commitment made.

Docs and tests

Spec, higher-order-patterns.md (including a rewrite of the "why there's no let" section), templates.md, examples.md, llms.txt, README, doc-sync tests, and runnable example companions are all updated. New test files cover named bindings (scoping, shadowing, fast path, errors), flatMap/sortBy, entries/sort/reverse, and the template features; template fuzzing gained a custom-delimiter target (all five fuzz targets run clean).

🤖 Generated with Claude Code

…ate upgrades

Implements the post-v1.1.0 roadmap items from the Mobius Ops
integration feedback.

Language surface:
- Every iterating form (map, filter, flatMap, any, all, find, count,
  sortBy) now accepts a three-arg shape that names the element
  binding: filter(orders, o, o.status == "paid"). The named form
  binds only the chosen name plus index, leaving `it` resolving to an
  enclosing two-arg form, which closes the nested-forms scoping gap.
  Two-arg calls are unchanged.
- New flatMap form: like map, but list body results splice
  element-by-element, nil splices as nothing, and any other value
  appends as a single element.
- New sortBy form: stable sort of a copy by a per-element key
  expression; keys must be all numbers or all strings.
- New entries(m) builtin in the default set: sorted key-value pairs
  of a string-keyed map, making maps iterable through the forms.
- New sort(xs) and reverse(xs) builtins in CollectionFuncs().

Templates:
- Composite values (maps, slices, arrays, structs) render as compact
  JSON with HTML escaping disabled instead of Go map syntax. This is
  a rendering behavior change; see the spec's templates section.
- WithTemplateDelimiters("${{", "}}") replaces the default `${`/`}`
  so shell snippets like ${HOME} pass through literally.
- WithTemplateFormatter(fn) is an escape hatch that runs before the
  default rendering chain.
- Template errors now report 1-based line:column with the byte
  offset as supplementary detail.
- New Template.Segments() exposes literal and expression segments
  with positions and each segment's compiled *Program, for editor
  hints and per-segment Identifiers().
- Template-only options passed to Compile fail with ErrCompile
  instead of being silently ignored.

Also adds docs/rfcs/0001-pipe-operator.md, a design RFC for
repurposing `|` as a pipeline operator. No implementation commitment;
the recommendation is opt-in via a WithPipeOperator option if
adopted.

Spec, guides, examples, llms.txt, README, doc tests, and runnable
example companions are updated to match. Template fuzzing now covers
custom delimiters.

Co-Authored-By: Claude Fable 5 <[email protected]>
@myzie myzie merged commit 5e8333c into main Jun 12, 2026
1 check passed
@myzie myzie deleted the roadmap-bindings-templates-sort branch June 12, 2026 19:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant