Logo Orsak

Scopes

Some dependencies only exist for a while: a database transaction, a unit of work, a lock, a progress reporter. They are begun, a few effects run with them, and they are disposed. In Orsak such a dependency is a scope, and the effects running in it see a bigger environment than the effects outside: the outer environment grows into an inner one, which also provides the scope.

The effects in a scope are ordinary effects. One that needs the transaction says so with a provider interface, as for any other dependency, and the compiler then checks that it only runs where there is a transaction.

This page shows how to define a scope, how an environment grows into it, how to run effects in it, and what happens when they fail. It is a script: it runs with dotnet fsi docs/scopes.fsx once Orsak is built, and the output shown is from such a run.

A scope

The examples move money between accounts. The bank stands in for a database, and a transaction for a database transaction: its changes reach the bank when it is completed, and are forgotten otherwise.

A scope is anything disposable asynchronously, which is what Scoped stands for. A CompletableScope can also be completed, which is how a transaction is committed:

open System
open System.Collections.Generic
open System.Threading.Tasks
open Orsak
open Orsak.Scoped

type Bank() =
    let balances = Dictionary<string, decimal>(dict [ "ada", 100m; "grace", 20m ])
    member _.Balances = balances

type Transaction(bank: Bank) =
    let pending = Dictionary<string, decimal>()

    member _.Balance(account: string) =
        match pending.TryGetValue account with
        | true, balance -> balance
        | false, _ -> bank.Balances[account]

    member _.SetBalance(account: string, balance: decimal) = pending[account] <- balance

    interface CompletableScope with
        member _.Complete() =
            for KeyValue(account, balance) in pending do
                bank.Balances[account] <- balance

            printfn "transaction committed"
            ValueTask()

        member _.DisposeAsync() =
            printfn "transaction disposed"
            ValueTask()

Effects that need the scope

The effects reach the transaction through a provider interface, like any other dependency:

type ITransactionProvider =
    abstract member Transaction: Transaction

type TransferError = InsufficientFunds of account: string

