Chapter 6. Other Unique Features

In previous chapters, I showed you exciting things you can do in F# as well as various F# data structures. This chapter itemizes the F# data structures and describes them in detail. This chapter is crucial to starting your F# development in full swing.

Working with Reference Cells

You already used the reference cell in Example 1-86 from Chapter 1. In that code, a reference cell was used to wrap a ref parameter. According to the MSDN documentation, a reference cell is a storage structure used to create mutable data. The reference cell definition is shown in Example 6-1, which is actually a record type containing a mutable variable.

Example 6-1. Reference cell definition
type Ref<'a> =
    { mutable contents: 'a }

This is another way to define mutable data, which you can see in Example 6-2. You can think of the reference cell as a pointer. The ref operator is used to get the pointer, the := is used to set the underlying value, and the exclamation point (!) is used to return the underlying value. Example 6-2 provides an example.

Example 6-2. Reference cell example
// make a reference.
let refVar = ref 1

// Change the value pointed by the reference.
refVar := 5

// Dereference by using ! operator.
printfn "%d" !refVar

As mentioned previously in this book, F# provides two shortcut functions, incr and decr, to increase or decrease the reference value. Example 6-3 demonstrates how to use these two functions.

Example 6-3. Reference cell functions incr and decr
let a = ref 0

// increase a by 1
incr a
printfn "a = %A" !a

// decrease a by 1
decr a
printfn "a = %A" !a

Execution result

a = 1
a = 0

The reference cell might seem like a redundant design. As you explore more F# features, you will soon realize that under certain circumstances the reference cell is the only way to define mutable values.

Working with Object Expressions

C# has anonymous functions you can use so that making function definitions is faster. As a functional-first language, F# already solves this problem, but F# needs a better way to define classes and implement interfaces. Well, you have object expressions to solve this problem. One advantage of object expressions is that less code is needed to create an instance of an object because you don’t need to declare a full type definition. You will see the advantage of this shortly with several of the examples in this section. You might be familiar with the SOLID (Single responsibility, Open-closed, Liskov substitution, Interface segregation, and Dependency inversion) design principles, which are described at http://en.wikipedia.org/wiki/SOLID_(object-oriented_design). The last principle referred to in the acronym requires that the program rely on abstractions rather than concrete implementations. The object expression is a way for F# developers to provide concrete implementations of interfaces with less code.

The object expression can create an instance of a dynamically created, anonymous object type based on an existing type. In Example 6-4 and Example 6-7, the types for objExpression and objExpression2 are created on the fly. Example 6-4 shows how to implement an interface with an object expression. The objExpression variable type is based on IMyInterface and can be passed in any function that needs an IMyInterface parameter.

Example 6-4. Use mutable-variable data storage in the object expression
// define interface IA
type IA =
    abstract member F : int with get, set


// object expression
let objectExpression =
    let dataStorage = ref 9  //actual data storage
    {
        new IA with
            member this.F
                with get() = !dataStorage
                and set(v) = dataStorage :=  v
    }

The object expression is very powerful. It can be used to implement multiple interfaces or create objects based on a class. However, the object expression cannot extend base classes by adding new methods or properties. It can override only virtual or abstract methods. An object expression, like objExpression2, is not a type, so it is not possible to create object expressions based on other object expressions.

Example 6-5. Implementing an object expression interface
// first interface
type IMyInterface =
    abstract F : unit -> unit

// second interface
type IMyInterface2 =
    abstract F2 : unit -> unit

// implement one interface using object expression
let objExpression =
    {
        new IMyInterface with
            member this.F() = printfn "hello object expression"
    }

// object expression implementing two interfaces
let objExpression2 =
    {
        new IMyInterface with
            member this.F() = printfn "from interface"
        interface IMyInterface2 with
            member this.F2() = printfn "from interface2"
    }

Note

Auto-implemented properties are not supported in an object expression. Properties in object expressions can be declared only by using the old syntax.

If the field needs to be mutable, another approach is to use a reference cell. Example 6-6 shows how to define a reference cell inside and outside of the object expression. And Example 6-7 shows how to extend a base class.

Example 6-6. Object expression with a property
// first interface
type IMyInterface =
    abstract F : unit -> unit
    abstract Prop0 : string with get, set
    abstract Prop1 : string with get, set

// define a reference cell outside of objExpression
let myData = ref ""

// implement one interface using object expression
let objExpression =
    // define a reference cell inside the objExpression
    let x = ref ""
    {
        new IMyInterface with
            member this.F() = printfn "hello object expression"
            member this.Prop0 with get() = !x and set(v) = x:=v
            member this.Prop1 with get() = !myData and set(v) = myData:=v
    }

// set the Prop0 and print out the result
objExpression.Prop0 <- "set Prop0"
printfn "%s" objExpression.Prop0

// set the Prop1 and print out the result
objExpression.Prop1 <- "set Prop1"
printfn "%s" objExpression.Prop1
Example 6-7. Object expression based on class
// define an interface
type IMyInterface2 =
    abstract F2 : unit -> unit

// abstract class definition
[<AbstractClass>]
type MyAbstractClass() =
    abstract F : unit -> unit
    member this.F2() = printfn "my abstract class"

// object expression with abstract class and interface
let objExpressionInterfaceAbstract =
    {
        new MyAbstractClass() with
            override this.F() = printfn "implement the abstract class"
        interface IMyInterface2 with
            member this.F2() = printfn "from interface2"
    }

// override the object's ToString method using object expression
let objExpressionExtendObject =
    {
        new System.Object() with
            override this.ToString() = "hello from object expression"
    }

Note

As in C#, the base class (which contains some concrete class member implementations) must come before all interfaces in the derived class definition. In an F# object expression, an abstract class must be present before any interface. In other words, the code will not compile if MyAbstractClass is put after IMyInterface2 in the objExpressionInterfaceAbstract.

Example 6-8 is a real-world example that implements a WPF command. WPF commands usually do not involve the construction of a class hierarchy. So it is a perfect place to use an object expression. From the sample code, you can see the object expression can have fields that cannot be accessed directly from outside. The variable event1 can be viewed as a private field being implemented in the object expression. This example also shows that an object expression with parameters, like action in createCommand, can serve as a class factory to create objects based on passed-in functions.

Example 6-8. WPF command using an object expression
open System.Windows.Input

// class factory to generate the WPF command object
let createCommand action canExecute=
            let event1 = Event<_, _>()
            {
                new ICommand with
                    member this.CanExecute(obj) = canExecute(obj)
                    member this.Execute(obj) = action(obj)
                    member this.add_CanExecuteChanged(handler) =
                        event1.Publish.AddHandler(handler)
                    member this.remove_CanExecuteChanged(handler) =
                        event1.Publish.AddHandler(handler)
            }

// create a dummy command that does nothing and can always be executed
let myDummyCommand = createCommand
                            (fun _ -> ())   // execution function does nothing
                            (fun _ -> true) // this command can always be executed

Note

PresentationCore.dll and WindowsBase.dll need to be referenced for this example to compile.

Imagine a scenario where you have functions and variables in your module and need a way to provide the functionality to a C# method. The quickest way to organize the code inside a module and pass it to a C# method is to use an object expression. Example 6-9 shows a sample solution with three projects. The object expression provides a convenient way to put individual functions defined in a module into a unit. If the development team has some domain-knowledge experts whose background is not in computer software, these domain experts can write the function in the module and a software developer can use the object expression to quickly organize the functions into a unit. The following list shows how to use F# object expression to organize code:

  • First, a C# project is created in which an interface named IMyInterface is defined. This project will be referenced by other C# projects and the F# project.

  • Second, a C# project is created and defines a class method named MyFunction, which takes IMyInterface as a parameter.

  • And finally, an F# project creates an object expression and passes the object expression to the MyFunction class.

Example 6-9. Using object expressions to organize code

IMyInterface definition in the first C# project

public interface IMyInterface
{
    void MyFunction();
    int MyProperty { get; set; }
}

MyFunction definition in the second C# project

public class MyClass
{
    public void MyFunction(IMyInterface myObj)
    {
        // <my operations>
    }
}

Using an object expression to organize code and invoke a C# function

// function library module
module MyFunctionLibrary =
    let myFunction () = () //< other operations >

// use object expression to organize code from module
let myData =
        let myData = ref 9
        { new ClassLibrary1.IMyInterface with
                 member this.MyFunction() = MyFunctionLibrary.myFunction()
                 member this.MyProperty
                    with get() = !myData
                    and set(v) = myData := v
        }

// invoke the C# method using an object expression
let myClass = ConsoleApplication1.MyClass()
myClass.MyFunction(myData)

There are some restrictions for object expressions. The type information for the object is generated by the compiler; therefore, the type name is not user friendly. The type that backs up the object expression is sealed, so the back-end type does not help you a lot when you really need a complex inheritance hierarchy.

Working with Options

Example 6-10 defines a function that returns an int option. According to the MSDN documentation, options have the following characteristics:

The option type in F# is used when an actual value might not exist for a named value or variable. An option has an underlying type and can hold a value of that type, or it might not have a value.

For a C# developer, Example 6-10 is similar to Example 6-11, which uses Nullable<T>. Both functions filter out odd numbers. The C# function returns NULL, while the F# function returns None. Table 6-1 shows the members in the option.

Table 6-1. Option property and method

Property or Method

Type

Description

None

‘T option

The option None value. This is a static property.

IsNone

Boolean

If the option is None, it returns TRUE.

IsSome

Boolean

If the option has some value, it returns TRUEs.

Some

‘T option

Like None, but it returns a Some value.

Value

‘T

Retrieves the underlying value. If there is no value (None), a NullReferenceException is thrown.

There is a subtle difference between NULL and None. First, F# classes and other reference objects do not accept NULL as a valid value. For example, the GetDistance function in Example 2-16 cannot be passed NULL.

Example 6-10. Function that returns an int option
// function only returns even-number option
let filterOutOddNumber a =
    if a % 2 = 0 then Some(a)
    else None
Example 6-11. C# function that filters out odd numbers
static Nullable<int> FilterOutOddNumber(int a)
{
    if (a % 2 == 0)
    {
        return a;
    }
    else
    {
        return null;
    }
}

The fact that the F# function GetDistance cannot accept NULL as a parameter is not a bug. Actually, this is a carefully thought-out F# feature. Example 6-12 shows how to check the user input string in C# and F#. The C# code seems perfect until you realize that str can be null! So you have to first check that str != null and then perform the real work. And this kind of error can be caught only at runtime. Many developers think about this kind of error, but the errors are often missed. The person who implements read_string often assumes that the invoker should handle the NULL, and the compiler does not say anything if the NULL value is not being considered.

Example 6-12. Reading an input string using C# and F#

C# code

//a function that returns a string type value
var str = read_string();

if (str.Length < 10) Console.WriteLine("less than 10");
else Console.WriteLine"long string...";

F# code using an option

// return a string option to indicate the return value can be an uninitialized value
let str = read_string()
match str with
    | Some data -> printfn "good, we got value and we can process..."
    | None -> printfn "what happened, call IT right now!"

Note

A simple implementation of read_string() is Console.ReadLine(). When Ctrl+Z is pressed, the Console.ReadLine() method returns NULL.

For the F# programmer, the situation is different. The user can give invalid input, so the code must represent the invalid input. An option type is used to explicitly indicate the invalid state. When an F# programmer invokes the read_string function, the returned option type reminds (or even forces) the programmer to handle this invalid situation. The design of F# helps the developer avoid many bugs. One typical example is how F# introduces an option rather than using a NULL value to represent multiple states. If you count how many NULL exceptions you received in the last month, it is not difficult to understand why F# programmers report fewer bugs in their code.

Let’s look at a customized class sample in C#. If you have a Person type object, what is the meaning of an assigned value of NULL? It can mean either the object is in an unassigned state or the object is in a special-case state (that is, it has an invalid value). This ambiguity breaks the single-responsibility principle, the first principle in SOLID. If two different developers on the same team have a different view about what NULL means for the object, there will likely be many null-reference flaws in the application. If you compare this to the F# option approach, None is used to represent no value and Some(...) is used to represent any valid value. This clarifies the definition.

When we designed the language, we knew that not everyone agreed with the preceding argument. This is totally fine because you can use the AllowNullLiteral attribute to make a class nullable. Example 6-13 shows an example of how to use the AllowNullLiteral attribute.

Example 6-13. Using AllowNullLiteral on a type
[<AllowNullLiteral>]
type NullableType() =
    member this.TestNullable(condition) =
        if condition then NullableType()
        else null

Note

This code does not compile if the type is not decorated with the AllowNullLiteral attribute.

After reading through Example 6-13, you might wonder why F# still allows null values. This is because F# is living in a world full of objects and methods that accept NULL as an input value. If there were no support for NULL in F#, a number of .NET calls would be impossible. Also, NULL is needed when a variable needs to be bound to a function. See Example 6-14.

Example 6-14. Checking the function initialization
type A() =
    [<DefaultValue>]
    val mutable MyFunction : unit -> int
    member this.IsMyFunctionInitialized
        with get() = box this.MyFunction = null

In summary, the NULL restriction exists only in F#. If you reference an F# project from a C# project, you can still pass NULL into an F# function. If a C# function returns a NULL value inside of F# code, it is also still acceptable.

Before finishing this section, I will list some helper functions and tricks you can use to handle NULL and options. Example 6-15 provides a way to convert the NULL value to an option and convert it back to NULL if it is None. In addition to using AllowNullLiteral, this code provides an alternative to handle the NULL problem. The Unchecked.defaultof<T> operator returns the default value for T, such as 0 for int and NULL for any reference type.

Example 6-15. Converting NULL to or from an option when interacting with C#
type A() =
    let mutable a:option<A> = None
    member this.Value
        with get() =
            match a with
            | Some n -> n
            | None -> Unchecked.defaultof<A>
        and set v =
            if v = Unchecked.defaultof<A> then a <- None
            else a <- Some v

Working with Units of Measure

When I drove from freezing Toronto, Ontario to the NASA space center in Florida, I was shocked to see an LED screen showing that the current temperature was 28. I had just crossed the Niagara Falls a few minutes prior and could not believe that the warm Florida weather was so close! People from the US will giggle because they know that the temperature on the LED was expressed in Fahrenheit. Because Canada uses Celsius, the difference between metric and English units of measure confused me. Believe it or not, the first story I heard from NASA was about the Mars Climate Orbiter accident in 1999, whose root cause was the use of an incorrect unit of measure.

If a programming language supports units, as F# does, these problems are totally avoidable. Example 6-16 shows how to define a unit of measure. The value x is bound to 3 cm, and the value c is bound to 27 ml. If you try to set a float number of 2.8 to the value c, it will not work because c is a float<ml> type, which cannot take a float. Similarly, the value c cannot take x2 because the milliliter is a cube of centimeter.

Example 6-16. Defining and using a unit-of-measure type

Defining the units

//define centimeter
[<Measure>] type cm

// define milliliter which is cm2
[<Measure>] type ml = cm^3

let x = 3.<cm>
let c = x * x * x
// let c:float<ml> = x * x  //does not compile, because ml is a cube of cm
// let c:float<ml> = 2.8    //does not compile, cannot assign float to float<ml>

Execution result

[<Measure>]
type cm
[<Measure>]
type ml = cm ^ 3
val x : float<cm> = 3.0
val c : float<ml> = 27.0

Note

Spaces are not allowed between a number and a unit of measure.

Note

F# supports the (**) operator, which is equivalent to Math.Pow. However, the ** does not support unit, so the code let d:float<ml> = x ** 3 does not compile. This is because ** yields a float, which is not compatible with a float<ml> type.

The unit of measure can be presented in different but equivalent ways. F# can convert these different representations to a consistent format. From the execution result in Example 6-17, you can see that the variables kgm21 to kgm24 show the same result kg/m2.

Example 6-17. Different definition formats convert to consistent format

Define unit of measure

[<Measure>] type kg
[<Measure>] type m

[<Measure>] type kgm21 = kg/m ^ -2
[<Measure>] type kgm22 = kg/m/m
[<Measure>] type kgm23 = kg/(m * m)
[<Measure>] type kgm24 = kg/(m ^ 2)

Execution result

[<Measure>]
type kg
[<Measure>]
type m
[<Measure>]
type kgm21 = kg m ^ 2
[<Measure>]
type kgm22 = kg/m ^ 2
[<Measure>]
type kgm23 = kg/m ^ 2
[<Measure>]
type kgm24 = kg/m ^ 2

Units of measure can be converted as well. Example 6-18 shows a function that converts gram to kilogram.

Example 6-18. Converting units from gram to kilogram
// define gram
[<Measure>] type g

// define kilogram
[<Measure>] type kg

// define a conversion function that converts gram to kilogram
let convertGramToKilogram (x : float<g>) = x / 1000.0<g/kg>

Note

Because the unit of measure exists only at F#’s compile time, C# code is not able to access the unit of measure. The float<’measureUnit> can be viewed only as a double in C# code.

Note

As mentioned in the previous note, a unit of measure is a compile-time feature that cannot be accessed at runtime. Any attempt to access the unit at runtime will not work. For example, ToString() cannot print out any unit information.

A unit of measure can have static members, as shown in Example 6-19.

Example 6-19. Adding static members to unit-of-measure types
[<Measure>]
type m =
        static member C = 3.28<foot/m>
        static member FromFoot (x:float<foot>) = x / m.C
        static member ToFoot(x:float<m>) = x * m.C

and [<Measure>] foot =
        static member FromMeter = m.ToFoot
        static member ToMeter = m.FromFoot

m.FromFoot(3.28<foot>)

Execution result

val it : float<m> = 1.0

A unit of measure is defined as a type, so it can be used in generic functions. Example 6-20 defines a generic unit function that can add any two numbers. The function makes sure that numbers with different units of measure cannot be processed together. The execution result is result1 = 10.0, result2 = 5.0.

Example 6-20. A generic function using a unit of measure
// define units of measure
[<Measure>] type lb
[<Measure>] type inch

// generic function
let add ( x : float<'u>) (y : float<'u>)= x + y * 2.

let result1 = add 4.<lb> 3.<lb>
let result2 = add 1.<inch> 2.<inch>

printfn "result1 = %A, result2 = %A" result1 result2

Note

In the printfn statement, you have to use %A as the type identifier. The variable result1 is a float at runtime, but it is float<lb> type at compile time. The %f is not compatible with the unit of measure, so it will not compile.

F# also supports the ability to create a float value with units of measure by using LanguagePrimitives.FloatWithMeasure and LanguagePrimitives.Float32WithMeasure. Additionally, you can remove a unit of measure by converting it to a number. See Example 6-21.

Example 6-21. Converting a number to and from a unit of measure
// remove unit and return float
let removeUnit<[<Measure>]'u> x = float x

let result = removeUnit 4.<lb>

// give a unit of measure to a float
let fourlb : float<lb> = LanguagePrimitives.FloatWithMeasure result

Units of measure can be applied only to float and signed-integer types. If you want to use a unit with a customized type, you need to use the generic type definition. The unit of measure is a special type, so it can be used in the generic type definition without any problem. Example 6-22 defines a bank account class. By applying different units of measure, you generate bank accounts for different currencies.

Example 6-22. Bank account using a unit of measure
// account state enum
type AccountState =
    | Overdrawn
    | Silver
    | Gold

// units of measure: US and Canadian dollars
[<Measure>] type USD
[<Measure>] type CND

// bank account class
type Account<[<Measure>] 'u>() =
    let mutable balance = 0.0<_>
    member this.State
        with get() =
            match balance with
            | _ when balance <= 0.0<_> -> Overdrawn
            | _ when balance > 0.0<_> && balance < 10000.0<_> -> Silver
            | _ -> Gold
    member this.PayInterest() =
        let interest =
            match this.State with
                | Overdrawn -> 0.
                | Silver -> 0.01
                | Gold -> 0.02
        interest * balance
    member this.Deposit x =  balance <- balance + x
    member this.Withdraw x = balance <- balance - x

let measureSample() =
    // make US dollar account
    let account = Account<USD>()
    let USDAmount = LanguagePrimitives.FloatWithMeasure 10000.
    account.Deposit USDAmount
    printfn "US interest = %A" (account.PayInterest())

    // make Canadian dollar account
    let canadaAccount = Account<CND>()
    let CADAmount : float<CND> = LanguagePrimitives.FloatWithMeasure 10000.
    canadaAccount.Deposit CADAmount
    printfn "Canadian interest = %A" (canadaAccount.PayInterest())

measureSample()

For some C# developers, type is something they encounter every day but pay little attention to. A large number of test cases are brought up each day to check the correctness of the program. Have you ever thought about the compiler that actually checks your code on every branch before you run the program? If an error can be caught at compile time, it can be fixed much easier. The unit of measure is one example that uses the type system to reduce the possibility of making mistakes. Later in this section, you will see more examples of using the type system to reduce the possibility of making errors.

Working with Records

As previously mentioned, a tuple is a set of values. You can think of a record as an enhanced version of a tuple. A record is a set of named values, and it provides more features than a tuple does. Example 6-23 shows how to define and create a record. If the label in the record definition is unique, the record name is optional when you create the record instance; otherwise, you have to specify the record type name. For example, both Point2D and Point have x and y labels. Because of this, the type name is needed when you create a record instance of Point2D or Point.

Example 6-23. Defining a record and creating a record
// define record
type Point2D = { x : float; y: float }
type Point = { x : float; y: float }
type Student = { Name: string; Age : decimal; }
type Person = { First : string
                Last: string }

// create record

// the record name is mandatory because the label is not unique
let point2DRecord = { Point2D.x = 1.; Point2D.y = 2. }
let pointRecord = { Point.x = 1.; Point.y = 2. }

// record name is optional because the label is unique
let studentRecord = { Name = "John"; Age = 18m; }
let personRecord = { First = "Chris"
                     Last = "Root" }

Note

When you place each label on a new line, the semicolon (;) is optional. Also, the last semicolon is always optional.

The fields in a record are immutable by default. Example 6-24 shows how to use the with keyword to make a copy of an existing record.

Example 6-24. Record copying
type Person = { First : string
                Last: string }

let personRecord = { First = "Chris"
                     Last = "Root" }

let personRecord2 = { personRecord with First = "Matt" }

Execution result

type Person =
  {First: string;
   Last: string;}
val personRecord : Person = {First = "Chris";
                                 Last = "Root";}
val personRecord2 : Person = {First = "Matt";
                                  Last = "Root";}

Note

To avoid odd compile errors, make sure First and Last are aligned correctly. I highly recommend installing and using the F# depth colorizer to assist with this.

The record fields can be mutable if they are decorated with the mutable keyword. Also, records can define member functions or properties to extend their functionality. See Example 6-25.

Example 6-25. A record with a mutable field and members
type Car =

    {
    Make : string
    Model : string
    mutable Odometer : int
    }
    member this.Drive(mile) =
        printfn "you drove another %d miles" mile
        this.Odometer <- this.Odometer + mile
    member this.CurrentMileage with get() = this.Odometer

let myCar = { Make = "Plymouth"; Model = "Neon"; Odometer = 100892 }
myCar.Drive(20)
printfn "current mileage = %d" myCar.CurrentMileage

Execution result

you drove another 20 miles
current mileage = 100912

Note

F# uses space indents to distinguish between language elements. If the member keyword in the record definition is not aligned correctly, you will get a compile error.

The record does not support default values on its fields. However, you can declare a static member with a default value and mutate its value by using the with keyword. Example 6-26 shows how to do this.

Example 6-26. Using a static member as a base template record
// define a Point2D record with originalPoint = (0,0)
type Point2D =
    { x : float; y : float }
    static member OriginalPoint =
                  { x = 0.; y = 0.}

let original = Point2D.OriginalPoint
let onePoint = { Point2D.OriginalPoint with x = 1.; y = 1.}

printfn "original point = %A 
and one point = %A" original onePoint

Execution result

original point = {x = 0.0;
 y = 0.0;}
and one point = {x = 1.0;
 y = 1.0;}

Record equality is performed by using a structural comparison. Example 6-27 shows a record-equality comparison.

Example 6-27. Record structural comparison
// define record type
type MyRecord = { X : int; mutable Y : int }

// create two record instances
let r0 = { X = 0; Y = 1 }
let r1 = { X = 0; Y = 1 }

printfn "r0 = r1 ? %A" (r0 = r1)

r1.Y <- 2
printfn "r0 = r1 ? %A" (r0 = r1)

Execution result

r0 = r1 ? true
r0 = r1 ? false

Using the CLIMutable Attribute

The default constructor is needed for many of these scenarios. By default, a record type does not generate a default constructor. If you ever try to data bind to a record, you will be blocked by this behavior. The requirement for a default constructor is not limited to XAML; it also is a requirement with serialization, WCF, and other scenarios. The CLIMutable attribute was introduced in F# 3.0 to generate a default constructor for a record. Example 6-28 shows a type named R that is decorated with the CLIMutable attribute, which will cause a default constructor and property setters to be generated, although those features are not exposed to the F# side.

Example 6-28. CLIMutable attribute

Defining a CLIMutable attribute in F# code

[<CLIMutable>]
type R =
  { X : int; Y : int }

// R2 does not have default constructor
type R2 =
  { X : int; Y : int }

C# code invoking the CLIMutable record

var x = new R();
var x2 = new R(0, 2);
var y = new R2(0, 2);
// var y2 = new R2();   //does not compile

Adding the default constructor not only enables support for XAML, but also makes the record serialization story much easier. The Azure marketplace serialization example (shown in Example 4-54) can be rewritten to use a record as shown in Example 6-29. The last line of the code uses structural comparison to make sure that the data is the same before and after the serialization.

Example 6-29. Using a record type in serialization
// define the type provider
type Demographics = Microsoft.FSharp.Data.TypeProviders.ODataService<ServiceUri =
    "https://api.datamarket.azure.com/Uk.Gov/TravelAdvisoryService/">
let ctx = Demographics.GetDataContext()

// set the credentials
ctx.Credentials <- System.Net.NetworkCredential(<liveID>, <id>)

// define a serializable record
[<CLIMutable>]
type NewsRecord = { Title : string; Summary : string }

// query the latest news
let latestNews = query {
    for n in ctx.LatestTravelNewsFromFco do
    select { NewsRecord.Title = n.Title; NewsRecord.Summary = n.Summary}
}

let news = latestNews |> Seq.toArray

// deserialize from xml
let deserialize<'T> (fileName:string) =
    let reader = System.Xml.Serialization.XmlSerializer(typeof<'T>)
    use file = new System.IO.StreamReader(fileName)
    let fileData = reader.Deserialize(file) :?> 'T
    fileData

// serialize data
let serialize data (fileName:string) =
    let writer = System.Xml.Serialization.XmlSerializer(data.GetType())
    use file = new System.IO.StreamWriter(fileName)
    writer.Serialize(file, data)

//serialize the data
serialize news "myLocalNews.xml"

// deserialize the file
let data = deserialize<NewsRecord array> "myLocalNews.xml"

// structural comparison to make sure the data is the same
let isEqual = news = data

Comparing a Record with Other Data Structures

It might be confusing as to when to choose a record type over other data types, such as struct or class. The record type is still a class, while the struct is a lightweight data structure. If you have a large number of objects allocating memory on the heap, a struct is a better choice. This is because a struct does not trigger garbage collection. Instead, a struct is allocated on the stack, which will be cleared when the function returns. In other words, if there is a place where a struct can bring more benefit, you should use a struct. Otherwise, a record is your best choice. If you need more functionality than a record provides, use a full-fledged class.

If a record is more like a class, what is the difference between a record and a class? One main difference is in how you set the initial values. The creation of a record needs an initial value to be present, and it forces the invoker to give a meaningful value explicitly. So when explicit value initialization is mandatory, a record is preferred. The class equality and record equality are different. The class uses reference equality, while the record uses structural equality. The class provides more flexible ways of initialization by making it possible to specify multiple constructors, while the record does not provide a way to add more constructors.

Working with Discriminated Unions

If the record can be viewed as a tuple with labels, the discriminated union (DU) can be viewed as a union of different types. Example 6-30 shows how to define a shape union that contains a circle, triangle, and rectangle. The possible value in the union is called a union case. The shape union has three union cases, and each case has a different type. For example, the triangle case has three double tuples.

Example 6-30. Defining a discriminated union

DU with type

type Shape =
    | Circle of double
    | Triangle of double * double * double
    | Rectangle of double * double

DU without type

type Number = OddNumber | EvenNumber

Note

If the union case is on the same line, the first pipe (|) is not mandatory.

The DU can be recursive. Just like with other recursive types, you can define one in a DU with the and keyword. A typical application of the DU recursive feature is to define a tree structure. See Example 6-31.

Example 6-31. Defining a tree structure using a DU

Defining a tree using DU

type Tree =
    | Tip of int
    | Node of int * Tree * Tree

Defining a tree with a recursive type

type Tree =
    | Tip of NodeType
    | Node of int * Tree * Tree
and NodeType =
    | NodeContent of int

As with record, the DU supports member methods and properties. Example 6-32 shows how to override the ToString method so that it shows the current type name.

Example 6-32. DU with a method
type Tree =
    | Tip of NodeType
    | Node of int * Tree * Tree
    override this.ToString() = "this is a Tree DU"

and NodeType =
    | NodeContent of int
    override this.ToString() = "this is NodeType DU"

Note

A class hierarchy can be extended at will by adding new subclasses; however, the discriminated union structure is closed and cannot be extended.

The DU introduces an interesting feature that is comparable to the class hierarchy. The Shape type shown in Example 6-30 can be viewed as an abstract base class, and the three union cases are three subclasses derived from the base class.

Example 6-33 shows how to use an interface with the DU. One interesting point in this example is that the different union case types are now unified by the interface. Different DU instances now can be passed into a function that requires the IShape interface.

Example 6-33. DU with an interface
// define an interface
type IShape =
    abstract Area : double with get

// DU with interface
type ShapeWithInterface =
    | Circle of double
    | Triangle of double * double * double
    | Rectangle of double * double
    interface IShape
        with member this.Area =
                match this with
                | Circle(r) -> r*r* System.Math.PI
                | Triangle(a, b, c) ->
                    let s = (a + b + c)/2.
                    sqrt(s * (s - a) * (s - b) * (s - c))
                | Rectangle(a, b) -> a * b
    override this.ToString() = "This is a ShapeWithInterface type"

let getArea2 (shape:IShape) = shape.Area
getArea2 (Circle(4.))

Example 6-34 uses DU implement a binary tree structure and traverses it with Seq.unfold. The algorithm uses a queue to hold the unvisited node.

Example 6-34. Defining a binary tree using DU and performing a layer traversal
type NodeType = int

type BinaryTree =
    | Nil
    | Node of NodeType * BinaryTree * BinaryTree

let tree = Node(5,
                Node(42,
                     Node(3, Nil, Nil),
                     Node(2, Nil, Nil)),
                Node(4,
                     Node(13,
                          Node(14, Nil, Nil),
                          Node(16, Nil, Nil)),
                     Node(12,
                          Node(15, Nil, Nil),
                          Node(21,
                               Node(22, Nil, Nil),
                               Nil))))

let layerTraverse state =
    match state with
    | [] -> None
    | h::t ->
        match h with
        | Nil -> None
        | Node(v, Nil, Nil) -> Some (v, t)
        | Node(v, subTree, Nil)
        | Node(v, Nil, subTree) -> Some (v, t@[subTree])
        | Node(v, l, r) -> Some (v, t@[l;r])

[tree]
|> Seq.unfold layerTraverse
|> Seq.toList

Note

If you replace t@[subTree] and t@[l;r] with [subTree]@t and [l;r]@t, the algorithm becomes a pre-order traversal.

Working with Comparison Operations for a Record, Tuple, and DU

If I ask you to tell me the result of comparing two tuples such as (1,1) < (2,1), you might think this comparison does not make sense because these two tuples are two different physical objects. The comparison should not be allowed. Actually, this comparison returns TRUE. The code and execution result from F# Interactive (FSI) is shown in Example 6-35.

Example 6-35. Comparing two tuples
> (1, 1) < (2, 1);;
val it : bool = true

F# uses structural comparison, structural hashing, and structural equality on records, tuples, and DUs. Example 6-36 shows that (1+2, 1) is equal to (3, 1) and that they have the same hash value.

Example 6-36. Compare (1+2,1) and (3,1)
> (1 + 2, 1) = (3, 1);;
val it : bool = true
> hash (1+2,1) = hash (3,1);;
val it : bool = true

Note

The hash function in the preceding example is used to get the hash value of the tuple.

Example 6-37 shows the record, tuple, and DU comparison. Records are compared on each pair of fields in declaration order and then return the first non-zero result. DU compares first on the index of the union cases according to the declaration order and then compares the union value. The last comparison, which compares Case4 and Case3, returns Case4 < Case3 because Case3 is declared before Case4.

Example 6-37. Compare a record, tuple, and DU

Record, tuple, and DU comparison code

//define a record
type MyRecord = { X : int; Y : string }

// define a tuple type
type MyTuple = int*string

// define a DU type
type MyDU =
    | Case1 of int
    | Case2 of string
    | Case3 of MyRecord
    | Case4 of MyTuple

// define a comparison function
let inline compare x y =
    if x > y then
        printfn "%A > %A" x y
    elif x=y then
        printfn "%A = %A" x y
    else
        printfn "%A < %A" x y

// create records
let record1 = { X = 1; Y = "1" }
let record2 = { X = 1; Y = "2" }
let record3 = { X = 2; Y = "1" }
let record4 = { X = 1; Y = "1" }

compare record1 record2
compare record1 record3
compare record2 record3
compare record1 record4

// create tuples
let tuple1 : MyTuple = 1, "1"
let tuple2 : MyTuple = 1, "2"
let tuple3 : MyTuple = 2, "1"
let tuple4 : MyTuple = 1, "1"

compare tuple1 tuple2
compare tuple1 tuple3
compare tuple2 tuple3
compare tuple1 tuple4

// create DU Case1 instances
let du1 = Case1(1)
let du2 = Case1(2)
let du3 = Case1(1)

compare du1 du2
compare du1 du3
compare du2 du3

// create DU Case3 instances
let duRecord1 = Case3(record1)
let duRecord2 = Case3(record2)
let duRecord3 = Case3(record3)
let duRecord4 = Case3(record4)

compare duRecord1 duRecord2
compare duRecord1 duRecord3
compare duRecord2 duRecord3
compare duRecord1 duRecord4

// create DU Case4 instances
let duTuple1 = Case4(tuple1)
let duTuple2 = Case4(tuple2)
let duTuple3 = Case4(tuple3)
let duTuple4 = Case4(tuple4)

compare duTuple1 duTuple2
compare duTuple1 duTuple3
compare duTuple2 duTuple3
compare duTuple1 duTuple4

// compare Case4 with Case3
compare duTuple1 duRecord1

Execution result from the record, tuple, and DU comparison code

{X = 1; Y = "1";} < {X = 1; Y = "2";}
{X = 1; Y = "1";} < {X = 2; Y = "1";}
{X = 1; Y = "2";} < {X = 2; Y = "1";}
{X = 1; Y = "1";} = {X = 1; Y = "1";}

(1, "1") < (1, "2")
(1, "1") < (2, "1")
(1, "2") < (2, "1")
(1, "1") = (1, "1")

Case1 1 < Case1 2
Case1 1 = Case1 1
Case1 2 > Case1 1

Case3 {X = 1; Y = "1";} < Case3 {X = 1; Y = "2";}
Case3 {X = 1; Y = "1";} < Case3 {X = 2; Y = "1";}
Case3 {X = 1; Y = "2";} < Case3 {X = 2; Y = "1";}
Case3 {X = 1; Y = "1";} = Case3 {X = 1; Y = "1";}

Case4 (1, "1") < Case4 (1, "2")
Case4 (1, "1") < Case4 (2, "1")
Case4 (1, "2") < Case4 (2, "1")
Case4 (1, "1") = Case4 (1, "1")
Case4 (1, "1") > Case3 {X = 1; Y = "1";}

Note

Some returns and extra spaces are removed to increase the readability.

Using Pattern Matching

We used pattern matching in various examples in Chapter 1, such as in Example 1-10. At that time, your understanding of pattern matching was that it is something similar to a C# switch statement. Although this is true, pattern matching is much more powerful.

Let’s start by defining patterns. Patterns are simply rules used to transform data. According to this definition, you can use pattern matching to match input data with some patterns and trigger corresponding actions, such as actions that transform data. What’s more, the pattern also provides a way to decompose input data. Example 6-38 uses pattern matching to rewrite the odd-number program from Chapter 1. The transform function transformEvenToZero converts all even numbers to zero and then sums up the elements in the transformed sequence.

Pattern matching comes in several flavors. Example 6-38 is the simplest pattern-matching example; it matches the input data against a constant value. This is called a constant pattern.

Example 6-38. Pattern-matching sample
let isEvenNumber x = x % 2 = 0
let transformEvenToZero x =
    match isEvenNumber x with
    | true -> 0
    | false -> x

// sum of odd number from 0 to 100
let r = seq {0..100} |> Seq.sumBy transformEvenToZero

Before I introduce more complex patterns, I should briefly introduce the function keyword. Example 6-39 shows how to use the function keyword to simplify the match expression, which is the match x with in the sample code. The function keyword implicitly appends a parameter to the containing function.

Example 6-39. Using the function keyword

Using match in a function

let matchSample x =
    match x with
        | 1 -> printfn "this is one"
        | 2 -> printfn "this is two"
        | 3 -> printfn "this is three"
        | _ -> printfn "other value"

Using function to represent a match

let functionSample =
    function
        | 1 -> printfn "this is one"
        | 2 -> printfn "this is two"
        | 3 -> printfn "this is three"
        | _ -> printfn "other value"

Using the Tuple Pattern

A more complex pattern is the tuple pattern, which is used to match a value pair. Example 6-40 implements the logical OR function. The first version lists all the values in the form of a tuple. The OR function returns FALSE only when the two inputs are both FALSE. The simplified version implements this idea by using underscore (_), which can match anything. Note that the tuple can have more than two values. Example 1-65 uses tuple matching to decompose a triple element in the list.

Example 6-40. Tuple pattern

OR function with tuple pattern matching

let orFunction (x, y) =
    match x, y with
        | true, true -> true
        | true, false -> true
        | false, true -> true
        | false, false -> false

Simplified OR function with tuple pattern matching

let orFunction2 (x, y) =
    match x, y with
        | false, false -> false
        | _, _ -> true

Note

The _, _ can be further simplified as _. The _, _ is used to match any tuple value, while _ is used to match any value.

Example 6-41 shows how to use pattern matching to work with a tuple that is returned from the TryGetValue function.

Example 6-41. Using the tuple pattern to work with the Dictionary.TryGetValue function
let f x =
    let dict = System.Collections.Generic.Dictionary()
    dict.[0] <- "0"
    dict.[1] <- "1"

    match dict.TryGetValue x with
    | true, v -> sprintf "found %A mapped to %A" x v
    | false, _ -> "cannot find"

printfn "%A" (f 0)
printfn "%A" (f 1)
printfn "%A" (f 2)

Execution result

"found 0 mapped to "0""
"found 1 mapped to "1""
"cannot find"

One nice feature about pattern matching is that F# can generate a warning if one possible pattern is missing in the match expression. For example, when sending the code in Example 6-42 to FSI, F# warns the user about this problem.

Example 6-42. Missing one pattern in the pattern-matching expression
>     let orFunction (x, y) =
        match x, y with
        | true, true -> true
        //| true, false -> true // miss one pattern
        | false, true -> true
        | false, false -> false;;

          match x, y with
  --------------^^^^

stdin(2,15): warning FS0025: Incomplete pattern matches on this expression. For example,
the
 value '(_,false)' may indicate a case not covered by the pattern(s).

Using the List and Array Patterns

Like a tuple, patterns can also decompose a list or array into values. Example 6-43 decomposes an array and gets the array length.

Example 6-43. An array pattern
let arrayLength array =
    match array with
    | [| |] -> 0
    | [| _ |] -> 1
    | [| _; _ |] -> 2
    | [| _; _; _ |] -> 3
    | _ -> Array.length array

Example 6-44 shows how to use a recursive function to decompose a list into a head and tail and get the length of that list. The con operator (::) decomposes the list into a head part and a tail part.

Example 6-44. An example of the con operator pattern
let rec listLength list =
    match list with
    | head :: tail -> 1 + (listLength tail)
    | [] -> 0

Note

The preceding code can cause a stack overflow because it is not tail recursive. The following code provides a version that is tail recursive:

let rec listLength acc list =
    match list with
    | head :: tail ->
        listLength (acc + 1) tail
    | [] -> acc

There are some interesting examples in Example 6-45 for the list pattern matching. The first code snippet matches the list and decomposes it into two elements, and it contains a list that represents the tail. The second code snippet checks the element type and converts the element back to the type, as it was prior to being boxed.

Example 6-45. Pattern matching for list
let list = [1;2;3;4;]
match list with
| h0::h1::t -> printfn "two elements %A %A and tail %A" h0 h1 t
| _ -> ()

let objList = [box(1); box("a"); box('c')]
match objList with
| [:? int as i;
   :? string as str;
   :? char as ch] ->
       printfn "values are %A %A %A" i str ch
| _ -> ()

Note

The code :? Int as i is a type of pattern match that will be discussed later in this section.

Using the NULL Pattern

As I mentioned in the “Working with Options” section earlier in the chapter, an option is generally a better choice than using a NULL value. The first code snippet shown in Example 6-46 shows a match expression that uses the option type.

Another option is to match against a NULL. This is known as the NULL pattern, and an example of this is shown in the second code snippet in Example 6-46. An important application of the NULL pattern is to convert to a NULL from an option or vice versa.

Example 6-46. NULL pattern

Using an identifier pattern to test and extract an option value

let extractOption x =
    match x with
    | Some a -> printfn "option has value %A" a
    | None -> printfn "option has no value"

Using a NULL pattern to convert a nullable .NET value to an F# option

let toOption x =
    match x with
    | null -> None
    | _ -> Some x

Using the Record and Identifier Patterns

Example 6-47 shows how to use the identifier pattern to decompose a discriminated union. The second code snippet shows how to use the record pattern to decompose a record.

Example 6-47. Record and identifier patterns

Using an identifier pattern for a discriminated union

type Shape =
    | Circle of double
    | Triangle of double * double * double
    | Rectangle of double * double

let getArea shape =
    match shape with
    | Circle(r) -> r * r * System.Math.PI
    | Triangle(a, b, c) ->
        let s = (a + b + c) / 2.
        sqrt s * (s - a) * (s - b) * (s - c)
    | Rectangle(a, b) -> a * b

Using a record pattern

type Point2D = { x : float; y: float }

let isOnXAxis p =
    match p with
    | { Point2D.x = 0.; Point2D.y = _ } -> true
    | _ -> false

F# uses the type system to help prevent bugs and improve readability. Example 6-48 uses the Currency DU type to convert the decimal type to either the USD or CAD type. The sample extends the Currency DU type by adding two operators.

Example 6-48. Using DU and pattern matching in a currency calculation
type Currency =
    | USD of decimal
    | CAD of decimal

    // extend the DU type with the + and - operators
    with
        static member (+) (x:Currency, y:Currency) =
            match x, y with
                | USD(a), USD(b) -> USD(a+b)
                | CAD(a), CAD(b) -> CAD(a+b)
                | _ -> failwith "cannot add different unit value"

        static member (-) (x:Currency, y:Currency) =
            match x,y with
                | USD(a), USD(b) -> USD(a - b)
                | CAD(a), CAD(b) -> CAD(a - b)
                | _ -> failwith "cannot add different unit value"

// perform currency add and minus operations
try
    printfn "result = %A" (USD(1m) + USD(11m))
    printfn "result = %A" (CAD(1m) - CAD(11m))

    // this line generates an exception
    printfn "result = %A" (USD(100m) - CAD(10m))
with
    | _ as e -> printfn "%A" e

Execution result

result = USD 12M
result = CAD -10M
System.Exception: cannot add different unit value

Note

Unlike the unit-of-measure approach, this approach adds runtime overhead.

The pattern matching for a record needs to make sure the record is decomposed when performing the match. The match statement in Example 6-49 matches any record with the listed records even when the type differs. The correct way to match the record is shown in Example 6-50.

Example 6-49. Match Record Sample I
type MyRecord = { X : int list; Y : string }
type MyRecord2 = { XX : int; YY : string; Z : int }

let rr = { X = [100; 200; 300]; Y = "aa" }
let r0 = { X = [100; 200; 3]; Y = "aa" }
let r1 = { XX = 1; YY = "bb"; Z = 9 }

match r1 with
    | rr -> "a"
    | _ -> "b"

Note

A warning “This rule will never be matched” will be generated on the last line.

Example 6-50. Match Record Sample II
type MyRecord = { X : int list; Y : string }
type MyRecord2 = { XX : int; YY : string; Z : int }

let r1 = { XX = 1; YY = "bb"; Z = 9 }
let r2 = { XX = 2; YY = "r2"; Z = 10 }

match r2 with
    | {YY = "bb"} -> """r1 is matched with YY="bb" """
    | _ -> "others"

Working with the And/Or Pattern and Pattern Grouping

The And/Or pattern combines two patterns with an AND/OR relationship. Example 6-51 tests whether the point is on the axis using the AND pattern. All the patterns in the match list are OR-ed together; therefore, the OR pattern sample can be rewritten using pattern grouping, as shown in Example 6-52.

Example 6-51. And/Or pattern used to test a point

And pattern used to test a point

let testPoint2 point =
    match point with
    | x, y & 0, 0 -> "original point"
    | x, y & 0, _ -> "on x axis"
    | x, y & _, 0 -> "on y axis"
    | _ -> "other"

Or pattern

let testPoint3 point =
    match point with
    | x, y & (0, 0) -> "original point"
    | x, y & (0, _ | _, 0) -> "on axis"
    | _ -> "other"
Example 6-52. Pattern grouping
let testPoint4 point =
    match point with
    | (x, y) & (0, 0) -> "original point"
    | (x, y) & (0, _) | (x, y) & (_, 0) -> "on axis"
    | _ -> "other"

Using Variable Patterns and the when Guard

Up until now, the patterns you’ve looked at have not handled relationships among decomposed data items. If you want to know whether the decomposed data meets certain criteria, you can use the when guard. Example 6-53 decides whether or not the (x,y) point is on the line f(x) = x.

Example 6-53. Point relative to line f(x) = x
let testPoint tuple =
    match tuple with
    | x, y when x = y -> "on the line"
    | x, y when x > y -> "below the line"
    | _ -> "up the line"

Note

F# does not consider the when guard’s result to decide whether the match expression is complete. Therefore, even if you changed the last pattern in Example 6-53 to x, y when x < y, the compiler would still think that the match expression was not complete and generate a warning message.

Note

Another option is to use active patterns, which will be introduced in the next section.

Using the Type Pattern and as pattern

When the input parameter is decomposed to different parts, the different parts might need a name for future reference. The as pattern is introduced for this purpose. Example 6-54 shows how to write a type check. Instead of using an is operator, as you might do in C#, F# uses :? operator to check the type.

Example 6-54. Type pattern example
let testType (x:obj) =
    match x with
    | :? float as f -> printfn "float value %f" f
    | :? int as i -> printfn "int value %d" i
    | _ -> printfn "type cannot process"

Note

The input parameter must be the superclass of the type in the pattern. In Example 6-54, float and int are derived from obj.

Using the Choice Helper Type

There is a helper type used to encapsulating several choices called Choice. You can use Choice when you have between 2 and 7 choices. Example 6-55 shows how to initialize a dual-choice structure and how to match against the structure.

Example 6-55. Choice example
open System

// initialize a Choice<int,int>
let a : Choice<int, int> = Choice2Of2 1

// match against the choice
let f x =
    match x with
        | Choice1Of2 e -> printfn "value is %A" e
        | Choice2Of2 e -> printfn "value is %A" e

f a

Execution result

value is 1

Note

Unlike the C++ union, Choice does not share the same memory. Setting Choice2of2 makes the execution go to the second pattern.

Working with Active Patterns

As you’ve already seen, pattern matching introduces a way to decompose data into small pieces. However, the decomposition process cracks the data only by its structure. For example, a tuple is decomposed only into a value pair. If instead you need to check the data, a when guard is needed. But when the checking logic gets complex, the when guard can have piles of conditions and, as a consequence, make the code hard to maintain.

You can use the active pattern to resolve this issue. It is a special function in itself. By evaluating this function, the data is decomposed, segmented by an active pattern function, and associated with the active pattern name. In other words, the active pattern is a customized way to decompose and label data.

Using Single-Case Active Patterns

The simplest form of an active pattern is the single-case active pattern. Example 6-56 defines an active pattern named Reminder2. When using the active pattern, the expected value must be passed in. For example, the 0 in Remainder2 0 is the expected value. If the input value can make the active pattern yield the expected value, this union case is matched to the pattern Remainder2.

Example 6-56. Defining a single-case active pattern
// define a single-case active pattern
let (| Remainder2 |) x = x % 2

// use Remainder pattern to check even/odd number
let checkNumber x =
    match x with
        | Remainder2 0 -> "even number"
        | Remainder2 1 -> "odd number"

Note

Active patterns are not used in completeness evaluations. Although the preceding pattern does cover all the cases, a warning will still be generated. The following code uses a wildcard to eliminate the warning:

// define a single-case active pattern
let (| Remainder2 |) x = x%2

// use Remainder pattern to check even/odd number
let checkNumber x =
    match x with
        | Remainder2 0 -> "even number"
        | Remainder2 1 -> "odd number"
        | _ -> "other number"

In the final analysis, the active pattern is a special function. Example 6-57 shows that a single-case active pattern can be used to make a safe Dictionary object.

Example 6-57. Using a single-case active pattern as a function
open System.Collections.Generic

let (|SafeDict|) (d : Dictionary<_, _>) =
    if d = null then Dictionary<_, _>()
    else d

let tryFind (SafeDict dic) key =
    if dic.ContainsKey key then
        Some dic.[key]
    else None

You can use the single-case active pattern to categorize the data and clean up the code, but the value that it provides falls short in more complex scenarios.

Using Partial-Case Active Patterns

The partial-case active pattern is an enhanced version of the single-case active pattern. Partial-case active patterns identify only part of the incoming data and leave the rest of data untouched (or unlabeled). The partial-case active pattern must return an option type. The pattern will be ignored when None is returned. Example 6-58 defines two partial-case active patterns. If the variable x is less than 10, the first pattern is triggered.

Example 6-58. Partial-case active pattern
// define partial active patterns
let (|LessThan10|_|) x = if x < 10 then Some x else None
let (|Btw10And20|_|) x = if x >= 10 && x < 20 then Some x else None

let checkNumber2 x =
    match x with
    | LessThan10 a -> printfn "less than 10, the value is %d" a
    | Btw10And20 a -> printfn "between 10 and 20, the value is %d" a
    | _ -> printfn "that's a big number %d " x

Unlike the single-case active pattern, the variable succeeding the active pattern is bound to the function’s return value. For example, the a in LessThan10 a is used to hold the values smaller than 10.

Using Multicase Active Patterns

If you view the partial-case active pattern as something used for decomposing the input data into two portions, you can consider the multicase active pattern as a pattern used to decompose data into multiple portions. Example 6-59 is used to determine the quarter of the year in which the provided date falls.

Example 6-59. Multicase active pattern example
let (|FirstQuarter|SecondQuarter|ThirdQuarter|FourthQuarter|) (date:System.DateTime) =
    let month = date.Month
    match month with
    | 1 | 2 | 3 -> FirstQuarter month
    | 4 | 5 | 6 -> SecondQuarter month
    | 7 | 8 | 9  -> ThirdQuarter month
    | _ -> FourthQuarter month

let newYearResolution date =
    match date with
    | FirstQuarter _-> printfn "New Year resolution: lose 10 lbs this year!"
    | SecondQuarter _-> printfn "beef is good for summer BBQ?"
    | ThirdQuarter _ -> printfn "maybe I should diet?"
    | FourthQuarter _-> printfn "Mom's apple pie is wonderful!"

Note

The underscore (_) in the second pattern match instructs the F# compiler to ignore the returned value from the active pattern.

Note

Active patterns cannot return more than seven possibilities. So the code let (|A|B|C|D|E|F|G|H|I|J|K|) x = x will generate a compile error.

Using Parameterized Active Patterns

The active pattern is a special function. If it is a function, can it accept parameters? The answer is yes. Parameters for an active pattern can be included immediately after the active pattern label, but they must appear before the active pattern return value. Example 6-60 shows how to parse a phone number and return all numbers in that phone number. The pattern string is the parameter and the out variable is used to store the return value from the active pattern.

Example 6-60. Parameterized active pattern example
open System.Text.RegularExpressions

// define the parameterized active pattern
let (|RegexMatch|_|) (pattern:string) (input:string) =
    let regex = Regex(pattern).Match(input)
    if regex.Success then Some(List.tail [ for x in regex.Groups -> x.Value])
    else None

let parsePhoneNumber str =
    match str with
    | RegexMatch "(d{3})-(d{3})-(d{4})" out -> System.String.Join("", out)
    | RegexMatch "(d{3})-(d{3})(d{4})" out ->  System.String.Join("", out)
    | _ -> "Not supported format"

// two statements below show 4251231234
parsePhoneNumber "425-123-1234"
parsePhoneNumber "425-1231234"

// this format does not parse and shows "Not supported format"
parsePhoneNumber "(425)123-1234"

Tip

The active pattern is a special function you can use to create a higher order function and use it in a match expression. The parsePhoneNumber function can be rewritten as follows:

let (|RegexMath2|_|) = (|RegexMatch|_|) "(d{3})-(d{3})-(d{4})"
let parsePhoneNumber str =
    match str with
    | RegexMath2 out -> System.String.Join("", out)
    | RegexMath2 out ->  System.String.Join("", out)
    | _ -> "Not supported format"

Example 6-61 shows how to use the AND pattern with the parameterized active pattern. The sample code is checked if the number is divisible by 6.

Example 6-61. Parameterized active pattern with AND pattern example
let (|Divisible|_|) x y=
    if y % x = 0 then
        Some Divisible
    else
        None

let f2 = function
    |  Divisible 2 & Divisible 3 -> "divisible by 6"
    | _ -> "other"

f2 6

You might be wondering why I am introducing the active pattern if it is just a function in itself. If you combine the active pattern and pattern matching, the value of active pattern can be seen. When the active patterns are put into the match statement, the match statement can make sure all the cases (data segments) are processes. If there is one case missing, there will be a compile-time warning generated.

Working with Exceptions

As a C# developer, you’re probably familiar with exceptions. In C#, exceptions can be found all over the place. The normal execution will be interrupted when an exception happens. This feature is used as a way to notify you that something is wrong. If you find that typing System.Exception is overly repetitive, you are not alone. F# actually introduces an abbreviation named exn for the System.Exception type. Example 6-62 shows how exn is defined.

Example 6-62. The exn abbreviation for System.Exception
type exn = System.Exception

Catching Exceptions

Example 6-63 shows how to catch an exception in F#.

Example 6-63. Using try...with to handle an exception
open System

let exceptionCatch() =
    try
        let a = 3 / 0     //divide by zero exception
        printfn "%d" a
    with
    | :? DivideByZeroException -> printfn "divided by zero"

exceptionCatch()

Note

Although the try...with statement is similar to the match statement, it does not check for pattern completeness. As with C#, if the exception is not caught, it will bubble up.

In addition to try...with, F# also supports try...finally. See Example 6-64. F# does not provide try...with...finally. If finally is used to release resources, the use statement can solve this without involving finally. If you require functionality similar to the try...catch...finally functionality in C#, you can nest try...with within a try...finally to achieve the same functionality, as shown in Example 6-65.

Example 6-64. Using try...finally
// try...finally sample
try
    <some exception thrown>
finally
    printfn "ignore the exception and perform some clean up"
Example 6-65. Implementing try...with...finally
// try...catch...finally
try
    try
        <some exception thrown>
    with _ -> printfn "catch the exception"
finally
    printfn "finally section executing..."

Throwing Exceptions

F# provides a shortcut to throw exceptions by using failwith or failwithf. Both of these functions generate a System.Exception. The failwith function takes a string as a parameter, and failwithf takes a string format, like printf, to generate a more sophisticated error message. The sample code is shown in Example 6-66. Note that this listing contains a compile warning that can be ignored.

Example 6-66. The failwith and failwithf examples
open System

try
    failwith "this is an exception"
with :? Exception as e -> printfn "the exception msg is %s" e.Message

try
    failwithf "this is an exception generated at %A" DateTime.Now
with _  as e -> printfn "the exception msg is %s" e.Message

Note

The last line can also written as with e -> printfn “the exception msg is %s” e.Message

C# has a throw keyword that is used to throw an exception. What is the equivalent in F#? In F#, the raise and reraise functions can be used. Example 6-67 shows how to raise a System.Exception and how to throw an exception in the with section.

Example 6-67. The raise exception
open System

try
    raise (Exception("raise exception"))
with _ -> printfn "catch exception"

try
    try
        raise (Exception("raise exception"))
    with _ -> printfn "catch exception"; reraise()
with _ -> printfn "catch reraise exception"

In addition to the failwith and reraise functions, F# provides three other shortcut functions to raise common argument exceptions. Example 6-68 shows how to use these three shortcut functions:

  • An invalid argument exception is thrown by using invalidArg with two string type parameters.

  • An invalid operation exception is thrown by using invalidOp with one string type parameter.

  • A Null argument exception is thrown by using nullArg with one string type parameter.

Example 6-68. The nullArg, invalidArg, and invalidOp functions
let demoInvalidArgumentAndOperation arg0 arg1 =
    if arg0 = null then
        nullArg "arg0 is null"
    if arg1 = System.String.Empty then
        invalidArg "arg1" "arg1 is empty string"
    invalidOp "sorry, this is an invalid operation"

Defining Exceptions

F# supports using class and inheritance to define an exception. It also has a shortcut way of defining an exception. Example 6-69 defines the MyException type. If you are curious about how to access the customized fields defined in the MyException type, Example 6-70 provides an example.

Example 6-69. Defining an exception
//define an exception type with int*int tuple
exception MyException of int * int

try
    raise (MyException(1, 2))
with _ as e -> printfn "%A" e
Example 6-70. Accessing customized fields in the exception
//define an exception type with int*int tuple
exception MyException of int * int

try
    raise (MyException(1, 2))
with :? MyException as e -> printfn "exception data is %d and %d" e.Data0 e.Data1

Working with a Generic Invoke Function

The generic invoke (GI) function is a static member constraint function. It is used to invoke a member function on a type that has a specific signature. A sample GI function is shown in Example 6-71. The functionA function requires that the parameter be a type with a member method whose name is MyMethod and that has a function signature of unit->int. The GI function takes at least one parameter, which is the object x. The other several samples in Example 6-71 demonstrate how to pass additional parameters into the GI function, such as a tuple. In the sample code, there is type information for the parameter, which is optional. This type information can help you identify what parameters should be passed into the GI function. There is an interesting keyword named inline in the function, which makes the restriction happen. I will discuss it in the “Using the inline Function” section.

Example 6-71. Generic invoke (GI) function example
// GI function takes one parameter
let inline functionA (x : ^T) = (^T : (member MyMethod : unit -> int) x)

// GI function takes two parameters
let inline functionB (x : ^T) str =
  (^T : (member CanConnectWithConnectString : string -> int)
    (x, str))

// GI function takes three parameters
let inline functionC (x : ^T) a b = (^T : (member Add : int -> int -> int) (x, a, b))

// GI function takes a tuple as a parameter
let inline functionD (x : ^T) a b = (^T : (member Add : int * int -> int) (x, a, b))

// GI function takes a tuple and another value as parameters
// c is float type that will go into MyFunction's second parameter
let inline functionE (x : ^T) a b (c:float) =
  (^T : (member MyFunction : int * int -> float -> int)
    (x, a, b, c))

The GI function is a powerful weapon you can use to crack the boundary of encapsulation formed by a class. It requires only that the signature of the methods match. If you ponder this feature more, you can see how this could be something significant. And you are right. The GI function is the fundamental building block for one of our design patterns that was discussed in Chapter 3. The other application is to use this function in the library with the inline keyword, which will be discussed in the next section.

Using the inline function

The inline keyword changes the type resolution. Example 6-72 shows how to use the inline keyword to change variable x’s inferred type to any type. This makes it so that x can be a float as well as an int.

Example 6-72. The inline keyword affects the type infererence
let F x = float x + 1.0
let inline F_inline x = float x + 1.0

Execution result in FSI

val F : int -> float
val inline F_inline :
   ^a -> float when  ^a : (static member op_Explicit :  ^a -> float)

The inline keyword can be applied to functions at the top level, at the module level, or to the method in the class. See Example 6-73.

Example 6-73. The inline keyword
let inline f x = x + 1
type InlineSample() =
    member inline this.F x = x + 1
    static member inline F x = x + 1

The inline keyword optimizes the code, but this should be the last item on your optimization to-do list.

Working with Asynchronous and Parallel Workflows

Modern computers are shipped with multiple cores. This means they contain multiple, independent processing units, which enable them to process several tasks at the same time. As a developer, this seems attractive, but creating programs that use these cores actually increases complexity and the risk of introducing bugs. In this section, I first cover the basic concept of threads and how to program with threads in F#. I then move to another one of F#’s unique features, named asynchronous workflows, which makes working with multiple cores much easier and safer.

Using Threads

According to the definition at Wikipedia—http://en.wikipedia.org/wiki/Thread_(computing)—a thread is the smallest unit of processing that can be scheduled by an operating system. Different operating systems can have different thread and process implementations, but usually a thread is contained inside a process. Threads share the memory heap. So the spawned threads in a process can communicate by writing to this shared memory.

Example 6-74 demonstrates how to create a thread and print out the current time and managed thread ID. The thread uses the Sleep method to pause the execution for a certain time—in the sample code, it is 1000 milliseconds. During this pause time, the operating system can schedule other threads to execute.

Example 6-74. Spawning a thread
open System.Threading
open System

// define a thread function body
let thread() =
    [1..5]
    |> Seq.iter (fun _ -> Thread.Sleep 1000;
                              printfn "%A from thread %d"
                                DateTime.Now
                                Thread.CurrentThread.ManagedThreadId)

// create a new thread
let spawn() =
    let thread = Thread thread
    thread.Start()

spawn()
spawn()

Note

The spawn function uses the variable shadowing feature. Look at the first line of code in the spawn function: let thread = Thread(thread). The first thread is a System.Threading.Thread type, while the second thread in Thread(thread) is a function.

When dealing with multiple threads in C#, the most commonly used keyword is lock. It marks a code block with a mutually-exclusive lock. F# also provides a lock feature, but it is in the form of a function, as shown in Example 6-75.

Example 6-75. The lock function

C# lock

lock (o)
{
    myCode();
}

F# lock

lock o (fun () ->
              SomeCode()
         )

Note

The lock function can take only a reference type, which means you cannot use a struct or int. In C#, you can use an object as a syncRoot, while a reference cell in F# can serve this purpose without any problem. See Example 6-76.

Example 6-76 demonstrates how to lock a long-running thread execution and make sure that it does not get interrupted during the execution.

Example 6-76. Using lock in a long thread execution
open System
open System.Threading

let synRoot = ref 0
let presenterFunction() =
    let id = Thread.CurrentThread.ManagedThreadId
    printfn "I (%d) starting, please do not interrupt..." id
    Thread.Sleep 1000
    printfn "I (%d) finished, thank you!" id

let longTalk () =
    lock(synRoot) (fun () -> presenterFunction())

ThreadPool.QueueUserWorkItem(fun _ -> longTalk()) |> ignore
ThreadPool.QueueUserWorkItem(fun _ -> longTalk()) |> ignore
ThreadPool.QueueUserWorkItem(fun _ -> longTalk()) |> ignore

Note

In F#, the printfn function’s printout result can be interleaving in a multithreaded environment. You can either use the lock function to make it thread-safe or use Console.WriteLine instead.

Using Asynchronous Workflows

If you ever read an article about the advantages of F#, you likely found some information about the ease of parallel programming and usually some association to the Async.Parallel method. If you want to do parallel/asynchronous programming, F# asynchronous workflows provide the cleanest way to do it.

Let’s start by solving a simple problem with an asynchronous workflow: checking whether a given sequence is in an increasing order. The algorithm is simple. Multiple threads are launched to cut the incoming sequence into a number of two-element small lists. If the first element is smaller than the second element, you increase a shared variable by one. At end of the execution, if the shared variable value is the sequence length minus one, the sequence is in an increasing order. The code is shown in Example 6-77. Note that this algorithm and code serve only as a sample for demonstrating the asynchronous workflow. I do not intend to use this code to discuss the performance between single-threaded and multithreaded programming.

According to the algorithm, the first problem that needs to be solved is how to lock a variable so that the write operation is thread-safe. You can use the lock function presented in Example 6-75 to solve this problem. The real interesting part starts with the use of async, which specifies that an asynchronous workflow can be executed in parallel. The function takes a two-element array. If the first element x is smaller than the second element y, the shared variable is increased by one.

You use the Seq.windowed function to create the two-element sequences that will be used as input. Async.Parallel is then used to make sure that these threads execute in parallel and wait to join the result when executing Async.RunSynchronously. This is known as the Fork/Join pattern.

Example 6-77. An asynchronous workflow sample that is used to check an increasing sequence
type AsyncSample(l:seq<int>) =
    member this.IsIncreasing() =
        // declare a reference cell to hold the shared variable
        let num = ref 0

        // safely increase the shared variable
        let inc () = lock(num) ( fun _ -> num := (!num) + 1 )

        // make the sequence into a sequence containing a number of small two-element
sequences
        let windowedList = l |> Seq.windowed 2

        // the compare function
        let compare s =
            async {
                let list = List.ofSeq s
                let [ x; y ] = list   // the two-element list can be assigned to x and y

                // if x < y, then increase the shared variable; otherwise, do nothing
                if x < y then inc()
                else ()
                }

        // split the sequence into small two-element sequences and execute
        // the function in parallel
        let compute =
            windowedList
            |> Seq.map compare  // can think as map seq of data into seq of functions
            |> Async.Parallel
            |> Async.RunSynchronously

        // compare the shared variable with sequence length
        (!num) + 1 = Seq.length l

Note

The segment let [x; y] = list generates a warning message. The pattern matching at compile time does not know that the list is a two-element list, so the F# compiler generates a warning. This warning can be ignored in this scenario.

When debugging a multithreaded program, one of the common techniques you resort to using for assistance is the printfn function. F# printfn uses a different technique than Console.WriteLine. As previously mentioned, it does not lock the output stream. As a consequence, a call to printfn without using the lock function yields interleaving information. Example 6-78 demonstrates how to write a printfn with a lock.

Example 6-78. Using printfn with the lock function
let syncRoot = ref 0
lock(syncRoot) ( fun _ ->  printfn "not interleaving printfn" )

You might be wondering how this sample can be made general enough so that it can test both decreasing and increasing cases. The answer is in Example 6-79. Some readers might still be surprised to see that “<” can be passed in as an argument to a function. This implies two important points that distinguish F# from C#. The “<” is a function that takes two parameters and returns a Boolean result. Because F# is a functional-first language, this function can be passed into another function. By taking the function as a parameter, the IsAll function becomes a higher-order function.

Example 6-79. Asynchronous workflow sample with a generalized function interface
type AsyncSample(l:seq<int>) =
    member this.AreAll(compareFunction) =
        // declare a reference cell to hold the shared variable
        let num = ref 0

        // safely increase the shared variable
        let inc () = lock(num) ( fun _ -> num := (!num) + 1 )

        // make the sequence into a sequence containing a number of small two-element
sequences
        let windowedList = l |> Seq.windowed 2

        // the compare function
        let compare s =
            async {
                let list = List.ofSeq s
                let [ x; y ] = list   // the two-element list can be assigned to x and y
                if compareFunction x y then inc()
                else ()
                }

        // split the sequence into small two-element sequences and execute
        // the function in parallel
        let compute =
            windowedList
            |> Seq.map compare  // can think as map seq of data into seq of functions
            |> Async.Parallel
            |> Async.RunSynchronously

        // compare the shared variable with sequence length
        (!num) + 1 = Seq.length l

// create an object with sequence 1 to 10
let asyncObj = AsyncSample(seq { 1..10 })

// pass function "<" to perform the comparison
asyncObj.AreAll(<)

Note

Passing an operator to a higher-order function can save you some typing. However, overusing this technique can make the code as easy to read as a World War II encrypted telegram, meaning that it can decrease the readability significantly.

Working with the Asynchronous Programming Model

Even though multithreaded programming can solve a number of problems, asynchronous programming is often a better choice. .NET provides support for asynchronous programming via a pattern called the Asynchronous Programming Model (APM). The typical APM has two parts. One is a method called Begin<Operation>—where Operation is the desired name of the asynchronous action—which initializes the operation asynchronously. The other is a method called End<Operation>, which is invoked when the operation finishes. The problem with APM is that it makes the code difficult to understand and maintain because related code is scattered in multiple places.

Asynchronous workflows are designed to solve this problem and simplify asynchronous programming tasks. Example 6-80 shows how to read a file and write the processed result into a new file asynchronously with an F# asynchronous workflow. If you have a file named tt.txt in c:MyCode that contains the text “A”, the content will become “B” after the execution.

Example 6-80. File process with an asynchronous workflow
open System.IO

let encryptFile fn =
    let operation =
        async {
            use fs = new FileStream(fn, FileMode.Open)

            // read the file content
            let! data = fs.AsyncRead(int fs.Length)
            let data' = data |> Array.map (fun n -> n+1uy)
            use encryptedFile = new FileStream(fn + ".out", FileMode.Create)

            // write the new content to the new file
            do! encryptedFile.AsyncWrite(data')
        }

    // start the operation with a three-scenario handler function
    Async.StartWithContinuations (operation,
        (fun _ -> printfn "finished"),       // print out when finished successfully
        (fun e -> printfn "something bad happened, %A" e),  //print when exception thrown
        (fun _ -> printfn "user gives up"))   // print when operation is cancelled

encryptFile @"c:mycode	t.txt"

There is something unfamiliar in Example 6-80—the let! (pronounced let-bang) and do! (pronounced do-bang). The let! extracts the underlying result out of the Async<’T>. If you position your pointer over the AsyncRead function, you see that its return type is Async<byte[]>, while the variable data’s type is byte[]. The do! is a special case of let!. It is used to handle the Async<unit> type where the return type is unit. The do! is used to start the asynchronous operation. The real computation is started when the operation variable is passed into the Async.StartWithContinuation function. The Async.StartWithContinuation function takes four parameters. The first one is the asynchronous operation, and the other three are used to specify the normal completion callback function, exception-handler function, and user-cancellation handler function, respectively.

The scattered code no longer exists thanks to let! and do!. You might be thinking that this sounds a lot like the Async and Await features in C# 5.0. If so, you are absolutely correct. F# has provided this functionality for many years, and the success of F# asynchronous workflows is the reason why C# 5.0 introduces these same features.

Handling Exceptions

The aysnc keyword seems to work nicely with the APM, but how about exception handling? Is it possible to handle the exception as you would in code that is executed sequentially? The answer is ”Yes.” Example 6-81 shows how to handle an exception inside of an asynchronous workflow.

Example 6-81. Asynchronous workflow exception handler
open System

let asyncOperationWithException =
    async {
        try
            /// some operations could throw an exception
        with
            | :? DivideByZeroException as e -> printfn "divided by zero"
            | :? ArgumentException as e -> printfn "this is an argument exception"
        }

You might have noticed immediately that this code is not safe. If failwith is put into the try section, the process can be brought down. Nice catch! The most obvious solution is to add an extra handler function in Example 6-81. Another approach is to use the Async.StartWithContinuation function, as shown in Example 6-80. If you like a more F#-oriented approach, examine Example 6-82, which uses Async.Catch to solve the problem. The FSI execution result shows that a System.Exception with a "my error" message is caught.

Example 6-82. Handling an exception using Async.Catch
open System

let asyncOperationWithException =
    async {
        try
            failwith "my error"
        with
            | :? DivideByZeroException as e -> printfn "divided by zero"
            | :? ArgumentException as e -> printfn "this is a argument exception"
        }

asyncOperationWithException
|> Async.Catch
|> Async.RunSynchronously
|> function
      | Choice1Of2 _ -> printfn "everything is good. No exception"
      | Choice2Of2 ex -> printfn "caught error: %A" ex

Execution result

caught error: System.Exception: my error
   at [email protected](Exception _arg1)
   at [email protected](AsyncParams'1 args)

Using Cancellation

If everything could be done in the blink of an eye, you might never need async. The reality is that users lose patience after a few blinks, and once their patience is lost they are likely to start searching for a cancel button. Asynchronous workflows can be cancelled when the execution is on let! or do!. After the cancellation, a handling function will be invoked to give the developer a chance to run some customized code. Other than using the Async.StartWithContinuation function, Example 6-83 shows how to use a Cancel token to interrupt and cancel the execution.

Example 6-83. Cancelling an asynchronous workflow
open System.Threading

let syncRoot = ref 0

// define printfn with lock
let lockedProcessingPrintf () =
     lock(syncRoot)
          (fun _ -> printfn "processing... reported from %d"
                         Thread.CurrentThread.ManagedThreadId)
let cancelPrintf() = lock(syncRoot) (fun _ -> printfn "cancelling operation...")
let lockPrintf str = lock(syncRoot) (fun _ -> printfn str)

let computation (tokenSource:System.Threading.CancellationTokenSource) =
    async {
        use! cancelHandler = Async.OnCancel(fun _ -> cancelPrintf())
        while true do
            lockedProcessingPrintf()
            do! Async.Sleep 100
    }

// a warning will be generated here if used inside a module
use ts1 = new CancellationTokenSource()
use ts2 = new CancellationTokenSource()

printfn "Starting..."

//start the computation with a cancellation token
Async.Start(computation ts1, ts1.Token)
Async.Start(computation ts2, ts2.Token)

Thread.Sleep(1 * 1000)

lockPrintf "Cancelling..."
ts1.Cancel()
ts2.Cancel()

// give some time for thread to finish
Thread.Sleep(1 * 1000)

System.Console.ReadLine() |> ignoreExamining Some Asynchronous Workflow Samples

Before proceeding with real-world examples, I first need to present a sample that demonstrates callback and cancellation within an asynchronous workflow. Example 6-84 presents back-end, callback, and cancellation processes.

Example 6-84. Asynchronous workflow example using back-end, callback, and cancellation processes
open System.Threading

let sleep : int -> unit = Thread.Sleep
let print : string -> unit = System.Console.WriteLine

// back-end process
let proc x =
    async {
        print "processing"
        sleep 5000
        print "processed"
        return x + 7
    }

// callback function
let callBack i =
    print "in call function function"
    print (sprintf "call back value = %A" i)

// aysnc using a back-end process and callback function
let myProcess callBackFunction x =
    async {
        let! v = proc x
        callBackFunction v
    }

// function print timer message with cancellation
let timer cancelCallBack =
    async {
        use! cancel = cancelCallBack
        while true do
            print "time..."
            sleep 1000
    }

// set up cancellation
let cancelToken = new CancellationTokenSource()
let cancelCallBack = Async.OnCancel (fun _ -> print "cancelling")
Async.Start(timer cancelCallBack, cancelToken.Token)

// start the back-end process
4
|> myProcess callBack
|> Async.RunSynchronously

// cancel timer
cancelToken.Cancel()

Note

Because System.Console.WriteLine has multiple overloaded versions, the type definition string->unit is necessary.

In this section, there are two samples using asynchronous workflows as well as other previously introduced data structures. Example 6-85 uses an asynchronous workflow to compute the Fibonacci number. The notable point for this sample is the Async.StartChild, which starts another workflow.

Example 6-85. Using asynchronous workflows to compute a Fibonacci number
let rec fibonacci = function
    | 0 -> async { return 0 }
    | 1 -> async { return 1 }
    | n -> async { let! n2Async = fibonacci(n - 2) |> Async.StartChild
                   let! n1 = fibonacci(n - 1)
                   let! n2 = n2Async
                   return n1 + n2 }

printfn "fibonacci(20) = %A"
    (Async.RunSynchronously <| (fibonacci 20 ))

Execution result

fibonacci(20) = 6765

Note

.NET 4+ uses the Task Parallel Library (TPL) to execute the asynchronous workflow.

The second example, presented in Example 6-86, is a quick sort implementation. It uses not only the asynchronous workflow, but also pattern matching and active patterns.

Example 6-86. A quick sort using an asynchronous workflow
// partial-case active pattern
let (|Empty|_|) l = if l |> Array.isEmpty then Some l else None
let (|SingleValue|_|) l = if l |> Array.length = 1 then Some l else None

// quick sort function
let rec quickSort (intArray : int array )=
    // pattern matching
    match intArray with
    | Empty empty ->
            async {return [||]}

    | SingleValue singleValue ->
            async {return singleValue}

    | multValues ->
            async {
                        //pivot
                        let p = ref 0
                        //headFlag, tailFlag
                        let h, t = ref 0, ref (multValues.Length - 1)
                        //tail active -- compare from tail to head ,by default
                        let tA = ref true

                        while (!h <= !t) do
                            if(!t - !h = 1) then
                                if(multValues.[!t] < multValues.[!h]) then
                                    let temp = multValues.[!h]
                                    multValues.[!h] <- multValues.[!t]
                                    multValues.[!t] <- temp
                            if (!tA = true) then
                                if (multValues.[!t] >= multValues.[!p]) then
                                    t := !t - 1
                                else
                                    let temp = multValues.[!t]
                                    multValues.[!t] <- multValues.[!p]
                                    multValues.[!p] <- temp
                                    p := !t
                                    h := !h + 1
                                    tA := false
                            else
                                if(multValues.[!h] <= multValues.[!p]) then
                                    h := !h + 1
                                else
                                    let temp = multValues.[!h]
                                    multValues.[!h] <- multValues.[!p]
                                    multValues.[!p] <- temp
                                    p := !h
                                    t := !t - 1
                                    tA := true

                        let leftArray = Array.sub multValues 0 (!p + 1)
                        let rigthArray = Array.sub multValues (!p + 1) (multValues.Length-
!p-1)

                        let! leftAsync = quickSort(leftArray) |> Async.StartChild
                        let! rightAsync = quickSort(rigthArray) |> Async.StartChild

                        let! left = leftAsync
                        let! right = rightAsync

                        return (Array.append left right) }

printfn " %A" (Async.RunSynchronously <| quickSort [|3; 7; 8; 5; 2; 1; 9; 5; 4|])

Execution result

[|1; 2; 3; 4; 5; 5; 7; 8; 9|]

Building Your Own Asynchronous Primitives

For some legacy code that provides only the APM Begin<Operation> and End<Operation> methods, you need to write a simple function to allow them to be used in an asynchronous workflow. The Async.FromBeginEnd function is designed to assist in this scenario. Example 6-87 takes a delegate’s BeginInvoke and EndInvoke to form an asynchronous workflow.

Example 6-87. Building an asynchronous primitive
open System
open System.IO

type MyAsyncPrimitive() =
    static member AsyncCopy(source, target) =
        // create a delegate
        let copyDelegate = new Func<_*_, _>(File.Copy)

        // convert delegate to asynchronous workflow
        Async.FromBeginEnd((source,target), copyDelegate.BeginInvoke, copyDelegate.
EndInvoke)

let asyncCopy source target =
    async { do! MyAsyncPrimitive.AsyncCopy(source, target) }

Note

The code in Example 6-87 is for demo purposes. Changing a delegate to an asynchronous operation does not always improve the performance.

Debugging Multithreaded Applications

Debugging a multithreaded application is always tricky. In addition to using the traditional console output technique shown in Example 6-78, you can use Microsoft Visual Studio to view different threads. If the thread is spawned by FSharp.Core and cannot be viewed, you can try to enable or disable the Just My Code option under Tools, Options to see the call stack. (See Figure 6-1.)

Changing the Just My Code debug setting
Figure 6-1. Changing the Just My Code debug setting

Using Agents

In additional to asynchronous workflows, F# provides another great feature known as the mailbox processor, which can be used to perform asynchronous programming tasks. Example 6-88 shows how to use a mailbox processor to implement a simple agent. The only function for this simple agent is printing the received message. The agent is started by using the MailBoxProcessor.Start method. The posted message is retrieved by calling Receive(). The post action is done by the Post method. You can think of an agent as a monitored message queue that executes the code only after the call to Receive() when a message arrives in that queue.

Example 6-88. Mailbox processor agent
 // rename the mailbox processor to Agent
type Agent<'T> = MailboxProcessor<'T>

// create the agent with async
let agent =
   Agent.Start(fun
mailbox ->
     async { while true do
               let! msg = mailbox.Receive()
               printfn "got message '%s'"
msg } )

// post the message
agent.Post "hello"
agent.Post "world"

At this point, you might be thinking that an agent is directly mapped to a thread. Luckily, this is not true. The mailbox processor is backed up by the ThreadPool. A mailbox processor is logically single threaded at the back end, For example, to send 100 messages to 1 box, only one thread is needed to process them serially. Sending 100 threads to 100 mailboxes will use X threads, where X is a number chosen by the ThreadPool. Example 6-89 creates 100,000 agents. A 32-bit machine can barely create several hundred threads, so this shows that the agent is not directly mapped to a thread. The execution result shows the execution time in FSI with the time switch set to ON. The mailbox processor can handle 100,000 messages in just two to three seconds!

Example 6-89. Creating 100,000 agents
// rename the mailbox processor to Agent
type Agent<'T> = MailboxProcessor<'T>

// create agents
let agents =
    [1 .. 100 * 1000]
    |> List.map  (fun i->
       Agent.Start(fun n ->
         async {
            while true do
               let! msg = n.Receive()
               if i % 20000 = 0 then
                   printfn "agent %d got message '%s'" i msg } ))

// post message to all agents
for agent in agents do
    agent.Post "hello"

Execution time

Real: 00:00:02.665, CPU: 00:00:03.375, GC gen0: 26, gen1: 24, gen2: 1

It’s rare for an agent to exist in isolation. Often, you need to interact with other aspects of your code from within the body of an agent. One approach for accomplishing this is to publish an event from within an agent to communicate with the outside world. Example 6-90 fires an event for every 30,000 messages that are received.

Example 6-90. An agent that fires an event
// rename the mailbox processor to Agent
type Agent<'T> = MailboxProcessor<'T>

// event to be raised
let myEvent = new Event<int>()

// current sync context
let syncContext = System.Threading.SynchronizationContext.Current

// create agents
let agents =
    [1 .. 100 * 1000]
    |> List.map  (fun i->
       Agent.Start(fun n ->
         async {
            while true do
               let! msg = n.Receive()
               if i % 30000 = 0 then
                   syncContext.Post( (fun _ -> myEvent.Trigger i), null) } ))

// added event handler
Event.add (fun n-> printfn "%A messages received" n) myEvent.Publish

// post message to all agents
for agent in agents do
    agent.Post "hello"

We live in an imperfect world—inevitably, our beautiful code will run across something unexpected and have to handle an exception. Example 6-91 shows how to handle an exception within the agent code. There will be one “something is wrong” message printed out when 100 is divided by 0.

Example 6-91. Handling an exception in the agent
// rename the mailbox processor to Agent
type Agent<'T> = MailboxProcessor<'T>

// event to be raised
let myEvent = new Event<int>()

let syncContext = System.Threading.SynchronizationContext.Current

// create agents
let agents =
    [-100 * 1000 .. 100 * 1000]
    |> List.map  (fun i->
        use a = new Agent<_>(fun n ->
                 async {
                    while true do
                       let! msg = n.Receive()
                       let result = 100 / i;
                       if i % 30000 = 0 then
                           syncContext.Post( (fun _ -> myEvent.Trigger i), null) } )
        a.Error.Add(fun _ -> printfn "something is wrong")
        a.Start()
        a)

// added event handler
Event.add (fun n-> printfn "%A message received" n) myEvent.Publish

// post message to all agents
for agent in agents do
    agent.Post "hello"

You can use MailboxProcessor to simulate a message-box banking account system where the deposit and withdrawal operations are performed asynchronously. The sample code is shown in Example 6-92.

Example 6-92. Banking account using MailboxProcessor
open Microsoft.FSharp.Control

[<Measure>] type USD

// define account state
type AccountState =
    | Overdrawn
    | Silver
    | Gold

// define operations: deposit and withdraw
type Operation =
    | Deposit of decimal<USD>
    | Withdraw of decimal<USD>

// define the account class
type Account() =
    let mutable balance = 0.m<USD>

    // define a MailboxProcessor
    let bank = new MailboxProcessor<Operation>(fun inbox ->
                let rec loop () =
                    async {
                        let! msg = inbox.Receive()
                        match msg with
                            | Deposit n -> balance <- balance + n
                            | Withdraw n -> balance <- balance - n
                        return! loop ()
                    }

                loop ())

    // start the MailboxProcessor instance
    do
        bank.Start()

    // define operations which post the message to MailboxProcessor
    member this.Deposit(args) =
        bank.Post(Deposit(args))
    member this.Withdraw(args) =
        bank.Post(Withdraw(args))

    member this.State
        with get() =
            match balance with
                | _ when balance <= 0.m<USD> -> AccountState.Overdrawn
                | _ when balance > 0.m<USD> && balance < 10000.m<USD> -> AccountState.
Silver
                | _ -> AccountState.Gold
    member this.PayInterest() =
        while bank.CurrentQueueLength > 0 do
            System.Threading.Thread.Sleep (100)
        printfn "current state is %A" this.State
        match this.State with
            | AccountState.Overdrawn -> ()
            | AccountState.Silver -> this.Deposit(balance * 0.01m)
            | AccountState.Gold -> this.Deposit(balance * 0.02m)
    member this.ShowBalance() =
        while bank.CurrentQueueLength > 0 do
            System.Threading.Thread.Sleep (100)
        printfn "current balance is %A" balance

let account = Account()
account.Deposit(1000.m<USD>)
account.Deposit(10000.m<USD>)
account.PayInterest()
account.ShowBalance()

Execution result in FSI

current state is Gold
current balance is 11220.00M

Working with Computation Expressions

I introduced query and async in Chapter 4 in the “Asynchronous Workflows” section. When you look at the blue-color async and query syntax in the Visual Studio editor window, you might think they are keywords, like the this keyword in C#. However, if that were the case, these keywords couldn’t be used as variable names. For example, how does the code in Example 6-93 compile?

Example 6-93. Using async, seq, and query as variables
let async = 0
let seq = 1
let query = 2

Actually, the code compiles because async, seq, and query are not keywords. Instead, they are a special F# feature called computation expressions. The MSDN documentation (http://msdn.microsoft.com/en-us/library/dd233182.aspx) provides a good definition of computation expressions:

Computation expressions in F# provide a convenient syntax for writing computations that can be sequenced and combined using control flow constructs and bindings. They can be used to provide a convenient syntax for monads, a functional programming feature that can be used to manage data, control, and side effects in functional programs.

F# provides computation expressions so that the language can be expanded. The reason you can put your code inside a seq is because it is a computation expression. Example 6-94 revisits the sequence-generation code that was discussed in Chapter 1.

Example 6-94. Sequence generation using a reference cell

Sequence generation code

let indices = [1, 2; 3, 4; 5, 6 ]

let newIndices = seq {
    let currentCell = ref 0
    for a, b in indices do
        yield !currentCell, a
        yield a, b
        currentCell := b
    yield !currentCell, 100
    }

Execution result

> newIndices;;
val it : seq<int * int> = seq [(0, 1); (1, 2); (2, 3); (3, 4); ...]

Note

This sample code also demonstrates how to use a reference cell. It is invalid to use a mutable variable in the computation expression. The only way to define a mutable variable in this scenario is to use a reference cell.

In addition to the mutable-variable restriction, there is another restriction for computation expressions. It is invalid to use base in the computation expression. (See Example 6-95.) The workaround is simple, just use this.

Example 6-95. Using this in a seq computation expression
type BaseClass() =
    member val X = 0 with get, set

type ClassA() =
    inherit BaseClass()
    member this.F() =
        let r = seq {
            let a = 0

            // use base here to generate a compile error
            let b = this.X //base.X
            yield a }
        r

Expanding a .NET language seems like an exciting possibility. OK, let’s do it now. The first sample is a bank system in which the decimal number is rounded when calculating a customer’s interest, as shown in Example 6-96.

Example 6-96. Rounding computation expression
// define a rounded computation expression
type RoundComputationBuilder(digits:int) =
    let round (x:decimal) = System.Math.Round(x, digits)
    member this.Bind(result, restComputation ) =
        restComputation (round result)
    member this.Return x = round x

// calculate the bank interests
let bankInterest = RoundComputationBuilder 2

bankInterest {
   let! x = 23231.34m * 0.002m
   return x
}

The RoundComputationBuilder defines two special methods: Bind and Return. These two methods correspond to let! and return if you debug into the code. Table 6-2 shows a full list of method names and corresponding statements. In most cases, the method name suggests its usage.

Table 6-2. Computation expression methods

Method

Description

For

seq<’a> * (‘a -> Result<unit>) -> Result<unit>

Enables the FOR loop

Zerounit

-> Result<unit>

Enables the execute unit expression, which returns “void” in C#

Combine

Result<unit> -> Result<’a> -> Result<’a>

Links the computation expression parts

While

(unit -> bool) * Result<unit> -> Result<unit>

Enables the WHILE loop

Return

‘a -> Result<’a>

Enables the return keyword

ReturnFrom

‘a -> Result<’a>

Enables the return! keyword

Yield

‘a -> Result<’a>

Enables the yield keyword

YieldFrom

seq<’a> -> Result<’a>

Enables the yield! keyword

Delay

(unit -> Result<’a>) -> Result<’a>

Used along with the Combine method to ensure the correct order of statement execution

Run

Result<’a> -> Result<’a>

Executes before the execution of a computation expression—like the C# Before<Operation>

Using ‘a * (‘a -> Result<’b>) -> Result<’b> when ‘a :> System.IDisposable

Enables the use and use! keywords

Result<’a> * (‘a -> Result<’b>) -> Result<’b>

Enables the let! and do! keywords

Result<’a> * (unit -> unit) -> Result<’a>

Enables the try...finally expression

TryWith

Result<’a> * (e -> Result<’a>) -> Result<’a>

Enables the try...with expression

With the full table listed, you can expand Example 6-96 by adding two more members, as shown in Example 6-97. The return! keyword returns the value without going through the rounding process. The Run method prints a message before any execution starts. Note that let (not let!) does not invoke any computation expression method.

Example 6-97. Computation expression example with more methods implemented

Computation expression code

type RoundComputationBuilder(digits:int) =
    let round (x:decimal) = System.Math.Round(x, digits)
    member this.Bind(result, restComputation ) =
        restComputation (round result)
    member this.Return x = round x
    member this.ReturnFrom x = x  //keep the original value
    member this.Run f =
        printfn "now start to run.."
        f

let bankInterest = RoundComputationBuilder 2

let roundedResult =
    bankInterest {
       let! x = 23231.34m * 0.002m   //invoke the Bind method
       return x        //invoke the Return
    }

let result =
    bankInterest {
       let y = 23231.34m * 0.002m   // do not invoke the Bind method
       return! y       //invoke the ReturnFrom
    }

printfn "roundResult = %A, unroundedResult = %A" roundedResult result

Execution result

now start to run..
now start to run..
roundResult = 46.46M, unroundedResult = 46.46268M

A more practical example is the retry computation expression, shown in Example 6-98. It performs the retry logic a given number of times before giving up.

Example 6-98. The retry computation expression

Retry computation expression code

// define a retry computation expression
type Retry (retryTimes:int) = class
    let mutable success = false
    member public this.Bind(value, restFunction:unit -> _) =
        success <- false
        let mutable n = retryTimes
        while not success && n > 0 do
            n <- n - 1
            try
                value()
                success <- true
            with _ as e -> printfn "failed with %s, %d times" e.Message (retryTimes - n)
        restFunction ()
    member public this.Return args =
        success
end

module TestModule =
     // set the retry limit to 4
     let retry = Retry 4

     // computation expression returns a Boolean value
     let a = retry {
        do! (fun () -> printfn "let us try";
                       failwith "test failure")  // fail the process on purpose
     }

     printfn "result is %A" a

Execution result

let us try
failed with test failure, 1 times
let us try
failed with test failure, 2 times
let us try
failed with test failure, 3 times
let us try
failed with test failure, 4 times
result is false

The computation expression creates a small region with its own language and maybe its own way of performing the computation. If you repeatedly write the same code, a computation expression is something that can be of great assistance.

Using Computation Expression Attributes

If query and seq are both computation expressions, you might be wondering how other keywords such as where are defined. They are defined by using attributes. Let’s start from a simple example, SimpleSequenceBuilder. Example 6-99 shows a simple sequence builder and the code to which it will be translated. This is the starting point to show how to add customer operators such as where.

Example 6-99. Simple sequence builder
// define the sequence builder class
type SimpleSequence() = class
    member this.For(source:seq<'a>, f:'a -> seq<'b>) =
        seq { for n in source do yield! f n }
    member this.Yield(n) = seq { yield n }
end

let mySeq = SimpleSequence()
let r = mySeq { for i in 1..10 do yield i * i }

Translation result

let b = mySeq
b.For ( [1..10] , fun i -> b.Yield(i * i))

The where operation is introduced by the CustomOperation attribute, which is shown in Example 6-100. The ProjectionParameter on the second parameter is useful to simplify the function definition. Lacking this attribute, the where part in the computation expression has to be written as fun i -> ... explicitly. The ProjectionParameter is also useful when there is a let binding between the customer operator and ForEach operation, like the one shown in Example 6-101.

Example 6-100. Using CustomOperation and ProjectionParameter attributes
type SimpleSequence2() =
    member this.For(source:seq<'a>, f:'a -> seq<'b>) =
        seq { for n in source do yield! f n }
    member this.Yield(n) = seq { yield n }
    [<CustomOperation("where")>]
    member this.Where(source:seq<'a>, [<ProjectionParameter>]f:'a -> bool) =
        source |> Seq.filter f

let mySeq2 = SimpleSequence2()
let r2 = mySeq2 {
            for i in 1..10 do
            where (i > 3)
        }

Translation result

let b2 = mySeq2
b2.Where ( b.For([1..10] , fun i -> b.Yield(i)), fun i-> i > 3)
Example 6-101. ProjectParameter and let binding
let r3 = mySeq2 {
            for i in 1..10 do
            let j = i + 3
            where (i > 5 && j < 10)
            }

Translation result

let b3 = mySeq2
b3.Where(b.For([1..10], fun i ->
                            let j = i + 3
                            b.Yield(i, j)),
         fun (i, j) -> i > 5 && j < 10)

There are two properties in the CustomOperation attribute. The MaintainsVariableSpace property supports writing statements such as where i>5 && j<10, like the one shown here:

where (i>5)
where (j<10)

The sample code is shown in Example 6-102. The other property is the AllowIntoPattern. Like its name suggests, it supports the into syntax, as shown in Example 6-103.

Example 6-102. MaintainsVariableSpace in the CustomOperation attribute
type SimpleSequence3() =
    member this.For(source:seq<'a>, f:'a -> seq<'b>) =
        seq { for n in source do yield! f n }
    member this.Yield(n) = seq { yield n }
    [<CustomOperation("where", MaintainsVariableSpace=true)>]
    member this.Where(source:seq<'a>, [<ProjectionParameter>]f:'a -> bool) =
        source |> Seq.filter f

let mySeq3 = SimpleSequence3()
let r4 = mySeq3 {
            for i in 1..10 do
            let j = i + 3
            where (i > 5)
            where (j < 10)
            }

Translation result

let b4 = mySeq3
b4.Where(
    b4.Where(
    b4.For([1..10], fun i ->
                        let j = i + 3
                        b4.Yield(i, j)),
       fun (i, j) -> i > 5),
    fun (i, j) -> j < 10)

Note

the above code generate a warning, the warning can be eliminated by adding |> ignore in the end.

Example 6-103. AllowIntoPattern in the CustomerOperation attribute
type SimpleSequence4() =
    member this.For(source:seq<'a>, f:'a -> seq<'b>) =
        seq { for n in source do yield! f n }
    member this.Yield(n) = seq { yield n }
    [<CustomOperation("where", AllowIntoPattern=true)>]
    member this.Where(source:seq<'a>, [<ProjectionParameter>]f:'a -> bool) =
        source |> Seq.filter f

let mySeq4 = SimpleSequence4()
let r5 = mySeq4 {
            for i in 1..10 do
            where (i > 5) into j
            where (j < 10)
            }

Translation result

let b5 = mySeq4
b5.Where(
    b5.For(
        b5.Where(
            b5.For([1..10], fun i-> b5.Yield(i)),
            fun i -> i > 5),
        fun j -> b5.Yield(j)),
fun j -> j < 10)

Note

the above code generate a warning, the warning can be eliminated by adding |> ignore in the end.

Other than the MaintainsVariableSpace property, there is another property named MaintainsVariableSpaceUsingBind, which can be used to pass the variable down the chain, but in a different way. The sample is shown in Example 6-104.

Example 6-104. MaintainsVariableSpaceUsingBind in the CustomerOperation attribute
type SimpleSequence5() =
    member this.For(source:seq<'a>, f:'a -> seq<'b>) =
        seq { for n in source do yield! f n }
    member this.Return(n) = seq { yield n }
    [<CustomOperation("where",AllowIntoPattern=true,MaintainsVariableSpaceUsingBind=true)>]
    member this.Where(source:seq<'a>, [<ProjectionParameter>]f:'a -> bool) =
        source |> Seq.filter f
    member this.Bind(value, cont) = cont value


let mySeq5 = SimpleSequence5()

let r6 = mySeq5 {
            for i in 1..10 do
            where (i > 5 && i + 3 < 10) into j
            return j
         }

Translation result

let b6 = mySeq5

b6.Bind(
    b6.Where(
        b6.For([1..10], fun i -> b6.Return(i)),
                fun i -> i > 5 && i + 3 < 10),
            fun j -> b6.Return(j))

Certain properties on the CustomOperationAttribute introduce join-like operators. Example 6-105 shows how to use the IsLikeJoin property.

Example 6-105. Using IsLikeJoin in a computation expression
type SimpleSequenceBuilder() =
    member __.For (source : seq<'a>, body : 'a -> seq<'b>) =
           seq { for v in source do yield! body v }
    member __.Yield (item:'a) : seq<'a> = seq { yield item }

    [<CustomOperation("merge", IsLikeJoin = true, JoinConditionWord = "whenever")>]
    member __.Merge (src1:seq<'a>, src2:seq<'a>, ks1, ks2, ret) =
              seq { for a in src1 do
                    for b in src2 do
                    if ks1 a = ks2 b then yield((ret a ) b)
              }

let myseq = SimpleSequenceBuilder()

IsLikeJoin indicates that the custom operation is similar to a join in a sequence computation, supporting two inputs and a correlation constraint. The expression in Example 6-106 is translated into Example 6-107.

Example 6-106. Query code related to LikeJoin
myseq {
    for i in 1 .. 10 do
    merge j in [5 .. 15] whenever (i = j)
    yield j
    }
Example 6-107. Translated code from Example 6-106
let b = myseq

b.For(
   b.Merge([1..10], [5..15],
            (fun i -> i), (fun j -> j),
            (fun i -> fun j -> (i, j))),
   fun j -> b.Yield (j))

The computation expression is a big and advanced topic. Interested readers can refer to the latest F# language specification (http://research.microsoft.com/en-us/um/cambridge/projects/fsharp/manual/spec.html) for more information.

Using Computation Expression Sample

Example 6-108 demonstrates a bank system that makes deposits to and withdrawals from an account. The computation expression creates a transaction with multiple deposit and withdrawal operations. The operation starts from the Yield method, which provides the account object for succeeding operations, such as deposit. The Deposit and Withdraw methods take two parameters. The first parameter is the account parameter, and the second one is the amount parameter, which is passed by the user. The Run method is the last one to be invoked. It returns a function unit->Account<'u> type.

Example 6-108. Bank-system computation expression example
type Account<[<Measure>]'u>() =
    member val Balance = 0.0<_> with get, set
    member val PendingAmount = 0.0<_> with get, set

type Bank<[<Measure>]'u>(account:Account<'u>) =
    member this.Yield (()) =
        account

    [<CustomOperation("deposit")>]
    member this.Deposit (account:Account<'u>, v:float<'u>) : Account<'u> =
        account.PendingAmount <- account.PendingAmount + v
        account

    [<CustomOperation("withdraw")>]
    member this.Withdraw (account:Account<'u>, v:float<'u>) : Account<'u> =
        account.PendingAmount <- account.PendingAmount - v
        account

    member this.Run (account:Account<'u>) =
        fun () ->
            let finalBalance = account.Balance + account.PendingAmount
            if finalBalance > 0.<_> then
                account.Balance <- finalBalance
                account
            else
                printfn "not enough found"
                account

[<Measure>] type USD
let account = Account<USD>()
let bank = Bank<USD>(account)

let transaction =
    bank {
        deposit 100.<USD>
        withdraw 50.<USD>
    }

printfn "balance after one transaction = %A" (transaction().Balance)
Computation expression execution result
Figure 6-2. Computation expression execution result

Using Reflection

As a C# developer, reflection is not likely a new concept for you. Usually, reflection in C# involves defining or applying attributes and getting type or method information.

Defining Attributes

Example 6-109 shows how to define a customized attribute.

Example 6-109. Defining an attribute
 // define attribute with a property
type MyAttribute() =
    inherit System.Attribute()
    member val MyAttributeProperty = System.String.Empty with get, set

You can apply one or more attributes to modules, or smaller program elements, such as classes and properties, by putting the attribute name between [< and >]. The assembly-level attribute is different, because it can be applied only by using the do keyword. The attribute can be applied separately as is done in C#. Additionally, multiple attributes can be applied in a group format, where multiple attributes are put into one [< >] and separated by semi-colons. Example 6-110 shows how to apply one or more attributes to a class definition. There is little difference between applying attributes to a class and to an interface/struct. I will leave this exercise to you.

Example 6-110. Applying an attribute to a class and class members
// define an attribute
type MyAttribute() =
    inherit System.Attribute()

// define an attribute
type MyAttribute2() =
    inherit System.Attribute()
// define a class with one attribute
[<MyAttribute>]
type MyClassWithOneAttribute() = class

    end

// define a class with two attributes applied separately
[<MyAttribute>]
[<MyAttribute2>]
type MyClassWithTwoAttributes() = class

    end

// define a class with two attributes applied as a group
[<MyAttribute; MyAttribute2>]
type MyClassWithGroupedAttribute() = class

    end

// apply an attribute to a method, property, and field
type MyClass3() =
    [<MyAttribute>]
    let mutable MyVariable = 0

    [<MyAttribute>]
    member this.MyMethod([<MyAttribute2; MyAttribute>] x0, [<MyAttribute>] x1) = ()

    member this.MyProperty
        with [<MyAttribute2>] get() = MyVariable
        and set(v) = MyVariable <- v

    [<MyAttribute>]
    member val MyProperty2 = 0 with get, set
// apply attribute to mutual reference class
type MyClass4() =
    let a:MyClass5 = MyClass5()
and [<MyAttribute>] MyClass5() = class

    end

Example 6-111 shows how to apply attributes to F#-specific structures, such as a let binding and a record. Also, it shows how to apply attributes on the special structures in F# class definitions, such as the primary constructor.

Example 6-111. An attribute on F#-specific data structures
// define an attribute
type MyAttribute() =
    inherit System.Attribute()

[<MyAttribute>]
let myValue = 0

[<MyAttribute>]
let myFunction x = x + 1

// the functionality from the following two statements are the same
let [<MyAttribute>] a, b = 1, 2

[<MyAttribute>]
let c, d = 1, 2

// apply an attribute to a function
let rec [<MyAttribute>] f() = g()

// apply an attribute to mutual reference functions
and [<MyAttribute>] g() = printfn "hello"

//apply an attribute to a record
[<MyAttribute>]
type Point = {
    [<MyAttribute>] x : float;
    [<MyAttribute>] y: float;
    z: float; }

// apply an attribute to a DU
[<MyAttribute>]
type DU =
| [<MyAttribute>] Case1 of float
| Case2 of double
| [<MyAttribute>] Case3 of double

// apply an attribute to a unit of measure
[<Measure; MyAttribute>] type km

// apply an attribute to an enumeration
[<MyAttribute>]
type Color =
   | [<MyAttribute>] Red = 0
   | Green = 1
   | [<MyAttribute>] Blue = 2

// apply an attribute to an exception type
[<MyAttribute>]
exception MyError of string

// apply an attribute to a tuple parameter
let f3 ([<MyAttribute>] a, [<MyAttribute>] b) = a + b
let f4 ([<MyAttribute>] a, [<MyAttribute>] b) c = a + b + c

[<MyAttribute>]
module MyModule =
    let myTest = 0

// apply an attribute to a primary constructor
type MyClass2[<MyAttribute>]() = class

    end

// apply an attribute to the parameter of a constructor
type MyClass([<MyAttribute>] xValue:double, yValue:double)=

    // define a static field named count
    [<MyAttribute>]
    static let mutable count = 0

    //additional constructor
    [<MyAttribute>]
    new() = MyClass(0., 0.)

Some F# structures do not accept attributes. The pattern matching expression let ([<MyAttribute>] myValue2, myValue3) segment in the following example does not support attributes. Also, attributes cannot be applied to the exception parameters in an F# exception definition. You can use an exception class definition to solve this problem. Also, curried functions do not accept attributes on parameters. The auto-implemented property’s getter and setter functions cannot have attributes. If the attribute is needed, you have to use the old syntax.

// pattern does not allow attribute
let ([<MyAttribute>] myValue2, myValue3) = (2, 3)

// attribute cannot be applied to exception parameter
exception MyError2 of [<MyAttribute>] string

//apply attribute to curried function
let f2 [<MyAttribute>]a [<MyAttribute>]b = a + 1

//apply attribute to auto-implemented property's getter or setter function
type MyClass() =
    member val MyProperty = 0 with [<MyAttribute >] get, set

Note

The sample code does not compile.

Working with Type and Member Info

F# supports the typeof operator similar to C#. For example, typeof<int> returns a System.Int32 type. Because typeof always takes the type parameter as a type of object, F# introduces the typedefof function to work with generic types. Example 6-112 shows the difference between typeof and typedefof.

Example 6-112. The typeof and typedefof operators

Typeof and typedefof code

typeof<System.Collections.Generic.IEnumerable<_>>

typedefof<System.Collections.Generic.IEnumerable<_>>

Execution result

val it : System.Type =
  System.Collections.Generic.IEnumerable'1[System.Object]

val it : System.Type =
  System.Collections.Generic.IEnumerable'1[T]

Getting the type info enables you to invoke methods or access the properties of an object. Example 6-113 shows how to use reflection to retrieve MethodInfo and invoke the method on a Student class. The output from the code shows that the student variable’s content is changed from (“John”, 16) to (“Brent”, 18).

Example 6-113. Reflection by using F#
// define student class
type Student(nameIn : string, idIn : int) =
    let mutable name = nameIn
    let mutable id = idIn

    do printfn "Created a student object"
    member this.Name with get() = name and set v = name <- v
    member this.ID with get() = id and set v = id <- v
    member public this.SetStudentInfo(name, id) =
        this.Name <- name
        this.ID <- id

module TestModule =

    let student = Student("John", 16)
    let studentType = typeof<Student>

    //use reflection to invoke SetStudentInfo method
    let result = studentType.GetMethod("SetStudentInfo").Invoke(student, [| "Brent"; 18 |]
)

    printfn "new student info: name = %s, id = %d" student.Name student.ID

Execution result

Created a student object
new student info: name = Brent, id = 18

Using Reflection on F# Types

Reflection was introduced before F# came into production, so F# needed a way to provide reflection on its specific types, such as a tuple. You can find all of these F# specific reflection functions in the Microsoft.FSharp.Reflection namespace.

Using Tuples

Example 6-114 shows how to use reflection to get the element types in a tuple, get element values, and make a tuple from an object array.

Example 6-114. F# reflection on a tuple

Source code

open Microsoft.FSharp.Reflection

let tuple = ("John", 18, "Canada")

// get element types in a tuple
FSharpType.GetTupleElements(tuple.GetType())

// retrieve given tuple field value
FSharpValue.GetTupleField(tuple, 2)

// retrieve all fields in a tuple
FSharpValue.GetTupleFields tuple

// make a tuple from an array array
FSharpValue.MakeTuple([| "Lisa"; 20; "USA" |], tuple.GetType())

// check if a type is a tuple
FSharpType.IsTuple (tuple.GetType())

// make a tuple type
let t = FSharpType.MakeTupleType([| typeof<int>; typeof<string> |])
FSharpValue.MakeTuple([| 1; "One" |], t)

Execution result

val tuple : string * int * string = ("John", 18, "Canada")
val it : System.Type [] = [|System.String; System.Int32; System.String|]

>
val it : obj = "Canada"
>
val it : obj [] = [|"John"; 18; "Canada"|]
>
val it : obj = ("Lisa", 20, "USA")
>
val it : bool = true
>

val t : System.Type = System.Tuple'2[System.Int32,System.String]
val it : obj = (1, "One")

Example 6-115 shows the discriminated union–related functions. The sample code shows how to use reflection to check the type, get union case info, get a union field, and create a union value.

Example 6-115. F# reflection on discriminated unions

Source code

open Microsoft.FSharp.Reflection

// define a shape discriminated union
type Shape =
    | Circle of double
    | Triangle of double * double * double
    | Rectangle of double * double

let du = Shape.Rectangle(2., 3.)

// check if the type is a discriminated union
FSharpType.IsUnion(du.GetType())

// get union case type info
FSharpType.GetUnionCases(du.GetType())

// get union field
FSharpValue.GetUnionFields(du, du.GetType())

// create new discriminated union value
let caseInfo = fst <| FSharpValue.GetUnionFields(du, du.GetType())
FSharpValue.MakeUnion(caseInfo, [| 10.; 20. |])

Execution result

val it : bool = true
>

val it : UnionCaseInfo [] =
  [|Shape.Circle {DeclaringType = FSI_0033+Shape;
                  Name = "Circle";
                  Tag = 0;}; Shape.Triangle {DeclaringType = FSI_0033+Shape;
                                             Name = "Triangle";
                                             Tag = 1;};
    Shape.Rectangle {DeclaringType = FSI_0033+Shape;
                     Name = "Rectangle";
                     Tag = 2;}|]
>

val it : UnionCaseInfo * obj [] =
  (Shape.Rectangle {DeclaringType = FSI_0033+Shape;
                    Name = "Rectangle";
                    Tag = 2;}, [|2.0; 3.0|])
>

val caseInfo : UnionCaseInfo = Shape.Rectangle
val it : obj = Rectangle (10.0,20.0)

The F# record-related functions are shown in Example 6-116. The sample code shows how to use reflection to check the type, get a record’s fields, get a particular field, and make a record from an array.

Example 6-116. F# reflection on a record

Source code

open Microsoft.FSharp.Reflection

type Point2D = { x : float; y: float }
let point2DRecord = { Point2D.x = 1.; Point2D.y = 2. }

// check if it is a record type
FSharpType.IsRecord(point2DRecord.GetType())

// get record fields
FSharpType.GetRecordFields(point2DRecord.GetType())

// get a record field
let fields = FSharpType.GetRecordFields(point2DRecord.GetType())
FSharpValue.GetRecordField(point2DRecord, fields.[1])

// make a record from an array
FSharpValue.MakeRecord(point2DRecord.GetType(), [| 4.; 5. |])

Execution result

val it : bool = true
>

val it : System.Reflection.PropertyInfo [] =
  [|Double x
      {Attributes = None;
       CanRead = true;
       CanWrite = false;
       CustomAttributes = seq
                            [[Microsoft.FSharp.Core.CompilationMappingAttribute((Microso
ft.FSharp.Core.SourceConstruct
Flags)4, (Int32)0)]];
       DeclaringType = FSI_0038+Point2D;
       GetMethod = Double get_x();
       IsSpecialName = false;
       MemberType = Property;
       MetadataToken = 385876070;
       Module = FSI-ASSEMBLY;
       Name = "x";
       PropertyType = System.Double;
       ReflectedType = FSI_0038+Point2D;
       SetMethod = null;};

    Double y
      {Attributes = None;
       CanRead = true;
       CanWrite = false;
       CustomAttributes = seq
                            [[Microsoft.FSharp.Core.CompilationMappingAttribute((Microso
ft.FSharp.Core.SourceConstruct
Flags)4, (Int32)1)]];
       DeclaringType = FSI_0038+Point2D;
       GetMethod = Double get_y();
       IsSpecialName = false;
       MemberType = Property;
       MetadataToken = 385876071;
       Module = FSI-ASSEMBLY;
       Name = "y";
       PropertyType = System.Double;
       ReflectedType = FSI_0038+Point2D;
       SetMethod = null;}|]
>

val fields : System.Reflection.PropertyInfo [] = [|Double x; Double y|]
val it : obj = 2.0

val it : obj = {x = 4.0;
                y = 5.0;}

Using Exceptions with F#

Although the exception is not an F# specific type, F#’s exception definition can be very different from a C# exception definition. Example 6-117 shows how to use F# reflection on exceptions—specifically, how to check the exception, get exception fields, and get exception field values.

Example 6-117. Using F# reflection on an exception

Source code

open Microsoft.FSharp.Reflection

exception MyException of int * int

let ex = MyException(1, 2)

// check if it is an exception representation
FSharpType.IsExceptionRepresentation(ex.GetType())

// get exception fields
FSharpType.GetExceptionFields(ex.GetType())

// get exception field values
FSharpValue.GetExceptionFields(ex)

Execution result

val it : bool = true
>

val it : System.Reflection.PropertyInfo [] =
  [|Int32 Data0
      {Attributes = None;
       CanRead = true;
       CanWrite = false;
       CustomAttributes = seq
                            [[Microsoft.FSharp.Core.CompilationMappingAttribute((Microsoft.
FSharp.Core.SourceConstruct
Flags)4, (Int32)0)]];
       DeclaringType = FSI_0046+MyException;
       GetMethod = Int32 get_Data0();
       IsSpecialName = false;
       MemberType = Property;
       MetadataToken = 385876082;
       Module = FSI-ASSEMBLY;
       Name = "Data0";
       PropertyType = System.Int32;
       ReflectedType = FSI_0046+MyException;
       SetMethod = null;};
    Int32 Data1
      {Attributes = None;
       CanRead = true;
       CanWrite = false;
       CustomAttributes = seq
                            [[Microsoft.FSharp.Core.CompilationMappingAttribute((Microsoft.
FSharp.Core.SourceConstruct
Flags)4, (Int32)1)]];
       DeclaringType = FSI_0046+MyException;
       GetMethod = Int32 get_Data1();
       IsSpecialName = false;
       MemberType = Property;
       MetadataToken = 385876083;
       Module = FSI-ASSEMBLY;
       Name = "Data1";
       PropertyType = System.Int32;
       ReflectedType = FSI_0046+MyException;
       SetMethod = null;}|]
>
val it : obj [] = [|1; 2|]

Function

F# provides a way to make a function by using reflection. Example 6-118 shows how to implement printfn.

Example 6-118. Using reflection to implement an F# function
open System

type FSV = Microsoft.FSharp.Reflection.FSharpValue
type FST = Microsoft.FSharp.Reflection.FSharpType

let notImpl<'T> : 'T = raise (NotImplementedException())
let printfn (fmt : Printf.TextWriterFormat<'T>) : 'T =
    let rec chain (ty : System.Type) : obj =
        if FST.IsFunction ty then
            let argTy, retTy = FST.GetFunctionElements ty
            FSV.MakeFunction(ty, (fun _ -> chain retTy))
        else
            if ty.IsValueType then Activator.CreateInstance(ty) else null

    chain typeof<'T> :?> 'T

let printf fmt = printfn fmt

Checking F# Types

This section presents a sample that returns the F# type using Microsoft.FSharp.Reflection functions and an active pattern. Example 6-119 detects the function, tuple, record, DU, and other types, such as .NET types.

Example 6-119. Checking F# types
// define the DU
type 'T ty =
    | Abbreviation of 'T list * string
    | Function of 'T ty * 'T ty
    | Record of string * (string * 'T) list
    | Tuple of 'T ty list
    | DU of string * (string * 'T list) list

// define the active patterns
let (| Fun | _ |) ty = if Reflection.FSharpType.IsFunction ty then Some ty else None
let (| Tup | _ |) ty = if Reflection.FSharpType.IsTuple ty then Some ty else None
let (| Rec | _ |) ty = if Reflection.FSharpType.IsRecord ty then Some ty else None
let (| Union | _ |) ty = if Reflection.FSharpType.IsUnion ty then Some ty else None

// function to get F# types
let rec typeofFun (myFunction) = function
    | Fun ty ->
        let arg, ret = Reflection.FSharpType.GetFunctionElements ty
        Function(typeofFun myFunction arg, typeofFun myFunction ret)
    | Tup ty ->
        Tuple ((Reflection.FSharpType.GetTupleElements ty )
               |> List.ofArray
               |> List.map (fun ty -> typeofFun myFunction ty ))
    | Rec ty ->
        Record(ty.Name,
                (Reflection.FSharpType.GetRecordFields ty)
                |> List.ofArray
                |> List.map (fun n -> (n.GetAccessors())
                                      |> List.ofArray
                                      |> List.map (fun acc -> (n.Name, acc)))
                |> List.collect id
                |> List.map (fun (n, acc) -> n, myFunction acc.ReturnType))
    | Union ty ->
        DU(ty.Name,
                (Reflection.FSharpType.GetUnionCases ty)
                |> List.ofArray
                |> List.map (fun n ->
                            let args = (n.GetFields())
                                       |> List.ofArray
                                       |> List.map (fun field -> myFunction field.
PropertyType)
                            n.Name, args))
    | ty ->
        Abbreviation(
                (ty.GetGenericArguments())
                |> List.ofArray
                |> List.map (fun ty -> myFunction ty),
                ty.FullName)

// function to be passed in the typeofFun
let f = fun (t:System.Type) -> t.Name

// code to get the types
let intType = typeof<int> |> typeofFun f
let tupleType = typeof<int*float> |> typeofFun f
let funType = typeof<int->string> |> typeofFun f
let funType2 = typeof<int->string->float32> |> typeofFun f
let intOption = typeof<int option> |> typeofFun f
let stringList = typeof<string list> |> typeofFun f

// print out result
[ intType; tupleType; funType; funType2; intOption; stringList; ]
|> Seq.iter (printfn "%A")

Execution result

Abbreviation ([],"System.Int32")
Tuple [Abbreviation ([],"System.Int32"); Abbreviation ([],"System.Double")]
Function (Abbreviation ([],"System.Int32"),Abbreviation ([],"System.String"))
Function
  (Abbreviation ([],"System.Int32"),
   Function (Abbreviation ([],"System.String"),Abbreviation ([],"System.Single")))
DU ("FSharpOption'1",[("None", []); ("Some", ["Int32"])])
DU ("FSharpList'1",[("Empty", []); ("Cons", ["String"; "FSharpList'1"])])

Working with Code Quotation

F# code quotation, which can be shortened to just quotation, is another way to expose source code information—even more information than reflection can provide. Reflection can give only the surface information of a function, such as return type or parameter type. However, the tree structure provided by a quotation can show the implementation details of a function.

You can choose whether or not to have the result of a quotation include type information. Example 6-120 shows how to declare a quotation with and without type information. The <@ @> is used to get the typed code quotation and the <@@ @@> is used to get the untyped code quotation. The result shows that expr is an Expr<int> type and that expr2 is an Expr (without a type).

Example 6-120. Defining a quotation

Source code

open Microsoft.FSharp.Quotations

// A typed code quotation.
let expr : Expr<float> = <@ 1.2  + 2.4 @>

// An untyped code quotation.
let expr2 : Expr = <@@ 1.2 + 2.4 @@>

Execution result

val expr : Quotations.Expr<int> =
  Call (None, op_Addition, [Value (1), Value (1)])

val expr2 : Quotations.Expr = Call (None, op_Addition, [Value (1), Value (1)])

For a complex function, the <@ @> and <@@ @@> are not the most convenient way to get the quotation. If that is the case, you can use the ReflectedDefinition attribute as shown in Example 6-121. Before F# 3.0, ReflectedDefinition could be applied only on a function. F# 3.0 now also supports this attribute on the module. If a module is decorated with this attribute, it works as though all functions within the module have this attribute.

Example 6-121. Using the ReflectedDefinition attribute

Source code

[<ReflectedDefinition>]
let ff a b =
    if a then b + 1 else b + 3

let q = <@ ff @>

Execution result

val ff : a:bool -> b:int -> int

val q : Expr<(bool -> int -> int)> =
  Lambda (a, Lambda (b, Call (None, ff, [a, b])))

The quotation is a tree structure, and you can iterate through it by using a recursive function. The function in Example 6-122 is a skeleton that can be used to go over the quotation tree. You can replace the () in each case to iterate through the expression tree. This function is a good starting point if you want to convert the F# code to another language, such as .NET IL code.

Example 6-122. Recursive function to access a quotation
let rec iterate exp
 match exp with
        | DerivedPatterns.Applications (e, ell) -> ()
        | DerivedPatterns.AndAlso (e0, e1) -> ()
        | DerivedPatterns.Bool e -> ()
        | DerivedPatterns.Byte e -> ()
        | DerivedPatterns.Char e -> ()
        | DerivedPatterns.Double e -> ()
        | DerivedPatterns.Int16 e-> ()
        | DerivedPatterns.Int32 e-> ()
        | DerivedPatterns.Int64 e -> ()
        | DerivedPatterns.OrElse (e0, e1)-> ()
        | DerivedPatterns.SByte e -> ()
        | DerivedPatterns.Single e -> ()
        | DerivedPatterns.String e -> ()
        | DerivedPatterns.UInt16 e -> ()
        | DerivedPatterns.UInt32 e -> ()
        | DerivedPatterns.UInt64 e -> ()
        | DerivedPatterns.Unit e -> ()
        | Patterns.AddressOf address -> ()
        | Patterns.AddressSet (exp0, exp1) -> ()
        | Patterns.Application (exp0, exp1) -> ()
        | Patterns.Call (expOption, mi, expList)  -> ()
        | Patterns.Coerce (exp, t)-> ()
        | Patterns.DefaultValue exp -> ()
        | Patterns.FieldGet (expOption, fi) -> ()
        | Patterns.FieldSet (expOption, fi, e) -> ()
        | Patterns.ForIntegerRangeLoop (v, e0, e1, e2) -> ()
        | Patterns.IfThenElse (con, exp0, exp1) -> ()
        | Patterns.Lambda (var,body) -> ()
        | Patterns.Let (var, exp0, exp1) -> ()
        | Patterns.LetRecursive (tupList, exp) -> ()
        | Patterns.NewArray (t, expList) -> ()
        | Patterns.NewDelegate (t, varList, exp) -> ()
        | Patterns.NewObject (t, expList) -> ()
        | Patterns.NewRecord (t, expList) -> ()
        | Patterns.NewObject (t, expList) -> ()
        | Patterns.NewRecord (t, expList) -> ()
        | Patterns.NewTuple expList -> ()
        | Patterns.NewUnionCase (t, expList) -> ()
        | Patterns.PropertyGet (expOption, pi, expList) -> ()
        | Patterns.PropertySet (expOption, pi, expList, e) -> ()
        | Patterns.Quote e -> ()
        | Patterns.Sequential (e0, e1) -> ()
        | Patterns.TryFinally (e0, e1) -> ()
        | Patterns.TryWith (e0, v0, e1, v1, e2) -> ()
        | Patterns.TupleGet (e, i) -> ()
        | Patterns.TypeTest (e, t) -> ()
        | Patterns.UnionCaseTest (e, ui) -> ()
        | Patterns.Value (obj, t) -> ()
        | Patterns.Var v -> ()
        | Patterns.VarSet (v, e) -> ()
        | Patterns.WhileLoop (e0, e1) -> ()
        | _ -> failwith "not supported pattern"

Working with the Observable Module

The event is a nice feature that can be used to notify others about something that happens elsewhere in the application. The .NET event is a delegate, which is similar to a function. According to the description on MSDN (http://msdn.microsoft.com/en-us/library/ee370313.aspx), the Observable module introduces a number of functions that make an event a first-class citizen. If you are not quite sure what I mean when I call it a first class citizen, Table 6-3 might give you a better idea. If you see the similarity between an event and data, you get the point.

Table 6-3. Comparison of the Observable module and the Seq module

Observable module

Seq module

// define an event
let myEvent = Event<int>()

myEvent.Publish
|> Observable.map (fun n -> n.ToString())
|> Observable.filter (fun n -> n<>"")
|> Observable.choose (fun n -> Some n)
// define a sequence
let myData = seq { 1.. 10 }

myData
|> Seq.map (fun n -> n.ToString())
|> Seq.filter (fun n -> n<>"")
|> Seq.choose (fun n -> Some n)

So I’ll explain more about the Observable module, starting with the partition function. Example 6-123 fires 10 events with arguments 1 to 10. Depending on the value of the provided argument, the events are partitioned or classified into two kinds of events: oddNumEvent and evenNumEvent. The event-handler function prints out the number in the end. This sample shows how the Observable module functions can process events as if they were data.

Example 6-123. Observable module partition function
// define an event
let myEvent = Event<int>()

// depending on the value, the event will be given a different label
let evenNumEvent, oddNumEvent =
        myEvent.Publish
        |> Observable.partition (fun n -> n % 2=0)

//set even-number event handler
evenNumEvent.Add(fun i -> printfn "even number %d triggered event" i)

//set odd-number event handler
oddNumEvent.Add(fun i -> printfn "odd number %d triggered event" i)

//fire 10 events with arguments from 1 to 10
[1..10]
|> Seq.iter myEvent.Trigger

Execution result

odd number 1 triggered event
even number 2 triggered event
odd number 3 triggered event
even number 4 triggered event
odd number 5 triggered event
even number 6 triggered event
odd number 7 triggered event
even number 8 triggered event
odd number 9 triggered event
even number 10 triggered event

Example 6-124 shows how to filter out the even-number event by using Observable.filter. The event can be filtered in much the same way as data is filtered in a list!

Example 6-124. Filtering out even numbers from events
// define an event
let myEvent = Event<int>()

// depending on the value, the event will be given a different label
let evenNumEvent, oddNumEvent =
        myEvent.Publish
        |> Observable.filter (fun n-> n % 2 = 0)
        |> Observable.partition (fun n -> n % 2 = 0)

//set even-number event handler
evenNumEvent.Add(fun i -> printfn "even number %d triggered event" i)

//set odd-number event handler
oddNumEvent.Add(fun i -> printfn "odd number %d triggered event" i)

//fire 10 events with arguments from 1 to 10
[1..10]
|> Seq.iter myEvent.Trigger

Execution result

even number 2 triggered event
even number 4 triggered event
even number 6 triggered event
even number 8 triggered event
even number 10 triggered event

The partitioned events can be merged, and the event arguments can be transformed as well. Example 6-125 merges the oddNumEvent and evenNumEvent and converts the integer argument to a float.

Example 6-125. Merging and converting the event
// define an event
let myEvent = Event<int>()

// depending on the value, the event will be given a different label
let evenNumEvent, oddNumEvent =
     myEvent.Publish
        |> Observable.partition (fun n -> n % 2 = 0)

let merged = Observable.merge evenNumEvent oddNumEvent
merged
|> Observable.map float
|> Observable.add (fun floatValue -> printfn "got value %f" floatValue)

//set even-number event handler
evenNumEvent.Add(fun i -> printfn "even number %d triggered event" i)

//set odd-number event handler
oddNumEvent.Add(fun i -> printfn "odd number %d triggered event" i)

//fire 5 events with arguments from 1 to 5
[1..5]
|> Seq.iter myEvent.Trigger

Execution result

got value 1.000000
odd number 1 triggered event
got value 2.000000
even number 2 triggered event
got value 3.000000
odd number 3 triggered event
got value 4.000000
even number 4 triggered event
got value 5.000000
odd number 5 triggered event

If you compare the sample code here and the code in the seq module, they are very similar. As mentioned a few times in this section, the Observable module makes the event processing pretty much like data processing. The reason I’ve mentioned it a few times is that this is something that other .NET languages do not provide without help from other libraries.

Using Lazy Evaluation, Partial Functions, and Memoization

One of the functional programming features F# provides is lazy evaluation or lazy computation. F# ships with a lazy keyword and a Lazy module that can be used to define lazy computations. The reason to use lazy evaluation is that the result is computed when it is needed. This can often improve performance.

The quickest way to define a lazy computation is to use the lazy keyword. Example 6-126 shows a sample that demonstrates the syntax of the lazy keyword. From the execution result, you can see that the myLazy variable has a type of Lazy<int>, where the int is the type from the expression (1+1). The actual value 2 is not created immediately.

Example 6-126. The lazy syntax and sample
// lazy syntax : let variable = lazy ( expression )
let myLazy = lazy ( 1 + 1 )

// lazy variable with explicit type info
let a:Lazy<int> = lazy 1

Execution result

val myLazy : Lazy<int> = Value is not created.
val a : Lazy<int> = Value is not created.

When the computation value is needed, you can invoke the Force method. This causes the computation to be executed a single time. Additional calls to Force do not trigger the computation, but simply return the value that was computed during the first execution. Example 6-127 shows how to use Force and how the variable state changes.

Example 6-127. Force the computation on lazy
// define a lazy computation
let myLazy = lazy(1 + 1)
printfn "myLazy is %A" myLazy

// force computation
let computedValue = myLazy.Force()
printfn "computed value = %d" computedValue
printfn "myLazy is %A" myLazy

Execution result

myLazy is Value is not created.
computed value = 2
myLazy is 2

The lazy keyword is good at wrapping a value. For creating a lazy computation from a function, you can use Lazy.Create. Example 6-128 computes the sum from a given number down to 0 by using Lazy.Create.

Example 6-128. Wrapping a function using Lazy.Create
// create a lazy computation from a function
let myLazy n = Lazy.Create (fun () ->
    let rec sum n =
        match n with
        | 0 -> 0
        | 1 -> 1
        | n -> n + sum (n - 1)
    sum n)

let lazyVal = myLazy 10
printfn "%d" (lazyVal.Force())

Note

The preceding code can potentially cause a stack-overflow exception, because it is not tail recursive. The following code is a tail-recursive version, which won’t cause a stack overflow.

let myLazy n = Lazy.Create (fun () ->
    let rec sum acc n =
        match n with
        | 0 -> 0
        | 1 -> acc + 1
        | n -> sum (acc + n) (n - 1)
    sum 0 n)

The other way to caching the value is to use a partial function. F# automatically caches the value from any function that takes no parameter. Example 6-129 and Example 6-130 show a nonpartial function and how to define a partial function. The data in a nonpartial function is created each time the function is invoked, while the partial function caches the data and the data is created only once.

Example 6-129. Nonpartial function definition
let f x =
    let data =
        printfn "the data is created"
        [1; 2; 3; 4]
    data |> Seq.tryFind ( (=) x )

f 1
f 2
f 3
f 5

Execution result

the data is created
the data is created
the data is created
the data is created

val f : x:int -> int option
val it : int option = None
Example 6-130. Partial function definition
let f2 =
    let data =
        printfn "the data is created"
        [1; 2; 3; 4]
    fun x -> data |> Seq.tryFind ( (=) x )

f2 1
f2 2
f2 3
f2 5

Execution result

the data is created

val f2 : (int -> int option)
val it : int option = None

Note

For a C# developer, the function without a parameter is something like f(). However, the function f() has one parameter, which is unit.

Memoization is way to cache an intermediate result to speed up the computation. Memoization uses a lookup table such as Dictionary<TKey, TValue> to cache the intermediate computation result. Example 6-131 shows the nonmemoization and memoization versions of Fibonacci code. To get the performance data, you need to run "#time" in FSI. From the execution result, the nonmemoization version takes much longer than the memoization version.

Example 6-131. Memoization and nonmemoization functions
// non-memoization version
let rec fibonacci n =
    match n with
    | 0 | 1 -> 1
    | _ -> ( fibonacci (n - 1) ) + ( fibonacci (n - 2) )

// memoization version
let rec fibonacci2=
    let lookup = System.Collections.Generic.Dictionary<_, _>()
    fun n ->
        match lookup.TryGetValue n with
        | true, v -> v
        | _ ->
            let a =
                match n with
                | 0 | 1 -> 1
                | _ -> ( fibonacci2 (n - 1) ) + ( fibonacci2 (n - 2) )
            lookup.Add(n, a)
            a

Execution result in FSI

> #time;;

--> Timing now on

> fibonacci 45;;
Real: 00:00:14.697, CPU: 00:00:14.640, GC gen0: 0, gen1: 0, gen2: 0
val it : int = 1836311903

> fibonacci2 45;;
Real: 00:00:00.001, CPU: 00:00:00.000, GC gen0: 0, gen1: 0, gen2: 0
val it : int = 1836311903

You can derive a more general function for a memoization function, as shown in Example 6-132.

Example 6-132. A more general memoization function
let mem f =
    let lookup = System.Collections.Generic.Dictionary<_, _>()
    fun n ->
        match lookup.TryGetValue n with
        | true, v -> v
        | _ ->
            let a = f n
            lookup.Add(n, a)
            a

let rec fibonacci3 = mem (fun n ->
                            match n with
                            | 0 | 1 -> 1
                            | _ -> (fibonacci3 (n - 1)) + (fibonacci3 (n - 2)))

Note

Example 6-132 generates a warning: “This and other recursive references to the object(s) being defined will be checked for initialization-soundness at runtime through the use of a delayed reference. This is because you are defining one or more recursive objects, rather than recursive functions. This warning might be suppressed by using '#nowarn "40"' or '--nowarn:40'.” This warning is to prevent the user from writing the following code, which will generate a runtime exception:

let init f = f()
let rec foo = init (fun() -> foo : obj)

Example 6-132 is not a template you can pass in any recursive function. Thanks to Uladzimir on our team, a new version is shown in Example 6-133.

Example 6-133. Memoization code template
let memorize wrapFunction =
    let cache = System.Collections.Generic.Dictionary()
    let rec f x =
        match cache.TryGetValue x with
        | true, v -> v
        | false, _ ->
            let v = wrapFunction f x
            cache.[x] <- v
            v
    f

let fib =
    memorize (
        fun f x ->
            if x < 2 then 1
            else f(x - 1) + f(x - 2)
    )

fib 45

Summary

For C# developer, the first three chapters introduce the imperative and object-oriented features provided by F#. This chapter introduces unique F# features that are not provided by C#. These new language features make F# development more efficient and allow you to develop applications that contain fewer bugs. A few examples include the unit-of-measure and pattern-matching feature. In the later chapters in this book, you can see more complex samples that use these features.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset