Skip to content

stdlib: finish the String module - #47

Merged
simontreanor merged 1 commit into
mainfrom
feat/stdlib-string-sweep
Jul 31, 2026
Merged

stdlib: finish the String module#47
simontreanor merged 1 commit into
mainfrom
feat/stdlib-string-sweep

Conversation

@simontreanor

Copy link
Copy Markdown
Owner

Dogfooding finding #5, PR 4 of 5 (see #34). Branched from main, so it is independent of #46 (Map/Set) — the two touch different regions and can merge in either order.

Eight members, the smallest sweep because String was already the best-covered module: isEmpty get repeat trimStart trimEnd splitLines rev ofList.

get is the one worth arguing about. It answers Option and is bounds-checked, exactly like List.get, and since there is no char type a character comes back as a one-character string. Before this, reading one character meant String.slice i (i + 1) — total, but saying nothing about what it is for.

isEmpty takes the name every other container already uses, so List/Set/Map/Seq/String now all answer the same question the same way.

Padding is deliberately absent. Format.padLeft/padRight already take a width and a fill character, so a String spelling would be a second way to do one thing. Worth flagging because my estimate for this PR was nine members, and checking what already existed is what removed one — the same kind of check the FSharp.Core audit pass is for.

The rest are the obvious gaps: repeat, trimStart/trimEnd (strip already did both ends), splitLines (splits on any line ending, and a trailing newline does not add an empty last line), rev, and ofList as the inverse of toList.

Tests: 3 typecheck cases (monomorphic over string, get answering Option, ofList inverting toList), 1 lowering assertion (routes to Python's own str methods), and an end-to-end program covering all eight including both clamping edges. Full suite, clippy and fmt clean.

Eight members, the smallest of the sweeps because String was already the
best-covered module.

isEmpty takes the name every other container uses. get is the one worth
arguing about: it answers Option and is bounds-checked, exactly like
List.get, and since there is no char type a character comes back as a
one-character string. Before this, reading one character meant slice i
(i + 1), which is total but says nothing about what it is for.

The rest: repeat, trimStart and trimEnd (strip already did both ends),
splitLines, rev, and ofList as the inverse of toList.

Padding is deliberately absent. Format.padLeft and padRight already take a
width and a fill character, so a String spelling would be a second way to
do one thing. That is worth recording because the estimate for this PR was
nine members and the audit of what already existed is what removed one.

Also states the cost on every String member, which the module was missing
almost entirely. contains and tryIndexOf are O(n*m) worst case, being
searches rather than lookups, which is the one a reader would assume
wrong; the whole-string operations are O(n), slice is O(k) in the slice,
and repeat is O(n*k).
@simontreanor
simontreanor force-pushed the feat/stdlib-string-sweep branch from 09d0076 to 8e83d63 Compare July 31, 2026 16:36
@simontreanor
simontreanor merged commit 96770b9 into main Jul 31, 2026
11 checks passed
@simontreanor
simontreanor deleted the feat/stdlib-string-sweep branch July 31, 2026 16:40
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