Prefer methods over free functions in std and public APIs #146

Open
opened 2026-10-06 06:06:56 +00:00 by orion · 6 comments
Collaborator

What

Move the standard library and compiler-facing APIs from free functions towards methods where a method is the natural shape (the function operates on one receiver type, e.g. foo(x: Digest, ...) becomes x.foo(...)).

Why

Many free functions exist only because TorbScript syntax grew step by step; method syntax is available now and is the preferred API style (owner direction, 2026-10-06; first applied in #96).

Scope for refine

  • Inventory the free functions in std/ (and anything the compiler looks up by name) and classify: convert to method, keep as free function (no natural receiver, constructors, multi-receiver), or keep as thin wrapper for compatibility.
  • Flag every candidate that touches the seed (std names the compiler looks up by string, natives).
  • Decide the deprecation path for existing free functions (wrapper vs. removal) per release calendar.
  • Split into PR-sized issues per module, linked by dependency; this issue becomes the tracking epic.

Not in scope

A big-bang rename in one PR.

## What Move the standard library and compiler-facing APIs from free functions towards methods where a method is the natural shape (the function operates on one receiver type, e.g. `foo(x: Digest, ...)` becomes `x.foo(...)`). ## Why Many free functions exist only because TorbScript syntax grew step by step; method syntax is available now and is the preferred API style (owner direction, 2026-10-06; first applied in #96). ## Scope for refine - Inventory the free functions in `std/` (and anything the compiler looks up by name) and classify: convert to method, keep as free function (no natural receiver, constructors, multi-receiver), or keep as thin wrapper for compatibility. - Flag every candidate that touches the seed (std names the compiler looks up by string, natives). - Decide the deprecation path for existing free functions (wrapper vs. removal) per release calendar. - Split into PR-sized issues per module, linked by dependency; this issue becomes the tracking epic. ## Not in scope A big-bang rename in one PR.
Author
Collaborator

[loop:torbscript] claim refine by session:loop-torbscript-20261008 until 2026-10-08T09:15:00Z

[loop:torbscript] claim refine by session:loop-torbscript-20261008 until 2026-10-08T09:15:00Z
Author
Collaborator

[loop:torbscript] owner decision: deprecation path for the free functions that get a method (breaking-change policy, not settled in a design record).
Context: #96 already settled that the method is primary and the free function stays as a thin wrapper. Open is what happens to the wrapper afterwards. RELEASE.md section 3 says std only grows within 1.x (removal waits for 2.0; a member is marked deprecated with a replacement and torb lint --fix migrates callers); before 1.0 any breaking change needs a migration. DEPRECATION.md builds the mark and the lint fix.
Options:

  1. Keep the wrappers indefinitely, never deprecated: both spellings stay valid, docs lead with the method. No breaking change, no migration.
  2. Mark each wrapper deprecated(replacement: ..., since: ...) in the release after its method ships, torb lint --fix migrates callers, removal not before 2.0. Warnings, no break within 0.x/1.x.
  3. Deprecate as in 2 and remove the wrappers before 1.0 (a breaking minor of std with the lint migration).
    Also open: ~170 public free functions are undocumented helpers (scanner, wire, derive plumbing). Are they API (then they follow the chosen option) or internals to be made non-public in a separate, breaking round?
    Recommendation: option 2, and treat undocumented helpers as internals handled in their own later issue. It keeps the 1.x promise, uses the machinery that already exists, and needs no break.
    Until answered, no slice marks a wrapper deprecated or removes anything; slices that only add methods and keep the wrappers are not blocked.
[loop:torbscript] owner decision: deprecation path for the free functions that get a method (breaking-change policy, not settled in a design record). Context: #96 already settled that the method is primary and the free function stays as a thin wrapper. Open is what happens to the wrapper afterwards. RELEASE.md section 3 says `std` only grows within 1.x (removal waits for 2.0; a member is marked `deprecated` with a replacement and `torb lint --fix` migrates callers); before 1.0 any breaking change needs a migration. DEPRECATION.md builds the mark and the lint fix. Options: 1. Keep the wrappers indefinitely, never deprecated: both spellings stay valid, docs lead with the method. No breaking change, no migration. 2. Mark each wrapper `deprecated(replacement: ..., since: ...)` in the release after its method ships, `torb lint --fix` migrates callers, removal not before 2.0. Warnings, no break within 0.x/1.x. 3. Deprecate as in 2 and remove the wrappers before 1.0 (a breaking minor of `std` with the lint migration). Also open: ~170 `public` free functions are undocumented helpers (scanner, wire, derive plumbing). Are they API (then they follow the chosen option) or internals to be made non-public in a separate, breaking round? Recommendation: option 2, and treat undocumented helpers as internals handled in their own later issue. It keeps the 1.x promise, uses the machinery that already exists, and needs no break. Until answered, no slice marks a wrapper `deprecated` or removes anything; slices that only add methods and keep the wrappers are not blocked.
Author
Collaborator

[loop:torbscript] refine: inventory at 129f1574: 301 public free functions in std/ (excluding tests), ~85 documented in docs/standard-library/, ~215 undocumented helpers. Classification of the documented ones:

  • Convert to method (wrapper stays): std/compression (inflated, deflated, gzipped, gunzipped, crc32 on Bytes), std/encoding (hexEncoded on Bytes, hexDecoded, base64Decoded on String), std/archive (tarred, extract on List<TarEntry>, untarred on Bytes), std/markdown (markdownOf, htmlOf on MarkdownDocument, plainText on inlines), std/uri (formDecoded, formEncoded, percentDecoded), later round: std/stream textOf, std/dns presentation/query/readResponse, std/http send/get-style helpers taking Uri/Headers, std/core combineHashes. constantTimeEqual is done (#96).
  • Keep as free function (no natural receiver, entry point or combinator): std/console (print, printError, isTerminal, ...), std/task (spawn, both, pause, offload), std/time sleep, std/test (test, group), std/io (readLine, standardInput, ...), std/core (panic, do, unless, retry, isSame), std/expression (assert, nameOf), std/iteration factories (mapping, filtering, taking, ..., all collectors), std/os platform functions (version, hostName, uptime, directories), std/network resolve/lookup/systemNameServers, std/encoding structureOf and the derive plumbing, std/markdown parsedDocument.
  • Seed-touching, therefore never converted without an owner/seed plan: the names the compiler looks up by string in compiler/src/backend/c/natives.trb, compiler/src/ir/lower/* and compiler/src/semantics/checker/wellknown.trb: print, printError, isTerminal, sleep, pause, spawn, assert, plus every native fn of std/task, std/fs, std/console. The converted slices below touch none of them.
  • Undocumented public helpers (uri/characters, yaml/*, markdown/text, dns/wire, http/parse, regex/*, signature/*, os/<platform>): treated as internals, not converted; whether they are API is part of the owner question above.
    First slices (std only, wrapper kept, no seed touch, no deprecation, one PR each): #175 compression, #176 encoding, #177 archive, #178 markdown (all ready), #179 uri (queued, not ready because the ready buffer is full). Later rounds: stream/dns/http/core after these land.
    Worker usage: about 95k tokens (sonnet, medium).
    next: waiting (owner decision above on the wrapper deprecation path; the epic stays open as tracker, slices #175-#178 do not depend on it)
[loop:torbscript] refine: inventory at 129f1574: 301 `public` free functions in `std/` (excluding tests), ~85 documented in `docs/standard-library/`, ~215 undocumented helpers. Classification of the documented ones: - Convert to method (wrapper stays): `std/compression` (inflated, deflated, gzipped, gunzipped, crc32 on `Bytes`), `std/encoding` (hexEncoded on `Bytes`, hexDecoded, base64Decoded on `String`), `std/archive` (tarred, extract on `List<TarEntry>`, untarred on `Bytes`), `std/markdown` (markdownOf, htmlOf on `MarkdownDocument`, plainText on inlines), `std/uri` (formDecoded, formEncoded, percentDecoded), later round: `std/stream` textOf, `std/dns` presentation/query/readResponse, `std/http` send/get-style helpers taking `Uri`/`Headers`, `std/core` combineHashes. `constantTimeEqual` is done (#96). - Keep as free function (no natural receiver, entry point or combinator): `std/console` (print, printError, isTerminal, ...), `std/task` (spawn, both, pause, offload), `std/time` sleep, `std/test` (test, group), `std/io` (readLine, standardInput, ...), `std/core` (panic, do, unless, retry, isSame), `std/expression` (assert, nameOf), `std/iteration` factories (mapping, filtering, taking, ..., all collectors), `std/os` platform functions (version, hostName, uptime, directories), `std/network` resolve/lookup/systemNameServers, `std/encoding` structureOf and the derive plumbing, `std/markdown` parsedDocument. - Seed-touching, therefore never converted without an owner/seed plan: the names the compiler looks up by string in `compiler/src/backend/c/natives.trb`, `compiler/src/ir/lower/*` and `compiler/src/semantics/checker/wellknown.trb`: `print`, `printError`, `isTerminal`, `sleep`, `pause`, `spawn`, `assert`, plus every `native fn` of `std/task`, `std/fs`, `std/console`. The converted slices below touch none of them. - Undocumented `public` helpers (`uri/characters`, `yaml/*`, `markdown/text`, `dns/wire`, `http/parse`, `regex/*`, `signature/*`, `os/<platform>`): treated as internals, not converted; whether they are API is part of the owner question above. First slices (std only, wrapper kept, no seed touch, no deprecation, one PR each): #175 compression, #176 encoding, #177 archive, #178 markdown (all `ready`), #179 uri (queued, not `ready` because the ready buffer is full). Later rounds: stream/dns/http/core after these land. Worker usage: about 95k tokens (sonnet, medium). next: waiting (owner decision above on the wrapper deprecation path; the epic stays open as tracker, slices #175-#178 do not depend on it)
Author
Collaborator

Question q1 for torben (decision, blocking): What happens to a free-function wrapper after its method ships?

v: 1
id: q1
to: torben
kind: decision
blocking: true
question: "What happens to a std free-function wrapper after its method ships, and are the undocumented public helpers API? Details in the owner decision comment above."
options:
  - id: keep
    label: Keep wrappers indefinitely
    detail: Both spellings stay valid, docs lead with the method; no break, no migration
  - id: deprecate
    label: Deprecate, remove not before 2.0
    detail: Mark deprecated(replacement, since) after the method ships, torb lint --fix migrates; undocumented helpers handled as internals in a later issue
  - id: remove-before-1
    label: Deprecate and remove before 1.0
    detail: Breaking std minor with the lint migration
recommendation: deprecate
asked_by: orion
**Question q1** for torben (decision, blocking): What happens to a free-function wrapper after its method ships? ```hos-question v: 1 id: q1 to: torben kind: decision blocking: true question: "What happens to a std free-function wrapper after its method ships, and are the undocumented public helpers API? Details in the owner decision comment above." options: - id: keep label: Keep wrappers indefinitely detail: Both spellings stay valid, docs lead with the method; no break, no migration - id: deprecate label: Deprecate, remove not before 2.0 detail: Mark deprecated(replacement, since) after the method ships, torb lint --fix migrates; undocumented helpers handled as internals in a later issue - id: remove-before-1 label: Deprecate and remove before 1.0 detail: Breaking std minor with the lint migration recommendation: deprecate asked_by: orion ```
Author
Collaborator

[loop:torbscript] owner decision recorded (torben, 2026-10-08): "Noch niemand nutzt TorbScript, die sollen raus einfach, komplett" (nobody uses TorbScript yet, they should simply go, completely).

Resolution:

  • Free functions that get a method are removed outright in the same slice that adds the method. No deprecated phase, no kept wrapper, no torb lint --fix migration rule. Callers inside the repo (std, tests, examples, docs) are migrated in the same PR.
  • This supersedes the "free function stays as a thin wrapper" part of #96 and the "Wrapper bleiben" scope of the open slices.
  • The pre-1.0 "breaking change needs a migration" rule in RELEASE.md section 3 does not apply to this cleanup because there are no external users; the 1.x stability promise is unchanged.
  • Undocumented public helpers (~170, scanner, wire, derive plumbing): treated as internals (Orion's call under the same reasoning). They become non-public in their own follow-up slices per module, also without deprecation.

Status: decided, no longer waiting. Slices #175 to #179 get the updated scope.

[loop:torbscript] owner decision recorded (torben, 2026-10-08): "Noch niemand nutzt TorbScript, die sollen raus einfach, komplett" (nobody uses TorbScript yet, they should simply go, completely). Resolution: - Free functions that get a method are **removed outright** in the same slice that adds the method. No `deprecated` phase, no kept wrapper, no `torb lint --fix` migration rule. Callers inside the repo (std, tests, examples, docs) are migrated in the same PR. - This supersedes the "free function stays as a thin wrapper" part of #96 and the "Wrapper bleiben" scope of the open slices. - The pre-1.0 "breaking change needs a migration" rule in RELEASE.md section 3 does not apply to this cleanup because there are no external users; the 1.x stability promise is unchanged. - Undocumented `public` helpers (~170, scanner, wire, derive plumbing): treated as internals (Orion's call under the same reasoning). They become non-public in their own follow-up slices per module, also without deprecation. Status: decided, no longer waiting. Slices #175 to #179 get the updated scope.
Owner

[owner-answer] q=q1 option=remove-before-1 „Deprecate and remove before 1.0“ 2026-10-29T22:53:18Z

[owner-answer] q=q1 option=remove-before-1 „Deprecate and remove before 1.0“ 2026-10-29T22:53:18Z
Sign in to join this conversation.
No labels
loop
ready
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
torbscript/language#146
No description provided.