module Accounts =
    let balance account =
        Effect.Create(fun (p: #ITransactionProvider) -> p.Transaction.Balance account)

    let private setBalance account balance =
        Effect.Create(fun (p: #ITransactionProvider) -> p.Transaction.SetBalance(account, balance))

    let withdraw account amount = eff {
        let! current = balance account

        if current < amount then
            return! Error(InsufficientFunds account)
        else
            return! setBalance account (current - amount)
    }

    let deposit account amount = eff {
        let! current = balance account
        return! setBalance account (current + amount)
    }

Effects that need the scope mix freely with effects that don't. The transfers are also written to an audit log, which has nothing to do with the transaction:

type IAudit =
    abstract member Record: message: string -> unit

type IAuditProvider =
    abstract member Audit: IAudit

module Audit =
    let record message =
        Effect.Create(fun (p: #IAuditProvider) -> p.Audit.Record message)

let transfer source target (amount: decimal) = eff {
    do! Accounts.withdraw source amount
    do! Accounts.deposit target amount
    do! Audit.record $"moved {amount} from {source} to {target}"
}

transfer is an effect of any environment with both providers:

val transfer:
  source: string -> target: string -> amount: decimal -> Effect<'r, unit, TransferError>
    when 'r :> ITransactionProvider and 'r :> IAuditProvider

An environment that grows

The environment of the application has no transaction, so it doesn't implement ITransactionProvider. Instead it implements IGrowsInto, which says how to begin one, and which environment it grows into once there is one. With one kind of scope, the inner environment can inherit the outer one, and add the provider of the scope:

type AppEnv(bank: Bank) =
    interface IAuditProvider with
        member _.Audit =
            { new IAudit with
                member _.Record message = printfn $"audit: %s{message}"
            }

    interface IGrowsInto<Transaction, TransactionEnv> with
        member _.BeginScope() =
            printfn "transaction begun"
            ValueTask<Transaction>(Transaction bank)

        member _.Grow transaction = TransactionEnv(bank, transaction)

and TransactionEnv(bank: Bank, transaction: Transaction) =
    inherit AppEnv(bank)

    interface ITransactionProvider with
        member _.Transaction = transaction

BeginScope is a member of ScopeProvider<'Scope>, which IGrowsInto inherits. An environment that only begins scopes, without growing, implements that alone, also under the name CompletableScopeProvider.

Running effects in a scope

CompletingScope<'Scope>.Within takes an effect of the inner environment and gives an effect of the outer one. When it runs, it begins the scope, grows the environment, runs the effect, completes the scope if the effect succeeded, and disposes the scope:

let transferInTransaction source target amount =
    transfer source target amount |> CompletingScope<Transaction>.Within

The transaction is gone from the type: what is left is an environment that grows, in a transaction, into one with what transfer needs.

val transferInTransaction:
  source: string -> target: string -> amount: decimal -> Effect<'Outer, unit, TransferError>
    when 'Outer :> IGrowsInto<Transaction, 'Inner>
    and 'Inner :> ITransactionProvider
    and 'Inner :> IAuditProvider

AppEnv is such an environment:

let bank = Bank()
let env = AppEnv bank

let show (effect: Effect<AppEnv, 'a, 'e>) =
    let result = (Effect.run env effect).AsTask().Result
    printfn $"%A{result}"
    printfn $"""ada: {bank.Balances["ada"]}, grace: {bank.Balances["grace"]}"""

transferInTransaction "ada" "grace" 30m |> show
transaction begun
audit: moved 30 from ada to grace
transaction committed
transaction disposed
Ok ()
ada: 70, grace: 50

Running transfer itself with AppEnv doesn't compile, as AppEnv provides no transaction:

transfer "ada" "grace" 30m |> Effect.run env
error FS0001: The type 'AppEnv' is not compatible with the type 'ITransactionProvider'

When the effects fail

When the effect fails, the scope is disposed without being completed. Here the first transfer succeeds and the second fails, so neither reaches the bank:

let twoTransfers =
    eff {
        do! transfer "ada" "grace" 30m
        do! transfer "grace" "ada" 500m
    }
    |> CompletingScope<Transaction>.Within

twoTransfers |> show
transaction begun
audit: moved 30 from ada to grace
transaction disposed
Error (InsufficientFunds "grace")
ada: 70, grace: 50

The same goes for an exception. It isn't caught or turned into an error: the scope is disposed without being completed, and the exception propagates to whoever runs the effect, as described under Errors.

The audit log shows what a scope doesn't do. Only what goes through the scope is undone, so the first transfer is still in the log.

A builder for a scope

Opening a scope around an expression is common enough to have a computation expression. A builder is created once for the type of scope, and is then used like eff:

let transaction = CompletableScopeCreatingEffectBuilder<Transaction>()

let payBonus account amount = transaction {
    do! Accounts.deposit account amount
    do! Audit.record $"paid a bonus of {amount} to {account}"
    return! Accounts.balance account
}

payBonus "grace" 5m |> show
transaction begun
audit: paid a bonus of 5 to grace
transaction committed
transaction disposed
Ok 55M
ada: 70, grace: 55

Each transaction { } is a scope of its own. Binding two of them in an eff runs two transactions, one after the other, and the second runs even though it can't undo the first:

eff {
    let! grace = payBonus "grace" 5m
    let! ada = payBonus "ada" 5m
    return grace, ada
}
|> show
transaction begun
audit: paid a bonus of 5 to grace
transaction committed
transaction disposed
transaction begun
audit: paid a bonus of 5 to ada
transaction committed
transaction disposed
Ok (60M, 75M)
ada: 75, grace: 60

So the parts that should share a transaction are written with plain eff, as transfer is, and the scope is opened once, around all of them. The type shows which is which: an effect that needs ITransactionProvider joins the transaction it runs in, and one that needs IGrowsInto<Transaction, 'Inner> begins its own.

Scopes that aren't completed

A scope without anything to complete is opened with Scope<'Scope>.Within, or a ScopeCreatingEffectBuilder. It is begun and disposed in the same way, whether the effects succeed or fail.

This scope counts the steps of a job, and reports each as it is done:

type ProgressScope() =
    let mutable steps = 0

    member _.Report(step: string) =
        steps <- steps + 1
        printfn $"step %i{steps}: %s{step}"

    interface Scoped with
        member _.DisposeAsync() =
            printfn $"finished after %i{steps} steps"
            ValueTask()

type IProgressProvider =
    abstract member Progress: ProgressScope

module Progress =
    let report step =
        Effect.Create(fun (p: #IProgressProvider) -> p.Progress.Report step)

let progress = ScopeCreatingEffectBuilder<ProgressScope>()

A function can open a scope around effects it is given. inOrder runs any steps in one progress scope, reporting each, and doesn't need to be inline:

let inOrder (steps: (string * Effect<'r, unit, 'e>) list) = progress {
    for name, step in steps do
        do! step
        do! Progress.report name
}
val inOrder:
  steps: (string * Effect<'r, unit, 'e>) list -> Effect<'Outer, unit, 'e>
    when 'Outer :> IGrowsInto<ProgressScope, 'r>
    and 'r :> IProgressProvider

The steps are effects of the inner environment, so they may use everything the outer environment provides:

type JobEnv() =
    interface IAuditProvider with
        member _.Audit =
            { new IAudit with
                member _.Record message = printfn $"audit: %s{message}"
            }

    interface IGrowsInto<ProgressScope, JobProgressEnv> with
        member _.BeginScope() = ValueTask<ProgressScope>(ProgressScope())
        member _.Grow scope = JobProgressEnv scope

and JobProgressEnv(scope: ProgressScope) =
    inherit JobEnv()

    interface IProgressProvider with
        member _.Progress = scope

let job: Effect<JobEnv, unit, string> =
    inOrder [
        "export", Audit.record "exported the accounts"
        "archive", Audit.record "archived the export"
    ]

let jobResult = (Effect.run (JobEnv()) job).AsTask().Result
printfn $"%A{jobResult}"
audit: exported the accounts
step 1: export
audit: archived the export
step 2: archive
finished after 2 steps
Ok ()

Several scopes

An environment can grow into more than one kind of scope, with an IGrowsInto for each. Scopes can then be nested, such as a job that reports its progress, where each step is a transaction of its own that also reports:

let pay account = transaction {
    do! Accounts.deposit account 1m
    do! Progress.report $"paid {account}"
}

let payAll (accounts: string list) = progress {
    for account in accounts do
        do! pay account
}

The transaction is a function of its own, rather than a transaction { } written inside the progress { }. An expression of a scope's builder written directly inside another computation expression doesn't compile, failing with error FS3401. Where the scope has to be opened in place, eff { ... } |> CompletingScope<Transaction>.Within works.

The inner environment of the progress scope is the outer environment of the transaction, so it is the one that grows into an environment with both. This is where inheriting the outer environment stops working: an inner environment that inherits the outer one also inherits how it grows, and would grow into an environment with a transaction but without the progress. The compiler rejects that, as Progress.report then has no provider.

So with several scopes, the environments inherit what they share from a common base, and each says for itself how it grows. The base begins the scopes, with ScopeProvider, so that is written once:

type BatchBase(bank: Bank) =
    interface IAuditProvider with
        member _.Audit =
            { new IAudit with
                member _.Record message = printfn $"audit: %s{message}"
            }

    interface CompletableScopeProvider<Transaction> with
        member _.BeginScope() =
            printfn "transaction begun"
            ValueTask<Transaction>(Transaction bank)

    interface ScopeProvider<ProgressScope> with
        member _.BeginScope() = ValueTask<ProgressScope>(ProgressScope())

type BatchEnv(bank: Bank) =
    inherit BatchBase(bank)

    interface IGrowsInto<Transaction, BatchTransactionEnv> with
        member _.Grow transaction = BatchTransactionEnv(bank, transaction)

    interface IGrowsInto<ProgressScope, BatchProgressEnv> with
        member _.Grow progress = BatchProgressEnv(bank, progress)

and BatchTransactionEnv(bank: Bank, transaction: Transaction) =
    inherit BatchBase(bank)

    interface ITransactionProvider with
        member _.Transaction = transaction

and BatchProgressEnv(bank: Bank, progress: ProgressScope) =
    inherit BatchBase(bank)

    interface IProgressProvider with
        member _.Progress = progress

    interface IGrowsInto<Transaction, BatchProgressTransactionEnv> with
        member _.Grow transaction = BatchProgressTransactionEnv(bank, progress, transaction)

and BatchProgressTransactionEnv(bank: Bank, progress: ProgressScope, transaction: Transaction) =
    inherit BatchBase(bank)

    interface IProgressProvider with
        member _.Progress = progress

    interface ITransactionProvider with
        member _.Transaction = transaction

let batchEnv = BatchEnv bank

let showBatch (effect: Effect<BatchEnv, 'a, 'e>) =
    let result = (Effect.run batchEnv effect).AsTask().Result
    printfn $"%A{result}"

(payAll [ "ada"; "grace" ]: Effect<BatchEnv, unit, TransferError>) |> showBatch
transaction begun
step 1: paid ada
transaction committed
transaction disposed
transaction begun
step 2: paid grace
transaction committed
transaction disposed
finished after 2 steps
Ok ()

Every combination of scopes that effects run in is an environment of its own, and so is every order of nesting them: here a transaction can be opened in a progress scope, but not the other way around, as BatchTransactionEnv doesn't grow. That is checked when compiling, and it is a reason to keep the kinds of scope few, and the nesting shallow.

Scopes side by side

Opening two kinds of scope one after the other, rather than one in the other, needs the type of the environment to be known. Left to be inferred, this doesn't compile:

let settle () = eff {
    let! _ = payBonus "ada" 1m
    do! inOrder [ "export", Audit.record "exported the accounts"; "archive", Audit.record "archived the export" ]
}
error FS0041: No overloads match for method 'Bind'.

The environment would have to be any type that grows in both a Transaction and a ProgressScope, and F# can't require the same interface of a type twice, with different type arguments. Naming the environment of each of the effects solves it:

let bonus: Effect<BatchEnv, decimal, TransferError> = payBonus "ada" 1m

let export: Effect<BatchEnv, unit, TransferError> =
    inOrder [ "export", Audit.record "exported the accounts"; "archive", Audit.record "archived the export" ]

let settle = eff {
    let! _ = bonus
    do! export
}

settle |> showBatch
transaction begun
audit: paid a bonus of 1 to ada
transaction committed
transaction disposed
audit: exported the accounts
step 1: export
audit: archived the export
step 2: archive
finished after 2 steps
Ok ()

Effects that open one kind of scope, however deeply they nest others in it, stay generic, as payAll is: each environment along the way is a type of its own, and only grows once.

In short

Orsak comes with a scope for ADO.NET transactions, DbTransactional, with its provider ITransactionProvider and builder commitEff. These are experimental, and using them gives warning FS0057 until it is suppressed.

In tests, an environment begins scopes that are fakes, in the way described under Testing: a fake transaction can record whether it was completed.

namespace System
namespace System.Collections
namespace System.Collections.Generic
namespace System.Threading
namespace System.Threading.Tasks
namespace Orsak
namespace Orsak.Scoped
Multiple items
type Bank = new: unit -> Bank member Balances: Dictionary<string,decimal>

--------------------
new: unit -> Bank
val balances: Dictionary<string,decimal>
Multiple items
type Dictionary<'TKey,'TValue> = interface ICollection<KeyValuePair<'TKey,'TValue>> interface seq<KeyValuePair<'TKey,'TValue>> interface IEnumerable interface IDictionary<'TKey,'TValue> interface IReadOnlyCollection<KeyValuePair<'TKey,'TValue>> interface IReadOnlyDictionary<'TKey,'TValue> interface ICollection interface IDictionary interface IDeserializationCallback interface ISerializable ...
<summary>Represents a collection of keys and values.</summary>
<typeparam name="TKey">The type of the keys in the dictionary.</typeparam>
<typeparam name="TValue">The type of the values in the dictionary.</typeparam>


--------------------
Dictionary() : Dictionary<'TKey,'TValue>
Dictionary(dictionary: IDictionary<'TKey,'TValue>) : Dictionary<'TKey,'TValue>
Dictionary(collection: KeyValuePair<'TKey,'TValue> seq) : Dictionary<'TKey,'TValue>
Dictionary(comparer: IEqualityComparer<'TKey>) : Dictionary<'TKey,'TValue>
Dictionary(capacity: int) : Dictionary<'TKey,'TValue>
Dictionary(dictionary: IDictionary<'TKey,'TValue>, comparer: IEqualityComparer<'TKey>) : Dictionary<'TKey,'TValue>
Dictionary(collection: KeyValuePair<'TKey,'TValue> seq, comparer: IEqualityComparer<'TKey>) : Dictionary<'TKey,'TValue>
Dictionary(capacity: int, comparer: IEqualityComparer<'TKey>) : Dictionary<'TKey,'TValue>
Multiple items
val string: value: 'T -> string

--------------------
type string = String
Multiple items
val decimal: value: 'T -> decimal (requires member op_Explicit)

--------------------
type decimal = Decimal

--------------------
type decimal<'Measure> = decimal
val dict: keyValuePairs: ('Key * 'Value) seq -> IDictionary<'Key,'Value> (requires equality)
Multiple items
type Transaction = interface CompletableScope new: bank: Bank -> Transaction member Balance: account: string -> decimal member SetBalance: account: string * balance: decimal -> unit

--------------------
new: bank: Bank -> Transaction
val bank: Bank
val pending: Dictionary<string,decimal>
val account: string
Dictionary.TryGetValue(key: string, value: byref<decimal>) : bool
val balance: decimal
property Bank.Balances: Dictionary<string,decimal> with get
type CompletableScope = inherit Scoped abstract Complete: unit -> ValueTask
<summary> A scope that is completed when the effects running in it succeed, such as a transaction that is committed. A scope disposed without being completed abandons its work. </summary>
active recognizer KeyValue: KeyValuePair<'Key,'Value> -> 'Key * 'Value
val printfn: format: Printf.TextWriterFormat<'T> -> 'T
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>
type ITransactionProvider = abstract Transaction: Transaction
type TransferError = | InsufficientFunds of account: string
val balance: account: string -> Effect<#ITransactionProvider,decimal,'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 p: #ITransactionProvider
property ITransactionProvider.Transaction: Transaction with get
member Transaction.Balance: account: string -> decimal
val private setBalance: account: string -> balance: decimal -> Effect<#ITransactionProvider,unit,'b>
member Transaction.SetBalance: account: string * balance: decimal -> unit
val withdraw: account: string -> amount: decimal -> Effect<#ITransactionProvider,unit,TransferError>
val amount: decimal
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>
val current: decimal
union case Result.Error: ErrorValue: 'TError -> Result<'T,'TError>
union case TransferError.InsufficientFunds: account: string -> TransferError
val deposit: account: string -> amount: decimal -> Effect<#ITransactionProvider,unit,'b>
type IAudit = abstract Record: message: string -> unit
type unit = Unit
type IAuditProvider = abstract Audit: IAudit
val record: message: string -> Effect<#IAuditProvider,unit,'b>
val message: string
val p: #IAuditProvider
property IAuditProvider.Audit: IAudit with get
abstract IAudit.Record: message: string -> unit
val transfer: source: string -> target: string -> amount: decimal -> Effect<'a,unit,TransferError> (requires 'a :> IAuditProvider and 'a :> ITransactionProvider)
val source: string
val target: string
module Accounts from Scopes
module Audit from Scopes
Multiple items
val string: value: 'T -> string

--------------------
type string = System.String
Multiple items
val decimal: value: 'T -> decimal (requires member op_Explicit)

--------------------
type decimal = System.Decimal

--------------------
type decimal<'Measure> = decimal
Multiple items
type AppEnv = interface IGrowsInto<Transaction,TransactionEnv> interface IAuditProvider new: bank: Bank -> AppEnv

--------------------
new: bank: Bank -> AppEnv
type IGrowsInto<'Scope,'Inner (requires 'Scope :> Scoped)> = inherit ScopeProvider<'Scope> abstract Grow: scope: 'Scope -> 'Inner
<summary> Implemented by an environment that can grow into <typeparamref name="'Inner" />, the environment of effects running in a scope of type <typeparamref name="'Scope" />. Such an environment also begins the scope, as a <see cref="ScopeProvider{Scope}" />. The inner environment usually inherits the outer one, adding a provider of the scope. </summary>
Multiple items
type TransactionEnv = inherit AppEnv interface ITransactionProvider new: bank: Bank * transaction: Transaction -> TransactionEnv

--------------------
new: bank: Bank * transaction: Transaction -> TransactionEnv
val transaction: Transaction
val transferInTransaction: source: string -> target: string -> amount: decimal -> Effect<#IGrowsInto<Transaction,'b>,unit,TransferError> (requires 'b :> IAuditProvider and 'b :> ITransactionProvider)
type CompletingScope<'Scope (requires 'Scope :> CompletableScope)> = static member Within: body: Effect<'Inner,'a,'err> -> Effect<#IGrowsInto<'Scope,'Inner>,'a,'err>
<summary> Opens scopes of type <typeparamref name="'Scope" /> that are completed when their effect succeeds, such as transactions, for effects needing an environment grown from the one the scoped effect is run with. </summary>
val env: AppEnv
val show: effect: Effect<AppEnv,'a,'e> -> unit
val effect: Effect<AppEnv,'a,'e>
'a
'e
val result: Result<'a,'e>
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>
Multiple items
module Result from Microsoft.FSharp.Core

--------------------
type Result<'T,'TError> = | Ok of ResultValue: 'T | Error of ErrorValue: 'TError
val twoTransfers: Effect<AppEnv,unit,TransferError>
val transaction: CompletableScopeCreatingEffectBuilder<Transaction>
Multiple items
type CompletableScopeCreatingEffectBuilder<'Scope (requires 'Scope :> CompletableScope)> = inherit EffBuilderBase new: unit -> CompletableScopeCreatingEffectBuilder<'Scope> member Run: code: EffectCode<'Inner,'a,'a,'err> -> Effect<#IGrowsInto<'Scope,'Inner>,'a,'err>
<summary> A computation expression for effects running in a completable scope of type <typeparamref name="'Scope" />, such as a transaction. The effects bound in it are ordinary effects, of the environment the outer one grows into: running the expression begins the scope and grows the environment, and completes the scope if the effect succeeds, so the result is an effect of the outer environment, without the scope. </summary>
<example><code> let transaction = CompletableScopeCreatingEffectBuilder&lt;Transaction&gt;() let transfer source target amount = transaction { do! Accounts.withdraw source amount do! Accounts.deposit target amount } </code></example>


--------------------
new: unit -> CompletableScopeCreatingEffectBuilder<'Scope>
val payBonus: account: string -> amount: decimal -> Effect<#IGrowsInto<Transaction,'b>,decimal,'c> (requires 'b :> ITransactionProvider and 'b :> IAuditProvider)
val grace: decimal
val ada: decimal
Multiple items
type ProgressScope = interface Scoped new: unit -> ProgressScope member Report: step: string -> unit

--------------------
new: unit -> ProgressScope
val mutable steps: int
val step: string
Multiple items
namespace Orsak.Scoped

--------------------
type Scoped = IAsyncDisposable
<summary> A scope: something effects run in for a while, which is disposed when they are done, such as a transaction or a unit of work. </summary>
type IProgressProvider = abstract Progress: ProgressScope
Multiple items
type Progress<'T> = interface IProgress<'T> new: unit -> unit + 1 overload member ProgressChanged: EventHandler<'T>
<summary>Provides an <see cref="T:System.IProgress`1" /> that invokes callbacks for each reported progress value.</summary>
<typeparam name="T">Specifies the type of the progress report value.</typeparam>


--------------------
Progress() : Progress<'T>
Progress(handler: Action<'T>) : Progress<'T>
val report: step: string -> Effect<#IProgressProvider,unit,'b>
val p: #IProgressProvider
property IProgressProvider.Progress: ProgressScope with get
member ProgressScope.Report: step: string -> unit
val progress: ScopeCreatingEffectBuilder<ProgressScope>
Multiple items
type ScopeCreatingEffectBuilder<'Scope (requires 'Scope :> Scoped)> = inherit EffBuilderBase new: unit -> ScopeCreatingEffectBuilder<'Scope> member Run: code: EffectCode<'Inner,'a,'a,'err> -> Effect<#IGrowsInto<'Scope,'Inner>,'a,'err>
<summary> A computation expression for effects running in a scope of type <typeparamref name="'Scope" />. The effects bound in it are ordinary effects, of the environment the outer one grows into: running the expression begins the scope and grows the environment, so the result is an effect of the outer environment, without the scope. </summary>
<example><code> let progress = ScopeCreatingEffectBuilder&lt;ProgressScope&gt;() let importAll files = progress { for file in files do do! import file do! Progress.report file } </code></example>


--------------------
new: unit -> ScopeCreatingEffectBuilder<'Scope>
val inOrder: steps: (string * Effect<'r,unit,'e>) list -> Effect<#IGrowsInto<ProgressScope,'r>,unit,'e> (requires 'r :> IProgressProvider)
val steps: (string * Effect<#IProgressProvider,unit,'e>) list
'r
type 'T list = List<'T>
val name: string
val step: Effect<#IProgressProvider,unit,'e>
Multiple items
val report: step: string -> Effect<#IProgressProvider,unit,'b>

--------------------
val report: t: float -> Effect<#IProgressProvider,unit,'b>
Multiple items
type JobEnv = interface IGrowsInto<ProgressScope,JobProgressEnv> interface IAuditProvider new: unit -> JobEnv

--------------------
new: unit -> JobEnv
Multiple items
type JobProgressEnv = inherit JobEnv interface IProgressProvider new: scope: ProgressScope -> JobProgressEnv

--------------------
new: scope: ProgressScope -> JobProgressEnv
val scope: ProgressScope
val job: Effect<JobEnv,unit,string>
val jobResult: Result<unit,string>
val pay: account: string -> Effect<#IGrowsInto<Transaction,'b>,unit,'c> (requires 'b :> IProgressProvider and 'b :> ITransactionProvider)
val payAll: accounts: string list -> Effect<#IGrowsInto<ProgressScope,'b>,unit,'d> (requires 'b :> IGrowsInto<Transaction,'c> and 'c :> IProgressProvider and 'c :> ITransactionProvider)
val accounts: string list
Multiple items
type BatchBase = interface ScopeProvider<ProgressScope> interface CompletableScopeProvider<Transaction> interface IAuditProvider new: bank: Bank -> BatchBase

--------------------
new: bank: Bank -> BatchBase
type CompletableScopeProvider<'Scope (requires 'Scope :> CompletableScope)> = ScopeProvider<'Scope>
<summary> Implemented by an environment that can begin completable scopes of type <typeparamref name="'Scope" />. </summary>
type ScopeProvider<'Scope (requires 'Scope :> Scoped)> = abstract BeginScope: unit -> ValueTask<'Scope>
<summary> Implemented by an environment that can begin scopes of type <typeparamref name="'Scope" />. </summary>
Multiple items
type BatchEnv = inherit BatchBase interface IGrowsInto<ProgressScope,BatchProgressEnv> interface IGrowsInto<Transaction,BatchTransactionEnv> new: bank: Bank -> BatchEnv

--------------------
new: bank: Bank -> BatchEnv
Multiple items
type BatchTransactionEnv = inherit BatchBase interface ITransactionProvider new: bank: Bank * transaction: Transaction -> BatchTransactionEnv

--------------------
new: bank: Bank * transaction: Transaction -> BatchTransactionEnv
Multiple items
type BatchProgressEnv = inherit BatchBase interface IGrowsInto<Transaction,BatchProgressTransactionEnv> interface IProgressProvider new: bank: Bank * progress: ProgressScope -> BatchProgressEnv

--------------------
new: bank: Bank * progress: ProgressScope -> BatchProgressEnv
val progress: ProgressScope
Multiple items
type BatchProgressTransactionEnv = inherit BatchBase interface ITransactionProvider interface IProgressProvider new: bank: Bank * progress: ProgressScope * transaction: Transaction -> BatchProgressTransactionEnv

--------------------
new: bank: Bank * progress: ProgressScope * transaction: Transaction -> BatchProgressTransactionEnv
val batchEnv: BatchEnv
val showBatch: effect: Effect<BatchEnv,'a,'e> -> unit
val effect: Effect<BatchEnv,'a,'e>
val bonus: Effect<BatchEnv,decimal,TransferError>
val export: Effect<BatchEnv,unit,TransferError>
val settle: Effect<BatchEnv,unit,TransferError>

Type something to start searching.