Introduction to F#'s Computation Expressions

Article ID: 2832367 - View products that this article applies to.

About Author:

Collapse this tableExpand this table
Collapse this imageExpand this image
MVP
This article is provided by MVP Eriawan Kusumawardhono. Microsoft is so thankful that MVPs who proactively share their professional experience with other users. The article would be posted on MVP's website or blog later.
Expand all | Collapse all

On This Page

Overview

F# is multi paradigm programming language. It contains a functional programming language and object oriented programming language, although it is more emphasized as functional programming language and also the code is functional in style. A features that exist commonly in functional programming is function composition and sequencing, and it is widely used to implement Monad.

F# has computation expression in the forms of control flows and bindings to compose and combine operations, functions. Computation expression in F# is F# functional feature to implement Monad. Computation expression in monad therefore can be used to handle side effect computations, not just control flow and data.


Built-in computation expressions in F#

F# has already implemented computation expression built-in F# language: the sequence operations, the seq and the asynchronous workflow of async. seq uses yield and yield! to define sequence operations and also it gives us control flow in form of delayed or lazy evaluation. Having Asynchronous workflow is very useful and gives us the easiness to code asynchronous operations using async keywords.

Implementing your own computation expressions

Implementing computation expression in F# has many ways, but the most common way is implementing builder pattern by using this syntax:

builder-name { expression }

This builder-name is an identifier for an instance of a special type known as the builder type or class. Another way to describe a builder class is to say that it enables you to customize the operation of many F# constructs, such as loops and bindings. These are the methods and the signatures of methods inside a builder:

Collapse this imageExpand this image
list of methods for builder


As we could observe from the table above, many of the methods return M<'T> as the return type. And this construct is also used in built-in Async<'T> and Seq<'T> in F#.

F# MSDN Library of computation expression put more information on this:

"The signatures of these methods enable them to be combined and nested with each other, so that the workflow object returned from one construct can be passed to the next."
This also implies that the types returned needs to be the same in order to combine one construct with another, as we often see in combining operations of sequence and asynchronous workflow in F#.

Sample implementation of computation expressions

We can create many kinds of Monads using F#. The samples below will provide sample scenarios from simple Maybe Monad to handle complexities of accepting inputs to exploiting computations expressions to implement a simple Domain Specific Language.

We are now creating Maybe monad. This is the background scenario: we want to write a function which asks the user for 3 integer inputs between 0 and 100 (inclusive). But if there is invalid inputs such as non numeric or non integer, the function execution should be aborted.

We can write the original intent in F# like this:

let addThreeNumbers() =
    let getNum msg =
        printf "%s" msg
        match System.Int32.TryParse(System.Console.ReadLine()) with
        | (true, n) when n >= 0 && n <= 100 -> Some(n)
        | _ -> None
 
    match getNum "#1: " with
    | Some(x) ->
        match getNum "#2: " with
        | Some(y) ->
            match getNum "#3: " with
            | Some(z) -> Some(x + y + z)
            | None -> None
        | None -> None
    | None -> None
The code has done its job well but it looks repetitive in some functions and therefore quite redundant. Using computation expressions we can rewrite the code above like this:

let addThreeNumbers() =
    let bind(input, rest) =
        match System.Int32.TryParse(input()) with
        | (true, n) when n >= 0 && n <= 100 -> rest(n)
        | _ -> None
 
    let createMsg msg = fun () -> printf "%s" msg; System.Console.ReadLine()
 
    bind(createMsg "#1: ", fun x ->
        bind(createMsg "#2: ", fun y ->
            bind(createMsg "#3: ", fun z -> Some(x + y + z) ) ) )

Now the function looks simpler, and it uses multiple bind to bind operations of createMsg with the bind definitions after the function signature. It also handles side effects gracefully.

Another common Monad is Reader monad. This Reader monad is very useful, read values from the environment and sometimes execute sub-computations in a modified environment (with new or shadowing bindings, for example), but they do not require the full generality of the State monad.

