CurryOn.FSharp.Control 0.1.7

There is a newer version of this package available.
See the version list below for details.
Install-Package CurryOn.FSharp.Control -Version 0.1.7
dotnet add package CurryOn.FSharp.Control --version 0.1.7
<PackageReference Include="CurryOn.FSharp.Control" Version="0.1.7" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add CurryOn.FSharp.Control --version 0.1.7
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
#r "nuget: CurryOn.FSharp.Control, 0.1.7"
#r directive can be used in F# Interactive, C# scripting and .NET Interactive. Copy this into the interactive tool or source code of the script to reference the package.
// Install CurryOn.FSharp.Control as a Cake Addin
#addin nuget:?package=CurryOn.FSharp.Control&version=0.1.7

// Install CurryOn.FSharp.Control as a Cake Tool
#tool nuget:?package=CurryOn.FSharp.Control&version=0.1.7
The NuGet Team does not provide support for this client. Please contact its maintainers for support.

CurryOn.FSharp.Control

The CurryOn.FSharp.Control library extends the FSharp.Control namespace with a framework for enabling the use of Railway-Oriented Programming patterns with the Task Parallel Library (TPL), Async Workflows, and Lazy Computations.

This is accomplished by providing a set of types for working with Operations and their results. An Operation is any function or expression that is intended to participate in the Railway-Oriented patterns, and is created by use of the operation Computation Expression.

open System.IO

let readFile (fileName: string) =
    operation {
        use fileStream = new StreamReader(fileName)
        return! fileStream.ReadToEndAsync()
    }    

The example above creates a function val readFile : fileName:string -> Operation<string,exn> that takes a file name and returns Operation<string,exn> representing the result of reading all text from the file. The Operation type is a discriminated union with four cases:

type Operation<'result,'event> =
| Completed of Result: OperationResult<'result,'event>
| InProcess of IncompleteOperation: InProcessOperation<'result,'event>
| Deferred of Lazy: EventingLazy<Operation<'result,'event>>
| Cancelled of EventsSoFar: 'event list

The cases of the Operation discriminated union represent the possible states of the Operation after invocation. Since the framework supports working with Tasks and Async Workflows, the Operation may not complete immediately, and may be cancelled, so the InProcess and Cancelled cases represent these states. Since the framework supports working with Lazy computations, the Deferred case represents Operations in the state of waiting for a Lazy to be evaluated.

Operations that are not completed can be waited on synchronously using Operation.wait. They can also be waited on with an F# Async using Operation.waitAsync or as a Task using Operation.waitTask. These functions return the same type as the Completed case of the Operation discriminated union, OperationResult<'result,'event>.

type OperationResult<'result,'event> =
| Success of Result: SuccessfulResult<'result,'event>
| Failure of ErrorList: 'event list

The OperationResult type represents the result of a Completed Operation. In the readFile example above, the result type would be OperationResult<string,exn>, since the resulting value is a string, and since the operation may throw exceptions, such as FileNotFoundException. If no exceptions are thrown and the Operation completed successfully, the OperationResult will be the Success case, and the result will be contained within a SuccessfulResult<'result,'event>. If any exception is thrown during the operation, the OperationResult will be the Failure case, and any exceptions thrown will be present in the list.

The SuccessfulResult<'result,'event> type is used to contain the resulting value and any domain events associated with a successful Operation. The SuccessfulResult type also has members .Result and .Events to provide direct access to the result value and the domain events without pattern-matching.

type SuccessfulResult<'result,'event> =
| Value of ResultValue: 'result
| WithEvents of ResultWithEvents: SuccessfulResultWithEvents<'result,'event>

When no domain events are associated with the SuccessfulResult, the Value case will be used, and the 'result will be directly accessible. When a successful Operation also returns domain events, the results will be contained in a SuccessfulResultWithEvents<'result,'event> record type.

type SuccessfulResultWithEvents<'result,'event> =
    {
        Value: 'result
        Events: 'event list
    }

This allows the framework to support a usage pattern where a successful Operation can also return domain events, or carry Warnings or Informational messages along with the resulting value. To use the framework in this way, it is common practice to create a discriminated union representing the possible errors, warnings, or domain events. Then, the events can be propogated from one operation to another, such as in the following examples:

type FileAccessEvents =
| FileOpenedSuccessfully
| FileReadSuccessfully
| FileNotFound of string
| FileIsInSystemRootWarning
| UnhandledException of exn // This is returned automatically if an unhandled exception is thrown by an Operation

let getFile (fileName: string) =
    operation {
        let file = FileInfo fileName
        return! if not file.Exists
                then Result.failure [FileNotFound file.FullName]
                else Result.success file
    }

let openFile fileName =
    operation {
        let! file = getFile fileName
        return! file.OpenText() |> Result.successWithEvents <| [FileOpenedSuccessfully]
    }

let readFile fileName = 
    operation {
        use! fileStream = openFile fileName
        let! fileText = fileStream.ReadToEndAsync()
        return! Result.successWithEvents fileText [FileReadSuccessfully]
    }

let writeFile fileName contents =
    operation {
        let! file = getFile fileName
        let stream = file.OpenWrite()
        do! stream.AsyncWrite contents
        return! if file.DirectoryName = Environment.SystemDirectory
                then Result.success ()
                else Result.successWithEvents () [FileIsInSystemRootWarning]
    }

When used in this way, the Operation framework allows for informational messages or domain events to be propogated from one operation to another, such that calling readFile (with a file that exists) would return a Success with two events, FileOpenedSuccessfully and FileReadSuccessfully. It also allows any known errors and warnings to be handled and returned from one Operation to another, terminating when a Failure is encountered without running Operations farther down the chain. Any unforseen exceptions that may still be raised will be captured with the UnhandledException case. It is recommended to include a case such as this in any discrimintaed union used for the 'event type of an Operation, as the framework contains special logic to seek out a union case with a single field of type exn when an uhandled exception is thrown from an Operation. This allows the exception to be captured and returned without changing the type of the Operation from Operation<'result,'event> to Operation<'result,exn>. If the Operation is already of type Operation<'result,exn>, the unhandled exception is returned in the list of exceptions in the Failure case of the OperationResult.

NuGet packages (3)

Showing the top 3 NuGet packages that depend on CurryOn.FSharp.Control:

Package Downloads
CurryOn.Elastic.FSharp

An F#-idiomatic Library for Elasticsearch, including an F# DSL representation of the Elasticsearch Query Model, and F# modules for searching and indexing documents as well as creating and maintaining indexes.

CurryOn.Common

A library of tools for F# development, used by other CurryOn packages for standard utilities and extensions.

CurryOn.Akka.Persistence.Streaming

Framework for creating Akka.net Persistence Plug-Ins based on Stream data-stores.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
0.1.9 661 7/13/2018
0.1.8 531 6/25/2018
0.1.7 558 6/8/2018
0.1.6 612 2/13/2018
0.1.5 828 2/9/2018
0.1.4 662 2/2/2018
0.1.3 753 1/23/2018
0.1.2 1,505 1/4/2018
0.1.1 609 1/3/2018
0.1.0 575 1/2/2018

Fixed implementation of Operation.bind to use correct function parameter type.