Morphir.SDK.FSharp
0.4.0-alpha.1
Prefix Reserved
See the version list below for details.
dotnet add package Morphir.SDK.FSharp --version 0.4.0-alpha.1
NuGet\Install-Package Morphir.SDK.FSharp -Version 0.4.0-alpha.1
<PackageReference Include="Morphir.SDK.FSharp" Version="0.4.0-alpha.1" />
<PackageVersion Include="Morphir.SDK.FSharp" Version="0.4.0-alpha.1" />
<PackageReference Include="Morphir.SDK.FSharp" />
paket add Morphir.SDK.FSharp --version 0.4.0-alpha.1
#r "nuget: Morphir.SDK.FSharp, 0.4.0-alpha.1"
#:package Morphir.SDK.FSharp@0.4.0-alpha.1
#addin nuget:?package=Morphir.SDK.FSharp&version=0.4.0-alpha.1&prerelease
#tool nuget:?package=Morphir.SDK.FSharp&version=0.4.0-alpha.1&prerelease
Morphir.SDK.FSharp
F# runtime library providing Morphir SDK types and functions for generated F# code.
Overview
Morphir.SDK.FSharp provides the F# runtime used by generated Morphir code.
Its namespace and runtime assembly remain Morphir.SDK.
Installation
dotnet add package Morphir.SDK.FSharp
Migrating existing consumers
Replace the Morphir.SDK package reference with Morphir.SDK.FSharp, using the
same release version. Keep open Morphir.SDK and existing generated source
unchanged. Project-reference consumers use
src/Morphir.SDK.FSharp/Morphir.SDK.FSharp.fsproj.
The legacy Morphir.SDK package remains installable. It depends on the exact
matching canonical package version and contains no runtime assembly. Installing
either package or both resolves the same Morphir.SDK.dll. Do not reference
the legacy pack-only project as a runtime project dependency.
The migration preserves the Morphir.SDK.Order type identity, existing public
entry points and F# aliases. Aliases such as Maybe<'a>, Dict<'k,'v>,
LocalDate and LocalTime retain their FSharp.Core/BCL types. They do not
become new CLR wrapper or forwarding types.
Select the FSharp.Core version required by the package. The captured baseline
uses 11.0.100; a compiler's lower implicit dependency can otherwise cause
NU1605 and runtime loading failures. For an SDK whose implicit version is lower:
<PropertyGroup>
<FSharpCoreImplicitPackageVersion>11.0.100</FSharpCoreImplicitPackageVersion>
<WarningsAsErrors>$(WarningsAsErrors);NU1605</WarningsAsErrors>
</PropertyGroup>
The bounded conformance contract and migration corrections are tracked in issue #465. Packaging identity preservation does not claim that behavior corrections replace old code already inlined into an existing binary.
Covered behavior changes
The package migration also corrects the cases admitted by the shared
morphir-dotnet-sdk-v1 / dotnet-sdk-v1 contract. It does not certify every SDK
operation or every host representation.
Dict.union left rightnow keepsleft's value for a shared key. Previously the second dictionary won. For example, unioning[("rate", 1)]with[("rate", 9)]now yields[("rate", 1)]. Both operands remain unchanged.- Int32
Basics.add,subtract,multiplyandnegatereport overflow instead of wrapping. The generic public entry points remain available. Basics.remainderBy -1 System.Int32.MinValueandBasics.modBywith those operands return zero. Previously the host remainder instruction overflowed.Basics.comparecompares direct String operands by ordinal UTF-16 code units, without culture or normalization. For example, decomposed"e\u0301"sorts before precomposed"\u00e9"; they no longer compare equal through collation.Decimal.toStringuses invariant positional text and removes fractional trailing zeros.1.50Mbecomes"1.5", and scaled zero becomes"0".Decimal.fromStringparses this canonical representation first under every culture, then tries the caller's culture for other input. For example, underde-DE, both"1.5"and localized"1,5"mean1.5M. Ambiguous dot-separated input follows canonical decimal-point semantics rather than localized grouping. Parsing remains a native API compatibility check outside the frozen inventory.- Additive
Basics.round,floor,ceilingandtruncateconvert Float to Int32 with non-finite and overflow checks. Round resolves halves toward positive infinity:round -2.5 = -2. - Additive
Decimal.divreturnsNonefor a zero divisor andSome quotientotherwise. ExistingDecimal.dividestill returns decimal and retains its existing zero-divisor exception behavior. Exact terminating Decimal96 division is covered; nonterminating or unrepresentable results are separate exclusions.
Already compiled inline calls can retain their old arithmetic, union or formatting behavior. Recompile consumers to use corrected inline bodies. Installing the canonical runtime alone preserves their type loading; it cannot replace code embedded in another assembly.
Features
- Type Aliases: Morphir types mapped to idiomatic F# types (e.g.,
Maybe<'a>=Option<'a>) - Standard Library: F# SDK functions; the shared contract defines the bounded conformance inventory
- Immutable Collections: Dict (Map), Set, List operations
- Date/Time Support: LocalDate (DateOnly), LocalTime (TimeOnly)
- AOT Compatible: No reflection, ready for Native AOT compilation
Modules
Core Types
- Basics: Fundamental operations (Order type, comparison, arithmetic)
- Maybe: Optional values (
Option<'a>alias with helper functions) - Result: Error handling (
Result<'v, 'e>with combinators)
Collections
- List: List operations and extensions
- Dict: Immutable dictionaries (Map<'k, 'v> alias)
- Set: Set operations
- Tuple: Tuple helper functions
Primitives
- String: String manipulation functions
- Int: Integer operations
- Bool: Boolean logic
- Char: Character operations
- Decimal: Decimal arithmetic
Date/Time
- LocalDate: Date without timezone (DateOnly alias)
- LocalTime: Time without timezone (TimeOnly alias)
Usage Examples
Maybe (Option)
open Morphir.SDK
let user = { name = "Alice"; email = Some "alice@example.com" }
let emailDomain =
user.email
|> Maybe.map (String.split "@")
|> Maybe.andThen List.last
|> Maybe.withDefault "unknown"
Result
open Morphir.SDK
let validateAge age =
if age < 0 then
Result.err "Age cannot be negative"
elif age > 150 then
Result.err "Age is too high"
else
Result.ok age
let processAge =
validateAge 25
|> Result.map (fun age -> age + 1)
|> Result.withDefault 0
List
open Morphir.SDK
let numbers = [1; 2; 3; 4; 5]
let result =
numbers
|> List.map (fun x -> x * 2)
|> List.filter (fun x -> x > 5)
|> List.sum
// result = 18
Dict
open Morphir.SDK
let scores =
Dict.fromList [
("Alice", 95)
("Bob", 87)
("Charlie", 92)
]
let aliceScore = Dict.get "Alice" scores
// Some 95
let updatedScores = Dict.insert "David" 88 scores
LocalDate
open Morphir.SDK
let today = LocalDate.today()
let nextWeek = LocalDate.addDays 7 today
let formatted = LocalDate.toIsoString nextWeek
// "2025-01-07"
let parsed = LocalDate.fromIsoString "2025-12-31"
// Some (LocalDate(2025, 12, 31))
Relationship to morphir-elm
This library provides F# implementations of types and functions from morphir-elm's SDK. The API is designed to maintain semantic compatibility while leveraging F#'s native types and idioms.
Key Mappings
| morphir-elm | Morphir.SDK (F#) | F# Native Type |
|---|---|---|
Maybe a |
Maybe<'a> |
Option<'a> |
Result e v |
F# Result<'v, 'e> |
Result<'v, 'e> |
List a |
'a list |
List<'a> |
Dict k v |
Dict<'k, 'v> |
Map<'k, 'v> |
Set a |
Set<'a> |
Set<'a> |
LocalDate |
LocalDate |
DateOnly |
LocalTime |
LocalTime |
TimeOnly |
Design Principles
- Type Aliases Over Wrappers: Use F# built-in types directly when possible
- Zero-Cost Abstractions: No runtime overhead for type conversions
- Idiomatic F#: Follow F# conventions and naming patterns
- AOT Compatibility: No reflection or dynamic code generation
- Semantic Compatibility: Maintain morphir-elm behavior
Version Compatibility
- Canonical package:
Morphir.SDK.FSharp - Compatibility package:
Morphir.SDK, at the matching version - .NET: 10.0+
- F#: 9.0+
- morphir-elm: Compatible with Morphir IR 2.x and 3.x
Contributing
This library is part of the morphir-dotnet project. For contributions, please see the main repository's CONTRIBUTING.md.
License
Apache License 2.0 - See LICENSE in the morphir-dotnet repository.
Links
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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
- FSharp.Core (>= 11.0.100)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Morphir.SDK.FSharp:
| Package | Downloads |
|---|---|
|
Morphir.SDK
Compatibility package for the Morphir F# runtime. Depends on Morphir.SDK.FSharp, which retains the Morphir.SDK namespace and runtime assembly identity. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.5.0-alpha.3 | 49 | 11/14/2026 |
| 0.5.0-alpha.2 | 46 | 11/13/2026 |
| 0.5.0-alpha.1 | 46 | 11/13/2026 |
| 0.4.0-alpha.5 | 48 | 11/13/2026 |
| 0.4.0-alpha.4 | 50 | 11/10/2026 |
| 0.4.0-alpha.3 | 58 | 11/9/2026 |
| 0.4.0-alpha.2 | 59 | 11/8/2026 |
| 0.4.0-alpha.1 | 57 | 11/7/2026 |
### Fixed
- The global Morphir home is no longer discovered as a project. Walking up from a directory under the home folder used to find `~/.morphir/morphir.toml` through the `.morphir/` layout and load it as both a global source and the project's primary. It now loads only as a global source ([#494](https://github.com/finos/morphir-dotnet/issues/494))
- A native Windows `MORPHIR_HOME` such as `C:/Users/me/.morphir` works. It was treated as relative and joined onto the current directory, so every `config` command failed with `discovery.read_error` ([#494](https://github.com/finos/morphir-dotnet/issues/494))
- Every Morphir assembly now takes its version from CHANGELOG.md, so assemblies no longer reference each other as `1.0.0.0`. An explicit `-p:Version` or `-p:PackageVersion` still wins, for local and test packages. Previously the packed `dotnet-morphir` tool failed `generate` with `Could not load file or assembly 'Morphir.Models, Version=1.0.0.0'`
- `Morphir.Tooling` and `Morphir` can now be installed from NuGet: releases also publish their dependencies `Morphir.Configuration`, `Morphir.Configuration.IO`, `Morphir.IR.Pipeline`, `Morphir.IR.Pipeline.Plugins`, `Morphir.Ion` and `Morphir.CodeGeneration.FSharp`. A build test now fails if a packed package depends on a Morphir package that is not packed
- GitHub releases now include the `morphir-{rid}-v{version}.tar.gz` archives that the proto plugin downloads, so `proto install morphir` works again
- The deployment workflow now requires the release version to be the latest release in CHANGELOG.md, which is the version the build stamps. The ignored `--version` arguments and the `--version-override` build parameter, which had no effect, are removed
- `morphir ir verify --json` no longer crashes in the standalone executables with `Reflection-based serialization has been disabled`; it now uses source-generated JSON serialization
- The deployment workflow's end-to-end tests run again. Their test filter was malformed and selected no tests, which hid an invalid step definition, a duplicate step definition and the `ir verify --json` crash
- `PublishLocalTool` installs from the local package directory again; it no longer fails on the repository's package source mapping
- The documentation site builds again: Docsy is pinned to v0.13.0 and Hugo to 0.169.0, after Docsy v0.18 moved its theme out of the module root
- F# `Decimal.fromString` roundtrips invariant SDK output under comma-decimal cultures while retaining unambiguous localized input; package migration checks also exercise a later release version ([#465](https://github.com/finos/morphir-dotnet/issues/465)).
- F# SDK arithmetic detects covered Int32 overflow; minimum-Int remainder/modulus by -1 return zero, direct string comparison uses ordinal UTF-16 order, dictionary union keeps the first value, and Decimal text is invariant without trailing fractional zeros. Added checked Float-to-Int rounding and optional `Decimal.div` while retaining existing public entry points ([#465](https://github.com/finos/morphir-dotnet/issues/465)).
- `VFileTree.writeToDisk` no longer writes outside the output directory. It returns `TreeWriteError.InvalidTree` and writes nothing for rooted or `..` paths, files with no path, and duplicate locations, and it no longer adds the root tree path to output paths (#376)
- `VFileTree.toFileMap` and `toStringMap` keys are now relative to the tree root and use forward slashes on every platform (#376)
- `VFileTree.findFile` and `updateFile` accept a file's full location, and `updateFile` no longer changes same-named files in other directories (#376)
- Claude Code skills now load correctly - renamed all `skill.md` files to `SKILL.md` (uppercase, case-sensitive requirement)
- Updated all skill documentation references to use correct `SKILL.md` filename
- **CRITICAL**: Removed contradictory Claude co-author example from AGENTS.md and strengthened CLA compliance warnings in both AGENTS.md and CLAUDE.md (issue #270)
### Added
- `MORPHIR_DISCOVERY_BOUNDARY` and `MORPHIR_ISOLATED` environment variables mirror `--discovery-boundary` and `--isolated` for the `config` commands, and `MORPHIR_DISCOVERY_BOUNDARY` also bounds `project migrate` and `workspace migrate`, which gains `--discovery-boundary`. A flag wins over its variable, which wins over the default. `--isolated` now accepts `true` or `false`, so a command can turn off isolation set in the environment, and `config path --json` reports the effective controls and their sources ([#494](https://github.com/finos/morphir-dotnet/issues/494))
- Shared test sandbox, child-process runner and machine tripwire in `tests/Morphir.TestSupport`, so tests stay off the developer's real Morphir configuration ([#494](https://github.com/finos/morphir-dotnet/issues/494))
- Synchronized both landed C# CLI deltas into the main specifications and archived all nine checked tasks; archive PR landing and coordinator cleanup remain pending ([#481](https://github.com/finos/morphir-dotnet/issues/481), [#482](https://github.com/finos/morphir-dotnet/pull/482)).
- Added exact `csharp` CLI generation with numeric 14/15 profiles, a validated optional namespace and the existing owned publication/reporting flow ([#481](https://github.com/finos/morphir-dotnet/issues/481)).
- Synchronized the landed C# generator's eight requirements and 19 scenarios into the main specification and prepared its 13-task OpenSpec archive; archive follow-up landing and coordinator cleanup remain pending ([#474](https://github.com/finos/morphir-dotnet/issues/474), [#478](https://github.com/finos/morphir-dotnet/pull/478)).
- Reusable in-memory C# generation for typed classic/v4 libraries with numeric 14/15 profiles, immutable structural values, executable functions/patterns/record operations, public SDK bindings, located whole-model refusals and traversal cancellation; compiled business/SDK observations and bounded modern package-host/trimming verification ([#474](https://github.com/finos/morphir-dotnet/issues/474)).
- Synchronized and archived the landed C# and F# SDK runtime specifications, with stable API handoff paths and separate runtime delivery evidence; archive PR #472 landed; empty archive-directory cleanup remains pending ([#464](https://github.com/finos/morphir-dotnet/issues/464), [#465](https://github.com/finos/morphir-dotnet/issues/465)).
- Independent immutable Morphir.SDK.CSharp runtime for the bounded .NET SDK contract, with checked scalars, persistent collections and public package consumer verification ([#464](https://github.com/finos/morphir-dotnet/issues/464))
- Canonical `Morphir.SDK.FSharp` runtime package and dependency-only `Morphir.SDK` compatibility package, preserving existing runtime identities, with packaged source/binary checks and the frozen F# conformance adapter ([#465](https://github.com/finos/morphir-dotnet/issues/465))
- Shared .NET SDK conformance contract, pinned source evidence, tagged offline corpus and fixture validator for the C# and F# SDK deliveries ([#460](https://github.com/finos/morphir-dotnet/issues/460))
- `Morphir.Ion` library project: reads Ion 1.0 text and binary into immutable values, and reads the MEP `morphir_model` document with `ModelDocument.readPayload`, `readFile` and `decodeIr`. Reading keeps the exact bytes, annotations, numeric exactness and unknown data, and decodes the classic or v4 IR only on request
- `IonIr.read` and `IonIrTree.read` read the draft Ion IR format `ionVersion` `0.1.0-draft.1` for IR 3.0.0, 3.1.0 and 4.0.0, from a single file or a document tree. `IonIrParser` plugs them into a pipeline
- `Morphir.IR.V4`, an immutable IR v4 model, with the JSON reader `V4Json` and strict name parsing in `CanonicalNames`
- `ClassicJson.readDistribution`, a public reader for complete classic IR JSON, including dependency specifications
- `VFileTree` usage and morphir-elm migration guide in the `Morphir.IR.Pipeline` README (#376)
- `VFileTree.fromFileMap` now infers nested directories from map keys (#376)
- `VFileTree.loadFromDisk` loads a directory into a tree, returning `Result<VFileTree, TreeLoadError>` (#376)
- `TreeWriteError`, `TreeWriteProblem` and `TreeLoadError` union types for file tree I/O failures, with `describe` functions for display (#376)
- `ParseError` union type, which keeps the rule, location and source position of malformed input on the resulting diagnostic
- Strongly typed pipeline: `Parser<'T>`, `Plugin<'T>`, `Compiler<'T>` and `MorphirProcessor<'T>` replace the `obj`-based contracts, and a run returns a `VFileTree`, so one input can produce many output files
- `MorphirProcessor.run` accepts a file or a directory, and `MorphirProcessor.runEachFile` runs once per file in a tree
- `Parser.forFile`, `Parser.ofParseable` and `Plugin.focus` for building typed stages
- `Property<'T>` and `PropertyBag`, a typed replacement for string-keyed data on files, trees, tree configuration and processors, with functions that work with `>`, `<` and `>>`
- `VFileContent` for text, bytes or a re-openable `StreamSource`, so file content does not have to be held in memory. `VFileTree.loadFromDisk` no longer reads files up front, and binary files survive a round trip
- Tree-level diagnostics on `VFileTree`, and `Path` and `Location` on `VMessage`, with `VMessage.describe` for display
- `VFileTree.publish`, `validatePublish`, `readManifest` and `declaredOrder`: path containment, symbolic link rejection, case-insensitive collision detection, staged publication, no rewriting of identical files, and manifest-based removal of obsolete files, moved from the Myriad build task so that every backend gets them
- `Morphir.CodeGeneration.FSharp` now runs as a real pipeline through `Transpiler.generate`, with scope validation, type validation and constant folding. Definite type mismatches now fail the build, and folded literals appear in generated source. `GeneratedModule` and `Diagnostic` are removed in favour of `VFileTree` and `VMessage`
- `Optimizer` and `TypeValidator` now process real packages: the optimizer matches SDK functions by fully qualified name and folds only where the result matches run-time behaviour, and the validator reports only definite mismatches
- **Morphir.SDK F# Runtime Library** (`Morphir.SDK` NuGet package)
- Comprehensive skills troubleshooting guide (`.claude/skills/TROUBLESHOOTING.md`)
- Cross-platform skill invocation documentation in `.agents/skills-reference.md` with platform-specific guidance for Claude Code, GitHub Copilot, Cursor, Windsurf, and JetBrains AI
- Skill alias documentation (documentation-only feature, not functionally supported)
- Session restart requirements documentation for Claude Code skills
- Agent capabilities comparison table showing feature support across platforms
- **CLA compliance guidance** in AGENTS.md with remediation steps for accidental Claude co-author inclusion
- **GitHub issue #270** for F# script to automatically remove Claude co-author from commit history
### Removed
- **BREAKING** (Morphir.Tooling API): `Morphir.Tooling.Configuration.ConfigResolver`, `WorkspaceDiscovery` and `TomlParser` are removed. They walked to the filesystem root and treated any `.git` or `.morphir` directory as a workspace, including the global Morphir home, and could not be contained. No restorable package release ever exposed them. Use `ProjectSystem.Discover` / `ProjectSystem.LoadDetailed` and the shared resolver; replace the legacy CI mode with a named profile selected by `--profiles` or `MORPHIR_PROFILES` ([#494](https://github.com/finos/morphir-dotnet/issues/494))
### Changed
- The `dist`, `tool` and `extension` sub-commands now fail with "not implemented yet", exit code 1 and a link to their tracking issue ([#490](https://github.com/finos/morphir-dotnet/issues/490), [#491](https://github.com/finos/morphir-dotnet/issues/491), [#492](https://github.com/finos/morphir-dotnet/issues/492)). Previously they printed a message and exited 0 without doing anything
- **BREAKING**: The classic `Distribution` has a `Specs` case for IR 3.1.0, and `Distribution.packageDefinition` returns an option because a `Specs` distribution has no definitions
- The classic JSON decoder moved from `Morphir.CodeGeneration.FSharp` to `Morphir.Models`. F# generation keeps its acceptance rules and diagnostics
- Ignore Reqnroll-generated `*.feature.cs` files per Reqnroll best practices
- Configure `ReqnrollUseIntermediateOutputPathForCodeBehind` in test projects to generate code-behind files to obj/ directory
- Refactored AGENTS.md skill invocation section - replaced verbose content (155 lines) with concise summary (29 lines) and link to detailed guide in skills-reference.md
- Enhanced skill template (`.claude/skills/template/SKILL.md`) with optional alias documentation in YAML frontmatter
- **Strengthened CLA warnings** in AGENTS.md and CLAUDE.md - made CRITICAL sections highly visible with remediation steps and reference to automated script (issue #270)