This is a sample code of Reader Monad:

    open System
    type Reader<'r,'a> = 'r -> 'a

    let bind k m = fun r -> (k (m r)) r
    
    /// The reader monad.
    /// This monad comes from Matthew Podwysocki's http://codebetter.com/blogs/matthew.podwysocki/archive/2010/01/07/much-ado-about-monads-reader-edition.aspx.
    type ReaderBuilder() =
        member this.Return(a) : Reader<'r,'a> = fun _ -> a
        member this.ReturnFrom(a:Reader<'r,'a>) = a
        member this.Bind(m:Reader<'r,'a>, k:'a -> Reader<'r,'b>) : Reader<'r,'b> = bind k m
        member this.Zero() = this.Return ()
        member this.Combine(r1, r2) = this.Bind(r1, fun () -> r2)
        member this.TryWith(m:Reader<'r,'a>, h:exn -> Reader<'r,'a>) : Reader<'r,'a> =
            fun env -> try m env
                       with e -> (h e) env
        member this.TryFinally(m:Reader<'r,'a>, compensation) : Reader<'r,'a> =
            fun env -> try m env
                       finally compensation()
        member this.Using(res:#IDisposable, body) =
            this.TryFinally(body res, (fun () -> match res with null -> () | disp -> disp.Dispose()))
        member this.Delay(f) = this.Bind(this.Return (), f)
        member this.While(guard, m) =
            if not(guard()) then this.Zero() else
                this.Bind(m, (fun () -> this.While(guard, m)))
        member this.For(sequence:seq<_>, body) =
            this.Using(sequence.GetEnumerator(),
                (fun enum -> this.While(enum.MoveNext, this.Delay(fun () -> body enum.Current))))
This is the sample usage of this Reader Builder:

    let reader = new ReaderBuilder()
    
    let ask : Reader<'r,'r> = id
    let asks f = reader {
        let! r = ask
        return (f r) }
Those builder usages can also be used to compose UI. The patterns of this composition can often be looked like a Domain Specific Language in a controlled manner within the builder.

The reader monad code article was excerpted from Matthew Podwysocki's blog of implementing Reader Monad: http://codebetter.com/matthewpodwysocki/2010/01/07/much-ado-about-monads-reader-edition/

The builder can also be used to compose User Interface (UI). This is the builder in object oriented implementation style using WPF:

#r "WindowsBase.dll"
#r "PresentationCore.dll"
#r "PresentationFramework.dll"
#r "System.Xaml"
#endif
 
open System
open System.Windows
open System.Windows.Controls
 
[<AbstractClass>]
type IComposableControl<'a when 'a :> FrameworkElement> () =
    abstract Control : 'a
    abstract Bind : FrameworkElement * (FrameworkElement -> 'a) -> 'a
    abstract Bind : IComposableControl<'b> * (FrameworkElement -> 'a)  -> 'a
    member this.Return (e: unit)  = this.Control
    member this.Zero () = this.Control
 
type WindowBuilder() =
    inherit IComposableControl<Window>()
    let win = Window(Topmost=true)
    override this.Control = win
    override this.Bind(c: FrameworkElement, body: FrameworkElement -> Window) : Window =
        win.Content <- c
        body c
    override this.Bind(c: IComposableControl<'b>, body: FrameworkElement -> Window) : Window =
        win.Content <- c.Control
        body c.Control
 
type PanelBuilder(panel: Panel) =
    inherit IComposableControl()
    override this.Control = panel
    override this.Bind(c: FrameworkElement, body: FrameworkElement -> Panel) : Panel=
        panel.Children.Add(c) |> ignore
        body c
    override this.Bind(c: IComposableControl<'b>, body: FrameworkElement -> Panel) : Panel=
        panel.Children.Add(c.Control) |> ignore
        body c.Control
 
 
And this is the use of the builders above:

 
let win =
    WindowBuilder()
        {   let! panel =
                PanelBuilder(StackPanel())
                    {   let! btn1 = Button(Content = "Hello")
                        let! btn2 = Button(Content = "World")
                        return () }
            return () }
 
win.Show() // Pops up the window in FSI.
 
#if COMPILED
[<STAThread()>]
do
    let app =  new Application() in
    app.Run() |> ignore
#endif

Notice the implementation of IComposableControl; it has abstract declaration of Bind methods. The actual concrete implementations, the WindowBuilder and the PanelBuilder implement Bind nicely with the type flows.

The UI builders above was excerpted from Adam Granicz article: http://www.devx.com/enterprise/Article/40481

More information

For more information, please visit:
  1. In Visual Studio 2010: http://msdn.microsoft.com/en-us/library/dd233182(v=vs.100).aspx
  2. In Visual Studio 2012: http://msdn.microsoft.com/en-us/library/dd233182.aspx


Community Solutions Content Disclaimer

MICROSOFT CORPORATION AND/OR ITS RESPECTIVE SUPPLIERS MAKE NO REPRESENTATIONS ABOUT THE SUITABILITY, RELIABILITY, OR ACCURACY OF THE INFORMATION AND RELATED GRAPHICS CONTAINED HEREIN. ALL SUCH INFORMATION AND RELATED GRAPHICS ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. MICROSOFT AND/OR ITS RESPECTIVE SUPPLIERS HEREBY DISCLAIM ALL WARRANTIES AND CONDITIONS WITH REGARD TO THIS INFORMATION AND RELATED GRAPHICS, INCLUDING ALL IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, WORKMANLIKE EFFORT, TITLE AND NON-INFRINGEMENT. YOU SPECIFICALLY AGREE THAT IN NO EVENT SHALL MICROSOFT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY DIRECT, INDIRECT, PUNITIVE, INCIDENTAL, SPECIAL, CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF USE, DATA OR PROFITS, ARISING OUT OF OR IN ANY WAY CONNECTED WITH THE USE OF OR INABILITY TO USE THE INFORMATION AND RELATED GRAPHICS CONTAINED HEREIN, WHETHER BASED ON CONTRACT, TORT, NEGLIGENCE, STRICT LIABILITY OR OTHERWISE, EVEN IF MICROSOFT OR ANY OF ITS SUPPLIERS HAS BEEN ADVISED OF THE POSSIBILITY OF DAMAGES.

Properties

Article ID: 2832367 - Last Review: September 25, 2013 - Revision: 3.1
Applies to
  • Microsoft Visual Studio Express 2012 for Web
  • Microsoft Visual Studio Premium 2012
  • Microsoft Visual Studio Professional 2012
  • Microsoft Visual Studio Ultimate 2012
Keywords: 
kbstepbystep kbmvp kbcommunity KB2832367

Give Feedback

 

Contact us for more help

Contact us for more help
Connect with Answer Desk for expert help.
Get more support from smallbusiness.support.microsoft.com