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.
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.
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.
// 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.
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.
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.
// 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.
// 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" }
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.
// 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
// 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" }
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.
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
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.
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.
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.
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.
// function only returns even-number option let filterOutOddNumber a = if a % 2 = 0 then Some(a) else None
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.
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...";
// 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!"
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.
[<AllowNullLiteral>] type NullableType() = member this.TestNullable(condition) = if condition then NullableType() else null
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.
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.
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.
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
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.
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.
// 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>
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.
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.
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
.
// 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
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.
// 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.
// 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.
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.
// 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" }
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.
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";}
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.
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
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.
// 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
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.
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.
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.
// 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
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.
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.
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.
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.
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"
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.
// 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.
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
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.
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.
> (1 + 2, 1) = (3, 1);; val it : bool = true > hash (1+2,1) = hash (3,1);; val it : bool = true
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.
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";}
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.
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.
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"
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.
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.
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)
"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.
> 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).
Like a tuple, patterns can also decompose a list or array into values. Example 6-43 decomposes an array and gets the array length.
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.
let rec listLength list = match list with | head :: tail -> 1 + (listLength tail) | [] -> 0
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.
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 | _ -> ()
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.
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
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.
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.
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
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.
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.
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"
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
.
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"
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.
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.
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"
The input parameter must be the superclass of the type in the pattern. In Example 6-54, float and int are derived from obj.
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.
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.
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.
// 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"
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.
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.
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.
// 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.
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.
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!"
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.
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"
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.
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.
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-63 shows how to catch an exception in F#.
open System
let exceptionCatch() =
try
let a = 3 / 0 //divide by zero exception
printfn "%d" a
with
| :? DivideByZeroException -> printfn "divided by zero"
exceptionCatch()
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.
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.
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
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.
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.
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.
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.
// 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.
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.
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.
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.
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.
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.
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()
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.
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.
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
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.
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
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.
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.
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(<)
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.
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.
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.
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.
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.
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
caught error: System.Exception: my error at [email protected](Exception _arg1) at [email protected](AsyncParams'1 args)
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.
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.
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()
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.
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
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.
// 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|])
[|1; 2; 3; 4; 5; 5; 7; 8; 9|]
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.
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) }
The code in Example 6-87 is for demo purposes. Changing a delegate to an asynchronous operation does not always improve the performance.
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.)
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.
// 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!
// 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.
// 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.
// 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.
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()
current state is Gold current balance is 11220.00M
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?
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.
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); ...]
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.
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.
// 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.
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.
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
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.
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
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.
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.
// 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 }
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.
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)
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.
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)
the above code generate a warning, the warning can be eliminated by adding |> ignore in the end.
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) }
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)
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.
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
}
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.
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.
myseq { for i in 1 .. 10 do merge j in [5 .. 15] whenever (i = j) yield j }
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.
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.
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)
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.
Example 6-109 shows how to define a customized 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.
// 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.
// 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
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.
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).
// 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
Created a student object
new student info: name = Brent, id = 18
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.
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.
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)
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.
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. |])
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.
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;}
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.
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|]
F# provides a way to make a function by using reflection. Example 6-118 shows how to implement printfn.
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
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.
// 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")
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"])])
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).
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.
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.
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"
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.
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.
// 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
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!
// 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.
// 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.
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.
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.
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.
// 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())
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.
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.
// 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.
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)))
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.
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
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.