Logo Orsak

Basic usage

This is a library intended to make effectful code easier to manage. It allows the user to easily create and consume side-effects modelled as interfaces, by making use of computation expressions.

Imagine having the interface

type IButtonPusher =
    abstract member PushButton: unit -> Task<unit>

describing some business-critical capability. To integrate that with the effect framework, we create the following helpers:

type IButtonPusherProvider =
    abstract member ButtonPusher: IButtonPusher

open Orsak

module ButtonPusher =
    let pushButton () =
        Effect.Create(fun (provider: #IButtonPusherProvider) -> provider.ButtonPusher.PushButton())

Then, we can write code like

let work () : Effect<#IButtonPusherProvider, unit, _> = eff {
    //code
    do! ButtonPusher.pushButton ()
//more code
}

Which can be run with some type which implements IButtonPusherProvider.

The big win with effects is that they compose. By using a flexible constraint in the effect creation function, we can simply add effects, which will constrain the effect runner type to implement both IButtonPusherProvider and ILogProvider

type ILogProvider =
    abstract member Logger: ILogger

let logInformation (message: string) =
    Effect.Create(fun (provider: #ILogProvider) -> provider.Logger.LogInformation(message))

let workThenLog () = eff {
    do! work ()
    do! logInformation "Did the work."
}

Then, in the composition root of the program, one could define:

type EffectRunner(buttonPusher: IButtonPusher) =
    interface ILogProvider with
        member _.Logger = NullLogger.Instance

    interface IButtonPusherProvider with
        member _.ButtonPusher = buttonPusher

And then run the effects:

let runner =
    EffectRunner(
        { new IButtonPusher with
            member this.PushButton() = Task.FromResult()
        }
    )

let result: ValueTask<Result<unit, string>> = workThenLog () |> Effect.run runner

Running an effect returns a ValueTask of a Result, whose error type is left open by work and chosen here. Nothing happens until then, so the same effect can be run again, with the same runner or with another one, such as one with fakes in a test.

namespace System
namespace System.Threading
namespace System.Threading.Tasks
namespace Microsoft
namespace Microsoft.Extensions
namespace Microsoft.Extensions.Logging
namespace Microsoft.Extensions.Logging.Abstractions
type IButtonPusher = abstract PushButton: unit -> Task<unit>
type unit = Unit
Multiple items
type Task = interface IAsyncResult interface IDisposable new: action: Action -> unit + 7 overloads member ConfigureAwait: continueOnCapturedContext: bool -> ConfiguredTaskAwaitable + 1 overload member ContinueWith: continuationAction: Action<Task,obj> * state: obj -> Task + 19 overloads member Dispose: unit -> unit member GetAwaiter: unit -> TaskAwaiter member RunSynchronously: unit -> unit + 1 overload member Start: unit -> unit + 1 overload member Wait: unit -> unit + 5 overloads ...
<summary>Represents an asynchronous operation.</summary>

--------------------
type Task<'TResult> = inherit Task new: ``function`` : Func<obj,'TResult> * state: obj -> unit + 7 overloads member ConfigureAwait: continueOnCapturedContext: bool -> ConfiguredTaskAwaitable<'TResult> + 1 overload member ContinueWith: continuationAction: Action<Task<'TResult>,obj> * state: obj -> Task + 19 overloads member GetAwaiter: unit -> TaskAwaiter<'TResult> member WaitAsync: cancellationToken: CancellationToken -> Task<'TResult> + 4 overloads member Result: 'TResult static member Factory: TaskFactory<'TResult>
<summary>Represents an asynchronous operation that can return a value.</summary>
<typeparam name="TResult">The type of the result produced by this <see cref="T:System.Threading.Tasks.Task`1" />.</typeparam>


--------------------
Task(action: System.Action) : Task
Task(action: System.Action, cancellationToken: System.Threading.CancellationToken) : Task
Task(action: System.Action, creationOptions: TaskCreationOptions) : Task
Task(action: System.Action<obj>, state: obj) : Task
Task(action: System.Action, cancellationToken: System.Threading.CancellationToken, creationOptions: TaskCreationOptions) : Task
Task(action: System.Action<obj>, state: obj, cancellationToken: System.Threading.CancellationToken) : Task
Task(action: System.Action<obj>, state: obj, creationOptions: TaskCreationOptions) : Task
Task(action: System.Action<obj>, state: obj, cancellationToken: System.Threading.CancellationToken, creationOptions: TaskCreationOptions) : Task

--------------------
Task(``function`` : System.Func<'TResult>) : Task<'TResult>
Task(``function`` : System.Func<obj,'TResult>, state: obj) : Task<'TResult>
Task(``function`` : System.Func<'TResult>, cancellationToken: System.Threading.CancellationToken) : Task<'TResult>
Task(``function`` : System.Func<'TResult>, creationOptions: TaskCreationOptions) : Task<'TResult>
Task(``function`` : System.Func<obj,'TResult>, state: obj, cancellationToken: System.Threading.CancellationToken) : Task<'TResult>
Task(``function`` : System.Func<obj,'TResult>, state: obj, creationOptions: TaskCreationOptions) : Task<'TResult>
Task(``function`` : System.Func<'TResult>, cancellationToken: System.Threading.CancellationToken, creationOptions: TaskCreationOptions) : Task<'TResult>
Task(``function`` : System.Func<obj,'TResult>, state: obj, cancellationToken: System.Threading.CancellationToken, creationOptions: TaskCreationOptions) : Task<'TResult>
type IButtonPusherProvider = abstract ButtonPusher: IButtonPusher
namespace Orsak
val pushButton: unit -> Effect<#IButtonPusherProvider,unit,'b>
Multiple items
union case Effect.Effect: EffectDelegate<'r,'a,'e> -> Effect<'r,'a,'e>

--------------------
module Effect from Orsak
<summary> Functions for running, combining, recovering and repeating effects. </summary>

--------------------
type Effect = static member Create: f: ('a -> Task<Result<'b,'e>>) -> Effect<'a,'b,'e> + 2 overloads static member Error: error: 'e -> Effect<'a,'b,'e>
<summary> Creates effects. <c>Effect.Create</c> turns a function of an environment into an effect, and is how effects are made from the interfaces that describe side effects. The function may return a plain value, a <c>Result</c>, a <c>Task</c>, a <c>ValueTask</c> or an <c>Async</c>, of a value or of a <c>Result</c>; returning a <c>Result</c> lets the effect fail. </summary>
<example> The usual pattern: an interface describing the side effect, a provider interface exposing it, and a function creating the effect, which requires any environment that implements the provider (<c>#IConsoleProvider</c>). <code lang="fsharp"> type IConsole = abstract ReadLine: unit -&gt; string abstract WriteLine: string -&gt; unit type IConsoleProvider = abstract Console: IConsole module Console = let readLine () = Effect.Create(fun (p: #IConsoleProvider) -&gt; p.Console.ReadLine()) let writeLine line = Effect.Create(fun (p: #IConsoleProvider) -&gt; p.Console.WriteLine line) </code> The Orsak.Myriad generators can write the functions of such a module from the interface. </example>


--------------------
type Effect<'r,'a,'e> = | Effect of EffectDelegate<'r,'a,'e> member Run: r: 'r -> AsyncResult<'a,'e> member RunOrFail: r: 'r -> Task<'a> static member (<*>)<'b,'a (requires member (+))> : f: Effect<'r,('b -> 'a),'a0> * e: Effect<'r,'b,'a0> -> Effect<'r,'a,'a0> (requires member (+)) static member (>>=) : h: Effect<'r,'a,'e> * f: ('a -> Effect<'r,'b,'e>) -> Effect<'r,'b,'e> static member Map: h: Effect<'r,'a,'e> * f: ('a -> 'b) -> Effect<'r,'b,'e> static member Return<'a,'b,'c> : a: 'a0 -> Effect<'b,'a0,'c> static member ap<'r,'a,'b,'e> : applicative: Effect<'r,('b -> 'a),'e> -> e: Effect<'r,'b,'e> -> Effect<'r,'a,'e>
<summary> Describes an effect that can either succeed with <typeparamref name="'a" />, or fail with <typeparamref name="'e" />. The effect is 'cold', and only starts when run with an <typeparamref name="'r" />. </summary>
<remarks> Effects are usually written with the <c>eff</c> computation expression, and created from interfaces that describe side effects with <c>Effect.Create</c>. The environment <typeparamref name="'r" /> is inferred from what the effect uses, and supplied when the effect is run. </remarks>
<example><code lang="fsharp"> let greet () = eff { let! name = Console.readLine () do! Console.writeLine $"Hello {name}" } // at the composition root, with an environment providing the console effects: let! result = greet () |&gt; Effect.run env </code></example>
<typeparam name="'r"> The environment required to run the effect </typeparam>
<typeparam name="'a"> The resulting type when the effect runs successfully </typeparam>
<typeparam name="'e"> The resulting type when the effect fails</typeparam>
static member Effect.Create: f: ('a -> 'b) -> Effect<'a,'b,'e>
static member Effect.Create: f: ('a -> Async<'b>) -> Effect<'a,'b,'a0>
static member Effect.Create: f: ('a -> Result<'b,'e>) -> Effect<'a,'b,'e>
static member Effect.Create: f: ('a -> Task<'b>) -> Effect<'a,'b,'a0>
static member Effect.Create: f: ('a -> ValueTask<'b>) -> Effect<'a,'b,'a0>
static member Effect.Create: f: ('a -> Async<Result<'b,'e>>) -> Effect<'a,'b,'e>
static member Effect.Create: f: ('a -> ValueTask<Result<'b,'e>>) -> Effect<'a,'b,'e>
static member Effect.Create: f: ('a -> Task<Result<'b,'e>>) -> Effect<'a,'b,'e>
val provider: #IButtonPusherProvider
property IButtonPusherProvider.ButtonPusher: IButtonPusher with get
abstract IButtonPusher.PushButton: unit -> Task<unit>
val work: unit -> Effect<#IButtonPusherProvider,unit,'b>
val eff: EffBuilder
<summary> The computation expression for writing effects. Binding (<c>let!</c>, <c>do!</c>, <c>return!</c>) works with other effects, and with <c>Task</c>, <c>ValueTask</c>, <c>Async</c> and <c>Result</c>, so .NET APIs can be used directly. It supports <c>for</c>, <c>while</c>, <c>use</c>, <c>try/with</c> and <c>try/finally</c>, and <c>and!</c> runs effects concurrently. A failed effect stops the rest of the expression, with its error. </summary>
<example><code lang="fsharp"> let placeOrder (order: Order) = eff { let! id = GuidGenerator.genGuid () let! now = Time.utcNow () do! Orders.save { order with Id = id; Placed = now } let! customer = Customers.load order.CustomerId and! stock = Stock.reserve order.Lines return id } </code></example>
module ButtonPusher from 1_Motivation
type ILogProvider = abstract Logger: ILogger
Multiple items
type Logger<'T> = interface ILogger<'T> interface ILogger new: factory: ILoggerFactory -> unit
<summary> Delegates to a new <see cref="T:Microsoft.Extensions.Logging.ILogger" /> instance using the full name of the given type, created by the provided <see cref="T:Microsoft.Extensions.Logging.ILoggerFactory" />. </summary>
<typeparam name="T">The type.</typeparam>


--------------------
Logger(factory: ILoggerFactory) : Logger<'T>
Multiple items
type ILogger = override BeginScope<'TState> : state: 'TState -> IDisposable override IsEnabled: logLevel: LogLevel -> bool override Log<'TState> : logLevel: LogLevel * eventId: EventId * state: 'TState * ``exception`` : exn * formatter: Func<'TState,exn,string> -> unit
<summary> Represents a type used to perform logging. </summary>
<remarks>Aggregates most logging patterns to a single method.</remarks>


--------------------
type ILogger<'TCategoryName> = inherit ILogger
<summary> A generic interface for logging where the category name is derived from the specified <typeparamref name="TCategoryName" /> type name. Generally used to enable activation of a named <see cref="T:Microsoft.Extensions.Logging.ILogger" /> from dependency injection. </summary>
<typeparam name="TCategoryName">The type whose name is used for the logger category name.</typeparam>
val logInformation: message: string -> Effect<#ILogProvider,unit,'b>
val message: string
Multiple items
val string: value: 'T -> string

--------------------
type string = System.String
val provider: #ILogProvider
property ILogProvider.Logger: ILogger with get
(extension) ILogger.LogInformation(message: string, args: obj array) : unit
(extension) ILogger.LogInformation(eventId: EventId, message: string, args: obj array) : unit
(extension) ILogger.LogInformation(``exception`` : exn, message: string, args: obj array) : unit
(extension) ILogger.LogInformation(eventId: EventId, ``exception`` : exn, message: string, args: obj array) : unit
val workThenLog: unit -> Effect<'a,unit,'b> (requires 'a :> ILogProvider and 'a :> IButtonPusherProvider)
Multiple items
type EffectRunner = interface IButtonPusherProvider interface ILogProvider new: buttonPusher: IButtonPusher -> EffectRunner

--------------------
new: buttonPusher: IButtonPusher -> EffectRunner
val buttonPusher: IButtonPusher
Multiple items
type NullLogger = interface ILogger member BeginScope<'TState> : state: 'TState -> IDisposable member IsEnabled: logLevel: LogLevel -> bool member Log<'TState> : logLevel: LogLevel * eventId: EventId * state: 'TState * ``exception`` : exn * formatter: Func<'TState,exn,string> -> unit static member Instance: NullLogger
<summary> Minimalistic logger that does nothing. </summary>

--------------------
type NullLogger<'T> = interface ILogger<'T> interface ILogger new: unit -> unit member BeginScope<'TState> : state: 'TState -> IDisposable member IsEnabled: logLevel: LogLevel -> bool member Log<'TState> : logLevel: LogLevel * eventId: EventId * state: 'TState * ``exception`` : exn * formatter: Func<'TState,exn,string> -> unit static val Instance: NullLogger<'T>
<summary> Minimalistic logger that does nothing. </summary>

--------------------
NullLogger() : NullLogger<'T>
Multiple items
field NullLogger<'T>.Instance: NullLogger<'T>
<summary> Returns an instance of <see cref="T:Microsoft.Extensions.Logging.Abstractions.NullLogger`1" />. </summary>
<returns>An instance of <see cref="T:Microsoft.Extensions.Logging.Abstractions.NullLogger`1" />.</returns>


--------------------
property NullLogger.Instance: NullLogger with get
<summary> Returns the shared instance of <see cref="T:Microsoft.Extensions.Logging.Abstractions.NullLogger" />. </summary>
val runner: EffectRunner
val this: IButtonPusher
Task.FromResult<'TResult>(result: 'TResult) : Task<'TResult>
val result: ValueTask<Result<unit,string>>
Multiple items
type ValueTask = new: source: IValueTaskSource * token: int16 -> unit + 1 overload member AsTask: unit -> Task member ConfigureAwait: continueOnCapturedContext: bool -> ConfiguredValueTaskAwaitable member Equals: obj: obj -> bool + 1 overload member GetAwaiter: unit -> ValueTaskAwaiter member GetHashCode: unit -> int member Preserve: unit -> ValueTask static member (<>) : left: ValueTask * right: ValueTask -> bool static member (=) : left: ValueTask * right: ValueTask -> bool static member FromCanceled: cancellationToken: CancellationToken -> ValueTask + 1 overload ...
<summary>Provides an awaitable result of an asynchronous operation.</summary>

--------------------
type ValueTask<'TResult> = new: source: IValueTaskSource<'TResult> * token: int16 -> unit + 2 overloads member AsTask: unit -> Task<'TResult> member ConfigureAwait: continueOnCapturedContext: bool -> ConfiguredValueTaskAwaitable<'TResult> member Equals: obj: obj -> bool + 1 overload member GetAwaiter: unit -> ValueTaskAwaiter<'TResult> member GetHashCode: unit -> int member Preserve: unit -> ValueTask<'TResult> member ToString: unit -> string static member (<>) : left: ValueTask<'TResult> * right: ValueTask<'TResult> -> bool static member (=) : left: ValueTask<'TResult> * right: ValueTask<'TResult> -> bool ...
<summary>Provides a value type that wraps a <see cref="T:System.Threading.Tasks.Task`1" /> and a <typeparamref name="TResult" />, only one of which is used.</summary>
<typeparam name="TResult">The result.</typeparam>


--------------------
ValueTask ()
ValueTask(task: Task) : ValueTask
ValueTask(source: Sources.IValueTaskSource, token: int16) : ValueTask

--------------------
ValueTask ()
ValueTask(task: Task<'TResult>) : ValueTask<'TResult>
ValueTask(result: 'TResult) : ValueTask<'TResult>
ValueTask(source: Sources.IValueTaskSource<'TResult>, token: int16) : ValueTask<'TResult>
Multiple items
module Result from Microsoft.FSharp.Core

--------------------
type Result<'T,'TError> = | Ok of ResultValue: 'T | Error of ErrorValue: 'TError
val run: env: 'r -> e: Effect<'r,'a,'e> -> AsyncResult<'a,'e>
<summary> Starts the effect. </summary>
<typeparam name="'r"> The environment required to run the effect </typeparam>
<typeparam name="'a"> The resulting type when the effect runs successfully </typeparam>
<typeparam name="'e"> The resulting type when the effect fails</typeparam>
<param name="env">The environment needed to start the effect</param>
<param name="e">The effect to run</param>
<example><code lang="fsharp"> task { match! placeOrder order |&gt; Effect.run env with | Ok id -&gt; printfn $"Placed {id}" | Error err -&gt; printfn $"Failed: {err}" } </code></example>

Type something to start searching.