Repository F# setup
open System
open System.IO
open System.Threading
open System.Threading.Tasks
open Axial
open Axial.Layers
open Axial.Console
open Axial.FileSystem
open Axial.Hosting
open Axial.Hosting.Browser
open Axial.Hosting.Node
open Axial.PlatformService
open Axial.State
open Axial.Telemetry
open Axial.Telemetry.JavaScriptGet started
Before you begin
Install the .NET SDK 8.0 or later, then install Axial:
dotnet add package Axial
The smallest Flow
Try it in F# Interactive (dotnet fsi, after #r "nuget: Axial";; and open Axial;;):
Shared setup
// Setup for the checked examples on this page.
open System
open System.IO
open System.Threading
open System.Threading.Tasks
open Axial
open Axial.Layers
open Axial.Console
open Axial.FileSystem
open Axial.Hosting
open Axial.Hosting.Browser
open Axial.Hosting.Node
open Axial.PlatformService
open Axial.State
open Axial.Telemetry
open Axial.Telemetry.JavaScript
/// Fails the docs test when an example's result differs from the value shown.
let shouldEqual expected actual =
if actual <> expected then failwithf "Expected %A but got %A" expected actual
SystemIOThreadingTasksAxialLayersConsoleFileSystemHostingBrowserNodePlatformServiceStateTelemetryJavaScriptshouldEqual: 'a -> 'a -> unitexpected: 'aactual: 'a(<>): 'T -> 'T -> boolStructural inequality The first parameter. The second parameter. The result of the comparison. 5 <> 5 // Evaluates to false 5 <> 6 // Evaluates to true [1; 2] <> [1; 2] // Evaluates to false
failwithf: Printf.StringFormat<'T,'Result> -> 'TPrint to a string buffer and raise an exception with the given result. Helper printers must return strings. The formatter. The formatted result. See Printf.failwithf (link: ) for examples.
> let ready () : Flow<string> = Flow.succeed "The application is ready.";;
> Flow.run () (ready ());;val it: Exit<string,Never> = Success "The application is ready."ready describes work; it has not started. Flow.run () is the edge that starts it. Flow<string> is the short
spelling for a Flow with no capabilities and no expected failure: Flow<unit, Never, string>. The
aliases table lists every short form and what it expands to.
Here, unit means no capabilities and Never means no expected failure. A capability is a value the workflow is
allowed to use: usually a service dependency, but sometimes configuration or request context. Only string carries
information here, so the alias keeps the first signature uncluttered.
You do not need to carry those two empty slots around until the workflow needs them. The next example does.
A useful Flow: quote a price
Suppose the application already has an exchange-rate service. The service is ordinary application code: its live implementation might call an API, use a cache, or dispatch to another service. Axial does not construct it and does not need to know how it works.
The workflow names the one service it needs and turns the service's cancellable Task<Result<_, _>> operation into a
Flow:
type QuoteError =
| RateUnavailable
type IExchangeRates =
abstract UsdToAud: CancellationToken -> Task<Result<decimal, QuoteError>>
type QuoteApp = { ExchangeRates: IExchangeRates }
let quoteAud (usd: decimal) : Flow<QuoteApp, QuoteError, decimal> =
flow {
let! rates = Flow.envWith _.ExchangeRates
let! rate = ColdTask rates.UsdToAud
return Math.Round(usd * rate, 2)
}
FsLiveDocsGeneratedPage4_4556FC416C2B.QuoteErrorRateUnavailableFsLiveDocsGeneratedPage4_4556FC416C2B.IExchangeRatesUsdToAud: IExchangeRates -> CancellationToken -> Task<Result<decimal,QuoteError>>System.Threading.CancellationTokenPropagates notification that operations should be canceled.
System.Threading.Tasks.Task`1Represents an asynchronous operation that can return a value. The type of the result produced by this .
Microsoft.FSharp.Core.FSharpResult`2Helper type for error handling without exceptions. Choices and Results
decimalAn abbreviation for the CLI type . Basic Types
FsLiveDocsGeneratedPage4_4556FC416C2B.QuoteAppExchangeRates: IExchangeRatesquoteAud: decimal -> Flow<QuoteApp,QuoteError,decimal>usd: decimalAxial.Flow`3Represents a cold workflow that reads an environment, returns a typed result, and is executed explicitly through one of its execution members such as ToTask, ToAsync, or RunSynchronously. The type of the environment dependency. The type of the failure value. The type of the success value.
flow: FlowBuilderThe universal flow { } computation expression.
rates: IExchangeRatesAxial.FlowenvWith: ('env -> 'value) -> Flow<'env,'error,'value>Projects one value from the current environment. This is the primary way to access app dependencies, configuration, or request metadata stored in env. The projection runs only when the flow is executed, so constructing the flow is still pure and side-effect free. Prefer small projections over passing a large environment deeper into reusable helpers. A function that extracts a value from the environment. A containing the projected value. let currentTime () = Flow.envWith (fun (environment: BaseRuntime) -> environment.Clock.UtcNow())
_arg1: QuoteApprate: decimalColdTaskUsdToAud: CancellationToken -> Task<Result<decimal,QuoteError>>System.MathProvides constants and static methods for trigonometric, logarithmic, and other common mathematical functions.
Round: decimal * int -> decimalRounds a decimal value to a specified number of fractional digits, and rounds midpoint values to the nearest even number. A decimal number to be rounded. The number of decimal places in the return value. The number nearest to that contains a number of fractional digits equal to . is less than 0 or greater than 28. The result is outside the range of a .
(*): ^T1 -> ^T2 -> ^T3Overloaded multiplication operator The first parameter. The second parameter. The result of the operation. 8 * 6 // Evaluates to 48
The type reads as a contract: quoteAud needs QuoteApp, can fail with QuoteError, and otherwise returns a decimal.
The caller does not pass a cancellation token; ColdTask receives the one owned by the Flow runtime and gives it to
the service.
Where the workflow runs, build the QuoteApp record. Here the service is a small implementation with a fixed rate,
constructed directly:
type FixedRate(rate: Result<decimal, QuoteError>) =
interface IExchangeRates with
member _.UsdToAud _ = Task.FromResult rate
FsLiveDocsGeneratedPage4_4556FC416C2B.FixedRaterate: Result<decimal,QuoteError>Microsoft.FSharp.Core.FSharpResult`2Helper type for error handling without exceptions. Choices and Results
decimalAn abbreviation for the CLI type . Basic Types
FsLiveDocsGeneratedPage4_4556FC416C2B.QuoteErrorFsLiveDocsGeneratedPage4_4556FC416C2B.IExchangeRates_: FixedRateUsdToAud: FixedRate -> CancellationToken -> Task<Result<decimal,QuoteError>>System.Threading.Tasks.TaskRepresents an asynchronous operation.
FromResult: 'TResult -> Task<'TResult>Creates a that's completed successfully with the specified result. The result to store into the completed task. The type of the result returned by the task. The successfully completed task.
quoteAud 80m
|> Flow.run { ExchangeRates = FixedRate(Ok 1.52m) }
|> shouldEqual (Exit.Success 121.60m)
quoteAud 80m
|> Flow.run { ExchangeRates = FixedRate(Error RateUnavailable) }
|> shouldEqual (Exit.Failure(Cause.Fail RateUnavailable))
quoteAud: decimal -> Flow<QuoteApp,QuoteError,decimal>(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
Axial.Flowrun: 'env -> Flow<'env,'error,'value> -> Exit<'value,'error>Runs the workflow and blocks until the final exit is available. The environment used by the workflow. The workflow to run. The final workflow exit. let exit = workflow |> Flow.run environment
ExchangeRates: IExchangeRates``.ctor``: Result<decimal,QuoteError> -> FixedRateOkRepresents an OK or a Successful result. The code succeeded with a value of 'T.
shouldEqual: 'a -> 'a -> unitAxial.Exit`2Represents the final outcome of a workflow execution. The type of the success value. The type of the domain-specific failure value.
SuccessThe workflow completed successfully.
ErrorRepresents an Error or a Failure. The code failed with a value of 'TError representing what went wrong.
RateUnavailableFailureThe workflow failed due to a specific cause.
Axial.Cause`1Represents the cause of a failed workflow. The type of the domain-specific failure value.
FailAn expected domain-specific failure.
If the application already registers its services with a dependency-injection container (an IServiceProvider),
fill the record from it instead:
let quoteApp (services: IServiceProvider) : QuoteApp =
{ ExchangeRates = services.GetService typeof<IExchangeRates> :?> IExchangeRates }
quoteApp: IServiceProvider -> QuoteAppservices: IServiceProviderSystem.IServiceProviderDefines a mechanism for retrieving a service object; that is, an object that provides custom support to other objects.
FsLiveDocsGeneratedPage4_4556FC416C2B.QuoteAppExchangeRates: IExchangeRatesGetService: Type -> objGets the service object of the specified type. An object that specifies the type of service object to get. A service object of type . -or- if there is no service object of type .
typeof: TypeGenerate a System.Type runtime representation of a static type. let t = typeof<int> // Gets the System.Type t.FullName // Evaluates to "System.Int32"
FsLiveDocsGeneratedPage4_4556FC416C2B.IExchangeRatesWith Microsoft.Extensions.DependencyInjection opened, services.GetRequiredService<IExchangeRates>() does the same
and reports a missing registration clearly.
The host's container supplies the IServiceProvider. For this example, a one-service provider stands in for it:
let services =
{ new IServiceProvider with
member _.GetService serviceType =
if serviceType = typeof<IExchangeRates> then box (FixedRate(Ok 1.52m)) else null }
quoteAud 80m |> Flow.run (quoteApp services) |> shouldEqual (Exit.Success 121.60m)
services: IServiceProviderSystem.IServiceProviderDefines a mechanism for retrieving a service object; that is, an object that provides custom support to other objects.
_: IServiceProviderGetService: Type -> objGets the service object of the specified type. An object that specifies the type of service object to get. A service object of type . -or- if there is no service object of type .
serviceType: Type(=): 'T -> 'T -> boolStructural equality The first parameter. The second parameter. The result of the comparison. 5 = 5 // Evaluates to true 5 = 6 // Evaluates to false [1; 2] = [1; 2] // Evaluates to true (1, 5) = (1, 6) // Evaluates to false
typeof: TypeGenerate a System.Type runtime representation of a static type. let t = typeof<int> // Gets the System.Type t.FullName // Evaluates to "System.Int32"
FsLiveDocsGeneratedPage4_4556FC416C2B.IExchangeRatesbox: 'T -> objnullBoxes a strongly typed value. The value to box. The boxed object. let x: int = 123 let obj1 = box x // obj1 is a generic object type unbox<int> obj1 // Evaluates to 123 (int) unbox<double> obj1 // Throws System.InvalidCastException
``.ctor``: Result<decimal,QuoteError> -> FixedRateOkRepresents an OK or a Successful result. The code succeeded with a value of 'T.
quoteAud: decimal -> Flow<QuoteApp,QuoteError,decimal>(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
Axial.Flowrun: 'env -> Flow<'env,'error,'value> -> Exit<'value,'error>Runs the workflow and blocks until the final exit is available. The environment used by the workflow. The workflow to run. The final workflow exit. let exit = workflow |> Flow.run environment
quoteApp: IServiceProvider -> QuoteAppshouldEqual: 'a -> 'a -> unitAxial.Exit`2Represents the final outcome of a workflow execution. The type of the success value. The type of the domain-specific failure value.
SuccessThe workflow completed successfully.
Either way, each area of the application gets a record holding only the services it uses. quoteAud sees QuoteApp
and nothing else, even when the host registers dozens of services, so the record marks the edge of that area. A test
builds the same record with its own IExchangeRates, as FixedRate does here, and the workflow code does not change.
What's next
- Why Flow? explains when this model earns its cost and when
ResultorTaskis still the right answer. - Installation and packages covers the package map.
- Add Axial to an existing Task application shows the one-module adoption path.
- Your first application runs a Flow as an application root.
- Creating and running flows covers every way to create and run a flow.
- Expected errors and defects explains the error channel and defects.
- Dependencies, services, and layers scales the environment record up.

