Skip to content

Commit 9631e72

Browse files
feat(docs): Update stdlib contributor guidelines (#2277)
Co-authored-by: Oscar Spencer <oscar.spen@gmail.com>
1 parent 220bfc3 commit 9631e72

File tree

1 file changed

+60
-10
lines changed

1 file changed

+60
-10
lines changed
Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,67 @@
11
# Standard Library
22

3-
Grain aims to have a comprehensive and consistent standard library.
3+
Grain aims to cultivate a **comprehensive and consistent standard library**.
44

5-
To us, this means that someone should be able to reach for the standard library to perform most of their day-to-day work, and it will always work the way they expect in the context of Grain.
5+
To us, this means that users should be able to reach for the standard library to perform most day-to-day tasks, and it should behave predictably within the context of Grain.
66

77
## Guidelines
88

9-
Here are some guidelines for making additions to the standard library while also keeping it high-quality and consistent!
9+
Here are some guidelines for making additions to the standard library while maintaining a high-quality and consistent user experience.
1010

11-
1. New data types should exist in their own modules. For example, the `Range` enum is the data type exported from `range.gr`. An exception to this are data types that are ubiquitous (`Option`/`Result`/`List`), which should live in `pervasives.gr`.
12-
1. Prefer data constructors when possible. Only use separate constructor functions, like `make` or `init`, if needed to set an initialization value or to hide internals of your data structure.
13-
1. All functions that operate on a data type should exist in the same module as that data type. For example, all `Map` methods exist in the `map.gr` file.
14-
1. Fallible functions should almost always return an `Option` or `Result`. Usage of `fail` should be reserved for exceptional cases, such as argument validation producing an index out-of-bounds failure.
15-
* `Option` should be preferred if you might or might not be able to get some value. Typically, these would be functions that could return `null` in other languages when they didn't produce a value. For example, `List.find` returns `None` when no item in the list matches the condition.
16-
* `Result` is useful if you have multiple failures, or if the consumer might need additional context around the failure. Typically, these functions would throw exceptions in other languages. For example, `Number.parseInt` returns `Err(reason)` when it fails to parse a string into an integer.
17-
1. If possible, keep dependencies on other standard library modules to a minimum. For example, `Array` re-implements some simple `List` operations to avoid depending on the entire `List` module.
11+
### Scope
12+
13+
The standard library is intended to support common day-to-day needs of Grain developers while remaining reliable, and easy to maintain. Standard library modules include functionality that:
14+
15+
- Serves a broad set of programs
16+
- Enhances the developer experience by being ergonomic and consistent
17+
- Works reliably in standard WebAssembly and [WASI](https://github.com/WebAssembly/WASI) environments
18+
- Avoids unnecessary complexity and maintenance burdens
19+
20+
If a feature fits naturally into everyday development, improves clarity or safety, and can be implemented cleanly within these constraints, it might fit well into the standard library. For example, while the `Json` and `Regexp` modules are relatively complex, they are ubiquitous and require little ongoing work to maintain. In contrast, something more niche like a `Protobuf` module is likely better off as a community library as it evolves independently and requiring active maintenance, check out [awesome-grain](https://github.com/grain-lang/awesome-grain) for some community libraries.
21+
22+
### Organization
23+
24+
A consistent and well-structured standard library makes it easier for developers to find what they need and understand how to use it. In order to keep things clean and predictable, we organize the standard library around clear, single-purpose modules.
25+
26+
Each module should represent a singular focused concept, like a data structure (i.e. `Map`, `Set`) or utility (i.e. `Marshal`, `Json`). Data types should live within their respective modules unless they are ubiquitous and require deep language integration such as `Option`, `Result`, `List` or `Range` which live either directly in the compiler, or `Pervasives`.
27+
28+
Within an individual module exports should be grouped by functionality, with common exports being closer to the top of the module. Modified functionality such as immutable variants (i.e `Map.Immutable`, `Set.Immutable`) should exist within submodules.
29+
30+
The goal is to make the standard library intuitive to explore and easy to maintain,
31+
with as little surprise as possible for contributors and users alike.
32+
33+
### Testing
34+
35+
Every exposed function or module in the standard library should be thoroughly tested to ensure reliability, correctness, and predictability. Good testing helps to prevent regressions as features and changes are introduced. Tests exist in `../../compiler/test/stdlib/<module>.test.gr` when adding a new module, make sure to add `assertStdlib("<module>.test");` to `../../compiler/test/suites/stdlib.re`.
36+
37+
Testing should focus on:
38+
- **Correctness**: Ensure the function or module behaves as expected under both normal and edge cases
39+
- The `Number` library is a great example where we test each unique `Number` layout along with various edge cases
40+
- **Consistency**: Ensure the behavior of a function or module is consistent across different environments (i.e. `Windows`, Mac`, `Linux`)
41+
- **Simplicity**: Keep tests straightforward and focused. Each test should try to verify a single, simple behavior
42+
- **Methodical**: Tests should be ordered in a methodical manner isolating failures
43+
- As an example, if you are testing `Uint8` ensure you test `Uint8.(==)` before using it within other tests so we can quickly identify failures
44+
45+
A solid test suite helps to maintain the quality and stability of the standard library as a whole, while providing confidence that behaviors are consistent and reliable.
46+
47+
### Documentation
48+
49+
Every exposed `type` `module` and `value` **must** include a Graindoc docblock, [Documentation can be found here](https://grain-lang.org/docs/tooling/graindoc). As we strive for consistency and clarity, a great starting place for new documentation is finding existing documentation with similar functionality and adapting it your needs. We haven't gotten around to documenting all of our documentation patterns yet, but a non-inclusive list can be found [here](https://github.com/grain-lang/grain/issues/828).
50+
51+
### Common Patterns
52+
53+
#### Fallible Functions
54+
55+
Fallible functions (functions that may fail), should almost always return either an `Option` or `Result`.
56+
57+
- `Option` is preferred when a function may or may not return a value
58+
- These are cases where `null` might typically be returned in other languages
59+
- Example: `List.find` which returns `Some(index)` containing the index of the first element found or `None` otherwise
60+
- `Result` is preferred when a function may fail
61+
- There are numerous failure modes
62+
- The caller might need more information about what went wrong
63+
- Example: `Number.parseInt` which returns `Ok(value)` containing the parsed number on a successful parse or `Err(err)` containing a variant of `ParseIntError`
64+
- `throw`/`fail` is reserved for rare edge cases
65+
- The failure is very rare and recovery is unlikely
66+
- Grain doesn't yet have exception handling, meaning users cannot recover when these occur
67+
- Example: `Number.(/)` which may throw `DivisionByZero` if you divide by zero

0 commit comments

Comments
 (0)