|
root / docs / reef-feedback.md
reef-feedback.md markdown 115 lines 6.9 KB

reef-lang Feedback — Surfaced While Planning repoman v0.1

Source: Spec'ing repoman (a real Linux CLI tool: Incus container management + rsync/NFS backup) against reef-lang 0.5.9 stdlib. Items below are gaps and quirks discovered while mapping the spec onto actual stdlib APIs. None are blockers — repoman v0.1 will work around them — but each is a friction point a future user will hit when writing a real production reef program.


1. Encoders missing for structured formats — highest impact

Every config-driven program eventually needs to write its config, not just read it. Today:

Module Reads Writes
encoding.toml yes (toml_parse, toml_get*, toml_array_*) no
encoding.json yes (json_parse, json_get*) no
encoding.yaml yes only line-at-a-time yaml_encode_string/int/bool — no nested tables, no lists
encoding.ini yes (line helpers) no
encoding.csv TBD TBD

What repoman needs: a toml_serialize(...) (or equivalent for one of the structured formats) that handles strings, ints, bools, string arrays, tables, and table-arrays ([[project]]).

Suggested API shape (TOML; same idea for JSON):

fn toml_serialize(keys: [string], values: [string], count: int): string
// or a higher-level builder:
type TomlBuilder
fn toml_builder(): TomlBuilder
proc toml_set_string(b: TomlBuilder, key: string, val: string)
proc toml_set_int(b: TomlBuilder, key: string, val: int)
proc toml_set_bool(b: TomlBuilder, key: string, val: bool)
proc toml_set_string_array(b: TomlBuilder, key: string, vals: [string])
proc toml_array_append_table(b: TomlBuilder, name: string)  // for [[project]]
fn toml_render(b: TomlBuilder): string

Parser/serializer asymmetry is the single biggest gap stdlib has for "write a real config-driven program."

2. io.file is missing rename and fsync

io.file exposes readFile, writeFile, appendFile, deleteFile, fileExists, fileSize, plus binary versions. Missing:

  • rename(old, new)fs.ops.rename exists, so this is just a discoverability issue (people will look in io.file first).
  • fsync(path) — there is no way to force a file's contents to disk before a rename. Important for the standard atomic-write recipe (write tmp → fsync → rename). Today repoman has to skip the fsync and accept a brief power-loss inconsistency window.

Suggested: add fn fsync(path: string): bool (or expose a file-handle-based API, then add flush_to_disk(handle)).

3. No generic Result[T, E]

core.result only exports concrete ResultInt, ResultStr, ResultBool, ResultFloat. For functions that return structs (e.g., load_registry() : Result<Registry, string>), every caller has to define a local enum:

type RegistryLoad = enum
    Ok(Registry)
    Err(string)
end RegistryLoad

core.option_generic already provides Option[T] — the same pattern would be a clean fit for Result[T, E]. The existing concrete ResultInt/ResultStr/etc. could be deprecated aliases or stay as ergonomic shortcuts.

Suggested: add core.result_generic with Result[T, E], is_ok[T,E], is_err[T,E], unwrap_ok[T,E], unwrap_err[T,E].

4. io.path is missing expand_home

~/foo expansion is one of the most frequent path operations in any user-facing CLI. Today io.path has join_path, dirname, basename, extension, normalize, is_absolute — but no expand_home or expand_user. Every caller writes (corrected from v1: str.substring_from does not exist publicly; the real call is str.substring(s, start, len)):

fn expand_home(p: string): string
    if str.starts_with(p, "~/")
        let len = str.length(p)
        return path.join_path(env.get_env_or("HOME", ""), str.substring(p, 2, len - 2))
    end if
    return p
end expand_home

Suggested: fn expand_home(path: string): string (and maybe expand_user(path) for ~user/...) in io.path.

5. sys.flag doesn't support subcommand dispatch

sys.flag is a V-style flat flag parser. It calls sys.args directly inside parse() — there's no way to feed it a subcommand-sliced argv. The common CLI shape tool <subcommand> [subcommand-flags] (git, kubectl, incus, podman, repoman) requires a hand-rolled outer parser to extract the subcommand, then either a hand-rolled per-subcommand parser or a way to give sys.flag a sub-slice of argv.

Suggested: either

  • (a) flag_parser_from(args: [string]): FlagParser so callers can pass a sliced argv, or
  • (b) a subcommand_parser API at the same level as flag_parser that handles the dispatch.

Even a doc note ("for subcommand tools, hand-roll using sys.args.get(i)") would help — today the example in sys/flag.reef makes it look like the answer.

6. test.framework thin assertion surface

TestRunner (an active object — nice) already has assert_eq_int/bool/string/float, and as of v0.5.9 also has assert_true, assert_false, and assert_not_eq_int/bool/string (filer error in v1 of this report — caught reading only the first 100 lines of a 235-line file). Genuinely missing:

  • assert_contains(haystack, needle, message) for substring/array membership.
  • assert_ok_* / assert_err_* for Result types (uses pattern-match plus a diagnostic that surfaces the wrapped error/value on failure).

7. TOML parser quirks

  • Hard 1024-entry cap in toml_alloc_keys(). Adequate for repoman (~127 projects max), but the cap is invisible — toml_parse returns the entry count it processed; if the file had more, callers don't know. Suggest a return value indicating "truncated" (or accept a max-entries arg and error if exceeded — the _sized variants already do the latter, so making them the recommended path would help).
  • Parallel-array repr ([string] keys, [string] values, int count) forces every caller to thread three values around. A small TomlDoc struct (or [Pair] array) would be friendlier — the runtime cost is the same but the API surface is much smaller.

8. Naming nit

io.path.join_path(a, b) — the _path suffix is redundant since the function is in io.path. Most languages call this path.join. Minor, but discoverability matters when learning a new stdlib.


Summary table (ordered by impact on real-world reef programs)

# Item Severity Type
1 TOML/JSON encoders missing High Feature gap
2 io.file no rename/fsync High Feature gap
3 No generic Result[T,E] Medium API design
4 io.path.expand_home missing Medium Feature gap
5 sys.flag no subcommand support Medium API design
6 test.framework thin assertions Medium Feature gap
7 TOML parser caps & repr Low API design
8 join_path naming Low Naming

repoman v0.1 works around all of these and ships. But every one of them costs the project some hand-rolled glue code that should be in stdlib.