Skip to content

stdlib: state the cost on every traversing member, and test it - #49

Merged
simontreanor merged 1 commit into
mainfrom
docs/member-cost-coverage
Jul 31, 2026
Merged

stdlib: state the cost on every traversing member, and test it#49
simontreanor merged 1 commit into
mainfrom
docs/member-cost-coverage

Conversation

@simontreanor

Copy link
Copy Markdown
Owner

Follow-up to the sweep, from the question of whether the big-O notation actually covers everything the last few PRs added. It did not.

I wrote a checker over MEMBER_DOCS and found three groups. Map/Set were already covered. String was missing costs almost entirely, which #47 fixed on its way in. This PR closes the rest:

  • List.range — O(n), and worth saying because it builds the list where Seq.range does not.
  • List.iter, List.init, List.replicate, List.chunkBySize — O(n), simply missed.
  • Seq.fold, Seq.toList, Seq.ofList, Seq.initInfinite, Seq.unfold — each had its lazy-or-consuming half implied rather than said. Seq.ofList is now explicitly O(1) with no copy, which is the one a reader might assume otherwise.

The durable part is the test. Every member of List, Seq, Set, Map and String must state a cost — in big-O, or (for Seq) in what it consumes — with a four-name exemption list for the genuine constants: Set.empty, Map.empty, String.fromInt, String.fromFloat.

Not held to it, deliberately: Option and Result hold one value and are uniformly O(1), so a complexity on all 24 of their members would be noise rather than information; Format, Decode and the globals are not traversals either. That exemption is by module and is stated in the test's own documentation, so the omission reads as a decision rather than an oversight.

This joins the three existing MEMBER_DOCS tests (every member documented, every entry real, every member has a signature), so the standard for a new stdlib member is now: it exists, it is documented, its documentation states its cost, and it has a signature.

The sweep left a handful of members without a complexity: List.range
(which builds a list where Seq.range does not), iter, init, replicate and
chunkBySize, plus five Seq members whose lazy-or-consuming half was
implied rather than said.

The test is the durable part. Every member of List, Seq, Set, Map and
String must now state a cost, in big-O or in what it consumes, with a
four-name exemption list for the genuine constants. Option and Result hold
one value and are uniformly O(1), and Format, Decode and the globals are
not traversals, so those modules are not held to it, which the test says
in its own documentation so the omission reads as a decision.
@simontreanor
simontreanor merged commit 60b6d08 into main Jul 31, 2026
11 checks passed
@simontreanor
simontreanor deleted the docs/member-cost-coverage branch July 31, 2026 16:51
@simontreanor simontreanor mentioned this pull request Jul 31, 2026
simontreanor added a commit that referenced this pull request Jul 31, 2026
Two dogfooding reports from real programs, and the standard-library sweep
they triggered.

Language:

* a `type` declaration can name an imported type, bare or module-qualified
  (#36) — the one gap that changed a program's architecture rather than its
  phrasing, forcing two modules into one file
* field access resolves from the base's type when it is known, so two
  records may share a field name without prefixes (#37)
* parameters destructure: tuples (#38), records (#40), and `_`
* a direct self tail call lowers to a loop, so an interactive turn loop no
  longer walks the stack (#39, #41)

Standard library — about 115 new members, taking every module to the F#
core set: List (#42), Seq (#44), Set and Map (#46), String (#47), Option
and Result (#48), then a member-by-member FSharp.Core audit (#51). Every
built-in member now carries a one-line description and its complexity in
hover and completion (#43, #49), enforced by tests.

Fixes:

* `pyfun run` on a single file gives the program its own stdin, so an
  interactive program is runnable by the command whose job is running
  programs (#35)
* a partially applied lambda closes over its argument instead of being
  wrapped, so `List.map ((+) 2)` emits `lambda b: 2 + b` (#52)
* every multi-argument callback's scheme put the effect variable on the
  wrong arrows, so `List.fold` could never accept an effectful folder (#51)
* `Seq.empty` lowered to a bare `iter()`, a TypeError (#51)

One source-incompatible change, which is why this is 0.4.0 and not 0.3.1:
a dotted `extern` target whose module prefix cannot be decided from the
text is now a compile error naming the `extern import` to add (#50).
`sys.stdout.flush` used to emit `import sys.stdout` and fail at runtime;
declaring `extern import sys` fixes it.
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