Etymon.Base
0.1.0-preview.10
dotnet add package Etymon.Base --version 0.1.0-preview.10
NuGet\Install-Package Etymon.Base -Version 0.1.0-preview.10
<PackageReference Include="Etymon.Base" Version="0.1.0-preview.10" />
<PackageVersion Include="Etymon.Base" Version="0.1.0-preview.10" />
<PackageReference Include="Etymon.Base" />
paket add Etymon.Base --version 0.1.0-preview.10
#r "nuget: Etymon.Base, 0.1.0-preview.10"
#:package Etymon.Base@0.1.0-preview.10
#addin nuget:?package=Etymon.Base&version=0.1.0-preview.10&prerelease
#tool nuget:?package=Etymon.Base&version=0.1.0-preview.10&prerelease
Etymon.Base
The .NET base library, returning option and Result instead of null
and exceptions.
Part of the Etymon suite, but it
depends on nothing but FSharp.Core — not even another Etymon package. Take
it on its own if that is all you want.
The base library signals failure three different ways: null, a thrown
exception, and a bool with an out parameter. None of them are visible in a type, so none of
them are things the compiler can remind you about. Every function here replaces
one of those with a value: something absent is option, something that failed
for a reason worth branching on is Result, and nothing throws for an outcome
that was always going to happen sometimes.
Parsing, with the culture spelled out
Parse.int "42" // Some 42
Parse.decimal "1.50" // Some 1.50m -- scale preserved
Parse.bool "YES" // Some true
Parse.guid "not-a-guid" // None
Parse.dateOnly "2026-09-20" // Some ...
The plain names use the invariant culture, because a value that came off a
wire, out of a config file or from a command line has no culture and must not
acquire the machine's by accident. The In variants take one, for text a person
typed:
Parse.decimal "1,50" // None
Parse.decimalIn (CultureInfo.GetCultureInfo "de-DE") "1,50" // Some 1.50m
That first line matters more than it looks. Decimal.TryParse with
NumberStyles.Number — the obvious thing to write — parses "1,5" as 15
under the invariant culture, because the comma is a group separator there. A
price becomes ten times a price and nothing complains. The invariant parsers here
disallow grouping for exactly that reason.
Two other sharp edges are handled: Parse.float rejects NaN and the
infinities, and Parse.enum rejects a number with no matching member, which is
how (DayOfWeek)99 normally gets into a domain.
Strings
Str.toOption " " // None -- blank is absent
Str.split ',' "a, b ,, c" // [ "a"; "b"; "c" ]
Str.splitFirst '=' "KEY=a=b" // Some ("KEY", "a=b")
Str.equalsIgnoreCase "Content-Type" "content-type" // true
Str.truncate 8 "a long sentence" // "a long …"
Every comparison is ordinal unless its name says otherwise, because
culture-aware comparison is for sorting prose and is almost never right for a
header name, a config key or an identifier. Nothing here throws on null.
Named Str, not String, so it never shadows FSharp.Core's String module —
String.concat keeps working.
Dictionaries
Dict.tryFind "accept" headers // Some "application/json"
Dict.tryPick Parse.int "retries" settings // Some 3
Dict.ofListIgnoreCase [ "Content-Type", "json" ] // the right comparer for headers
Written over IDictionary and IReadOnlyDictionary, so they work on
Dictionary, ConcurrentDictionary and anything else implementing them.
Environment
Env.tryGet "PATH" // Some "..."
Env.tryGetInt "PORT" // Some 8080
Env.isEnabled "ETYMON_DEBUG" // false when unset -- the shape of a feature switch
Unset and set-but-blank are both absent by default, because an empty connection
string is as useless as a missing one. Env.tryGetAllowEmpty is there for the
rare case that genuinely cares.
File IO with a typed error
match File.tryReadAllText "appsettings.json" with
| Ok contents -> ...
| Error (FileError.NotFound path) -> ... // create a default
| Error (FileError.AccessDenied path) -> ... // tell the operator
| Error e -> eprintfn "%s" (FileError.describe e)
FileError names the cases a caller can actually do something about — NotFound,
DirectoryNotFound, AccessDenied, InUse, InvalidPath, NotValidText — and
puts everything else in IoFailure. Nobody should have to match on a message.
Two deliberate behaviours: writing creates the containing directory, since not doing so only ever produces a failure the caller would fix by creating it; and deleting something already absent succeeds, because the caller's intent has been met either way. Text is UTF-8 without a byte order mark.
Dir is the same for directories. Listings are sorted, so anything generated
from one is reproducible.
Non-goals
This package is deliberately bounded. It will not grow:
- Collection combinators.
List,Seq,ArrayandMapare already good. OptionandResultcombinators. Those belong inEtymon.Core, or in FsToolkit if you want the full set.- Custom operators. No
>>=, no<!>, nothing you have to learn. - HTTP, JSON, serialisation, logging, DI, process control. Not a framework.
- Async or task helpers. That is a design space of its own, and a large one.
- Wrappers for APIs that are already fine from F#. If calling it directly reads well, it does not belong here.
The list exists so that "no" is a cheap answer to a feature request, which is the only thing that keeps a utility package from becoming everything.
Public surface
The complete public surface of this package, every value with its full signature
and its documentation, is in Surface.fsi beside this file. It is written by the
build from the implementation, so it is where to learn what a function hands
back without compiling anything.
Licence
MIT.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Etymon.Core (>= 0.1.0-preview.10)
- FSharp.Core (>= 8.0.100)
-
net8.0
- Etymon.Core (>= 0.1.0-preview.10)
- FSharp.Core (>= 8.0.100)
NuGet packages (3)
Showing the top 3 NuGet packages that depend on Etymon.Base:
| Package | Downloads |
|---|---|
|
Etymon.Config
Configuration loaded through an Etymon schema, from environment variables, JSON files or a source of your own. Reports every missing or invalid setting at once, each with what was expected and which source supplied it. Sensitive settings are redacted wherever Etymon prints them. |
|
|
Etymon.Api
HTTP endpoints described once: method, typed route, query parameters, request and response schemas, and typed failures. Performs no HTTP itself. Separate adapter packages interpret the same declaration as a Giraffe handler, an ASP.NET Core route, a typed client, OpenAPI paths or TypeScript declarations. |
|
|
Etymon
The Etymon suite: one schema definition as the source of truth for JSON codecs, validation, OpenAPI, TypeScript, configuration and database structure. This package has no code of its own; it references the parts of the suite that cost nothing but FSharp.Core. The adapters (Etymon.Api.Giraffe, Etymon.Api.AspNetCore) and the generator package (Etymon.Invariants.FsCheck) are deliberately left out, because each carries a dependency that would then be everyone's. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.0-preview.10 | 0 | 9/22/2026 |
| 0.1.0-preview.9 | 0 | 9/22/2026 |
| 0.1.0-preview.8 | 0 | 9/22/2026 |
| 0.1.0-preview.7 | 0 | 9/21/2026 |
| 0.1.0-preview.6 | 0 | 9/21/2026 |
| 0.1.0-preview.5 | 0 | 9/21/2026 |
| 0.1.0-preview.4 | 28 | 9/21/2026 |
| 0.1.0-preview.3 | 33 | 9/21/2026 |
| 0.1.0-preview.2 | 37 | 9/20/2026 |
| 0.1.0-preview.1 | 34 | 9/20/2026 |