Morphir.SDK.FSharp
0.5.0-alpha.3
Prefix Reserved
dotnet add package Morphir.SDK.FSharp --version 0.5.0-alpha.3
NuGet\Install-Package Morphir.SDK.FSharp -Version 0.5.0-alpha.3
<PackageReference Include="Morphir.SDK.FSharp" Version="0.5.0-alpha.3" />
<PackageVersion Include="Morphir.SDK.FSharp" Version="0.5.0-alpha.3" />
<PackageReference Include="Morphir.SDK.FSharp" />
paket add Morphir.SDK.FSharp --version 0.5.0-alpha.3
#r "nuget: Morphir.SDK.FSharp, 0.5.0-alpha.3"
#:package Morphir.SDK.FSharp@0.5.0-alpha.3
#addin nuget:?package=Morphir.SDK.FSharp&version=0.5.0-alpha.3&prerelease
#tool nuget:?package=Morphir.SDK.FSharp&version=0.5.0-alpha.3&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. net11.0 is compatible. |
-
net10.0
- FSharp.Core (>= 10.1.403)
-
net11.0
- FSharp.Core (>= 10.1.403)
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 |
### Added
- `morphir extension install nuget:<package-id>[@<version>] --home <abs-home> --no-probe` installs an extension from a NuGet feed: nuget.org, a private V3 feed such as Azure Artifacts, or a folder feed. It reads the sources, package source mapping, credentials (`packageSourceCredentials` and credential provider plugins) and signature policy of `nuget.config`, as `dotnet tool install` does, through the Microsoft NuGet client packages. `--source` names one source (a configured name, a V3 `index.json` URL or a folder), `--configfile` names the `nuget.config`, and `--interactive` lets a credential provider prompt; the three options apply only to a `nuget:` source. No version selects the highest stable listed version. The main package carries `morphir/release.json`; a process artifact is the subtree under its `root` of the main package (one package) or of a package whose id extends the main id (one package per platform, such as the RID packages of a self-contained .NET tool), pinned by its tree digest. A framework-dependent .NET tool is refused. Signatures are checked as the dotnet CLI checks them on each operating system. The command writes only to the Home: no NuGet HTTP cache, global packages folder or NuGet temporary folder. New codes: `extension.nuget_not_found`, `extension.nuget_unreachable`, `extension.nuget_unauthorized`, `extension.nuget_signature_invalid`. See [Publish an extension as a NuGet package](docs/content/docs/guides/publish-extension-nuget.md).
- `morphir extension install` installs WASM extensions from every provider: a local declaration, a GitHub release asset, or the entry `morphir/<filename>` of a NuGet main package. The module is checked against its descriptor `sha256` (`extension.digest_mismatch`) and stored in the single-file store; the lock records `runtime` `wasm` and the claims stay unchecked. The guest does not run during installation. `morphir compile` and `morphir generate` with `--extension-id` run an installed WASM extension through the host's WASM runtime, with the loader that configured modules use: they rehash the stored module against the lock first, and check the session against the installed claims. A host without a WASM runtime fails with `extension.runtime_unavailable`.
- On Linux and macOS, NuGet signature checks use the code signing and timestamping trust lists of the .NET SDK, which `Morphir.Extensions.Installation` carries, as the dotnet CLI does. Without them, NuGet on Linux has no trusted root, and a `require` policy refused every nuget.org package.
- `NuGetPackageProvider` in `Morphir.Extensions.Installation`, and `ExtensionSourceRequest.WithNuGetOptions`. `ExtensionSources.ProviderFor(request, http, home)` takes the Home for a NuGet source. The package ships `buildTransitive/Morphir.Extensions.Installation.props`, which sets the `NuGet.UseSystemTextJsonDeserialization` feature switch in each application that references it.
- `morphir extension install github:<owner>/<repo>[@<version-or-tag>] --home <abs-home> --no-probe` installs an extension package from a GitHub release ([#493](https://github.com/finos/morphir-dotnet/issues/493)). The release carries the extensions-family `release.json` as an asset; the command picks the artifact for this platform, streams the asset into a private stage and checks its SHA-256 against the descriptor before the installer reads it. `@<version>` selects the tag `v<version>`, else `<version>`; no `@` selects the latest release. It uses the anonymous GitHub API, or `GITHUB_TOKEN` or `GH_TOKEN` when set, and reports a rate limit as `extension.github_rate_limited`. `--local-developer` does not apply to a named release; `--no-probe` is still required. The installed extension then runs with `--extension-id`, with no network.
- Source providers in `Morphir.Extensions.Installation`: `IExtensionSourceProvider` (`Scheme`, `ResolveAsync`, `FetchAsync`), with `GitHubReleaseProvider` and `LocalRepositoryProvider`. A provider only resolves and fetches; validation, the package tree, and the catalog, lock and journal writes stay in one installer. A new source (a plain HTTPS URL, OCI, a registry index) is one more provider.
### Changed
- The extension installer writes and reads the extension family `0.1.0-preview.3` ([finos/morphir#1176](https://github.com/finos/morphir/pull/1176), merged as `65bbbc96`): the package format `nupkg` (a NuGet package, pinned by its tree digest only), the provenance `nuget-package` and the source `nuget-package-entry`. Catalogs, locks, journals, repositories and release descriptors of `0.1.0-preview.2` are refused and not converted: install again into a new Home, and publish `release.json` with the preview.3 `$schema`. A release descriptor may now offer `process` and `wasm` artifacts; the installer selects the `process` artifact for the platform, else a `wasm` artifact, and the lock records the selection.
- Build: the Native AOT publish of `morphir` fails on any trim or AOT warning from `NuGet.*` or `Newtonsoft.Json`. `morphir` and `dotnet-morphir` set `NuGet.UseSystemTextJsonDeserialization`, with which NuGet.Protocol has no such warning.
- The extension installer writes and reads the extension family `0.1.0-preview.2` ([finos/morphir#1173](https://github.com/finos/morphir/pull/1173)), where an artifact `source` and an `index` provenance are provider-tagged unions (`local-file` or `github-release-asset`, `local-directory` or `github-release`). Catalogs, locks, journals, repositories and release descriptors of `0.1.0-preview.1` are refused and not converted: install again into a new Home, and publish `release.json` with the preview.2 `$schema`. The package tree manifest format is unchanged.
- The install scripts `scripts/install-morphirup.sh` and `scripts/install-morphirup.ps1` install `morphirup` from the [finos/morphir releases](https://github.com/finos/morphir/releases): they download `morphirup` and `morphirup-v<version>.sha256` from `https://github.com/finos/morphir/releases/download/v<version>/`; the scripts stay hosted here. `morphirup` is on finos/morphir from `0.5.0-alpha.2` on; earlier `morphirup` releases stay on the [finos/morphir-dotnet releases](https://github.com/finos/morphir-dotnet/releases). With no version, the scripts take the newest finos/morphir release that has `morphirup` for the machine and skip the others, such as `v0.5.0-alpha.1` (the CLI only) and the releases of the earlier Rust CLI (`v0.4.0-beta.*` and older), in the release list (up to 100 releases from the GitHub API) and in the release feed `https://github.com/finos/morphir/releases.atom`. A given version whose release has no `morphirup` for the machine is refused with "finos/morphir release v<version> has no morphirup-<rid>; morphirup releases before 0.5.0-alpha.2 are on finos/morphir-dotnet": the `sh` script reads the HTTP status of the download instead of letting `curl -f` report a 404 as a network error, and the PowerShell script reads the status of the failed request. The line `downloading <file> <version> from <url>` names the download, and `MORPHIRUP_DOWNLOAD_URL` mirrors `https://github.com/finos/morphir/releases/download`. Two testing hooks, `MORPHIRUP_RELEASES_FILE` and `MORPHIRUP_FEED_FILE`, read the release list and the release feed from local files; the end-to-end scenarios of `InstallScripts.feature` use them with a local release mirror to check, with no network, that the script of the machine skips the releases without `morphirup` in both and refuses a Rust release given by version.
- mise installs `morphirup` with `mise use -g 'github:finos/morphir[matching=morphirup,version_prefix=v,prerelease=true]'`. mise also lists the releases without `morphirup`; with one of those it stops with "No matching asset found" and installs nothing, so name a release that has `morphirup`, such as `@0.5.0-alpha.2`.
- The proto plugin downloads the `morphir` CLI from `https://github.com/finos/morphir/releases/download/v<version>/`. The plugin itself is still released here.
- The manual workflows `install-scripts.yml` and `morphirup-release-journey.yml` use the finos/morphir releases. The pinned version of `install-scripts.yml` is `0.5.0-alpha.2`, each of its runs checks that the download comes from finos/morphir, and a run of the Rust release `0.4.0-beta.12` must be refused by name.