Chapter 7. The F# Libraries

Although F# can use all the classes available in the .NET BCL, it also ships with its own set of libraries.

The F# libraries are split into two, FSharp.Core.dll, which is also referred to as the F# core library or the native F# library, and FSharp.PowerPack.dll, sometimes just referred to as the power pack. The F# core library contains everything that the F# compiler really needs to work. For example, it contains the Tuple class that is used when you use a tuple. The power pack is designed to supplement the F# core library with other useful functions. There are two primary reasons for splitting the library in two: first, an effort was made to try and keep the core library as small as possible, so if application developers were making an effort to keep the footprint small the dependency on FSharp.Core.dll would not cause too much trouble. Secondly, and perhaps more importantly, to try and keep FSharp.Core.dll as stable as possible, so that it will only change with new releases of the compiler and allow the F# team to ship new versions of FSharp.PowerPack.dll, with interesting new features, more often.

The objective of this chapter is not to completely document every nuance of every F# library type and function. It is to give you an overview of what the modules can do, with a particular focus on features that aren't readily available in the BCL. The F# online documentation (http://msdn.microsoft.com/fsharp) is the place to find detailed documentation about each function.

The Native F# Library FSharp.Core.dll

The native F# library contains all the classes that you need to make the compiler work, such as the definition of the type into which F#'s list literal compiles. I'll cover the following modules:

  • Microsoft.FSharp.Core.Operators: A module containing functions that are mathematical operators.

  • Microsoft.FSharp.Reflection: A module containing functions that supplement the .NET Framework's reflection classes to give a more accurate view of F# types and values.

  • Microsoft.FSharp.Collections.Seq: A module containing functions for any type that supports the IEnumerable interface.

  • Microsoft.FSharp.Text.Printf: A module for formatting strings.

  • Microsoft.FSharp.Control.Event: A module for working with events in F#.

The Microsoft.FSharp.Core.Operators Module

In F#, operators are defined by libraries rather than built into the language. This module contains some of the language's operators. It also contains some useful operators such as functions, and it is these that I'll be covering here. The module is open by default, which means the user can use these functions with no prefix. Specifically, I will cover the following types of functions:

  • Arithmetic operators: Operators for basic arithmetic operations such as addition and subtraction.

  • Floating-point arithmetic functions: More advanced arithmetic functions including logarithms and trigonometry.

  • Mutable integer functions: Functions on mutable integers.

  • Tuple functions: Functions on tuples.

  • Conversion functions: Functions for converting between primitive types, such as strings, floats and integers.

Arithmetic Operators

As already covered in Chapter 2, F# operators can be defined by the programmer, so all the arithmetic operators are defined in the Operators module rather than built into the language. Therefore, the majority of operators that you will use in your day-to-day programming in F# are defined in the Operators module. I imagine that operators such as + and – need little explanation, since their usage is straightforward:

let x1 = 1 + 1
let x2 = 1 − 1

By default, F# operators are unchecked, which means that if a value is too big then it will wrap. Therefore, this is the value that will become the smallest possible value, rather than causing an error. If you would prefer to use checked operators that raise an exception when a value overflows you can do so by opening the Microsoft.FSharp.Core.Operators.Checked:

open Microsoft.FSharp.Core.Operators.Checked
let x = System.Int32.MaxValue + 1

The above example will now throw an error when executed, where as if the module Microsoft. FSharp.Core.Operators.Checked was not open the value in x would simply be wrapped to −2147483648.

The F# equality operator is a bit more subtle than most of the other arithmetic operators. This is because in F# equality is structural equality, meaning that the contents of the objects are compared to check whether the items that make up the object are the same. This is opposed to referential equality, which determines whether two identifiers are bound to the same object or the same physical area of memory. A referential equality check can be performed using the method obj.ReferenceEquals. The structural equality operator is =, and the structural inequality operator is <>. The next example demonstrates this. The records robert1 and robert2 are equal, because even though they are separate objects, their contents are the same. On the other hand, robert1 and robert3 are not equal because their contents are different.

type person = { name : string ; favoriteColor : string }

let robert1 = { name = "Robert" ; favoriteColor = "Red" }
let robert2 = { name = "Robert" ; favoriteColor = "Red" }
let robert3 = { name = "Robert" ; favoriteColor = "Green" }

printfn "(robert1 = robert2): %b" (robert1 = robert2)
printfn "(robert1 <> robert3): %b" (robert1 <> robert3)

The results of this code, when compiled and executed, are as follows:

(robert1 = robert2): true
(robert1 <> robert3): true

Structural comparison is also used to implement the > and < operators, which means they too can be used to compare F#'s record types. This is demonstrated here:

type person = { name : string ; favoriteColor : string }

let robert2 = { name = "Robert" ; favoriteColor = "Red" }
let robert3 = { name = "Robert" ; favoriteColor = "Green" }

printfn "(robert2 > robert3): %b" (robert2 > robert3)

The results of this code, when compiled and executed, are as follows:

(robert2 > robert3): true

If you need to determine whether two objects are physically equal, then you can use the PhysicalEquality function available in the LanguagePrimitives module, as in the following example:

type person = { name : string ; favoriteColor : string }

let robert1 = { name = "Robert" ; favoriteColor = "Red" }
let robert2 = { name = "Robert" ; favoriteColor = "Red" }

printfn "(LanguagePrimitives.PhysicalEquality robert1 robert2): %b"
    (LanguagePrimitives.PhysicalEquality robert1 robert2)

Floating-Point Arithmetic Functions

The Operators module also offers a number of functions (see Table 7-1) specifically for floating-point numbers, some of which are used in the following sample:

printfn "(sqrt 16.0): %f" (sqrt 16.0)
printfn "(log 160.0): %f" (log 160.0)
printfn "(cos 1.6): %f" (cos 1.6)

The results of this code, when compiled and executed, are as follows:

(sqrt 16.0): 4.000000
(log 160.0): 5.075174
(cos 1.6): −0.029200

Table 7.1. Arithmetic Functions for Floating-Point Numbers

Function

Description

abs

Returns the absolute value of the argument

acos

Returns the inverse cosine (arccosine) of the argument, which should be specified in radians

asin

Returns the inverse sine (arcsine) of the argument, which should be specified in radians

atan

Returns the inverse tangent (arctangent) of the argument, which should be specified in radians

atan2

Returns the inverse tangent (arctangent) of the two arguments, which should both be specified in radians

ceil

Returns the next highest integer value by rounding up the value if necessary; the value returned is still of type float

floor

Returns the next lowest integer value by rounding up the value if necessary; the value returned is still of type float

exp

Returns the exponential

infinity

Returns the floating-point number that represents infinity

log

Returns the natural log of the floating-point number

log10

Returns the base 10 log of the floating-point number

nan

Returns the floating-point number that represents "not a number"

sqrt

Returns the square root of the number

cos

Returns the cosine of the parameter, which should be specified in radians

cosh

Returns the hyperbolic cosine of the parameter, which should be specified in radians

sin

Returns the sine of the parameter, which should be specified in radians

sinh

Returns the hyperbolic sine of the parameter, which should be specified in radians

tan

Returns the tangent of the parameter, which should be specified in radians

tanh

Returns the hyperbolic tangent of the parameter, which should be specified in radians

truncate

Returns the parameter converted to an integer

float

Takes an integer and returns it as a float

float32

Takes an integer and returns it as a float32

Tuple Functions

The Operators module also offers two useful functions that operate on tuples. You can use the functions fst and snd to break up a tuple with two items in it. The following example demonstrates their use:

printfn "(fst (1, 2)): %i" (fst (1, 2))
printfn "(snd (1, 2)): %i" (snd (1, 2))

The results of this code are as follows:

(fst (1, 2)): 1
(snd (1, 2)): 2

The Conversion Functions

The operator module offers a number of overload functions for converting between the primitive types. For example, the function float is overload to convert from a string or integer type to a floating point number, a System.Double. The following example shows how to convert from an enumeration to an integer and then convert it back to an enumeration. Converting from an enumeration to an integer is straightforward. You just use the int function. Converting back is slightly more complicated; you use the enum function, but you must provide a type annotation so that the compile knows which type of enumeration to convert it to. You can see this in the following sample where you add the annotation DayOfWeek to the identifier dayEnum:

open System

let dayInt = int DateTime.Now.DayOfWeek
let (dayEnum : DayOfWeek) = enum dayInt

printfn "%i" dayInt
printfn "%A" dayEnum

The results of this code, when compiled and executed, are as follows:

0
Sunday

The Logical Or and And Operators

The other common tasks that you need to perform with enumerations is to combine them using a logical "or" and then test them using a logical "and." Enum types marked with the System.Flags attribute support the use of the &&& and ||| operators to perform these operations directly. For example, you can use ||| operator to combine several enum values. You can test to see if value is part of an enum using the &&& operators in the form v1 &&& v2 <> enum 0:

open System.Windows.Forms

let anchor = AnchorStyles.Left ||| AnchorStyles.Left

printfn "test AnchorStyles.Left: %b"
    (anchor &&& AnchorStyles.Left <> enum 0)
printfn "test AnchorStyles.Right: %b"
    (anchor &&& AnchorStyles.Right <> enum 0)

The Microsoft.FSharp.Reflection Module

This module contains F#'s own version of reflection. F# contains some types that are 100 percent compatible with the CLR type system, but aren't precisely understood with .NET reflection. For example, F# uses some sleight of hand to implement its union type, and this is transparent in 100 percent F# code. It can look a little strange when you use the BCL to reflect over it. The F# reflection system addresses this kind of problem. But, it blends with the BCL's System.Reflection namespace, so if you are reflecting over an F# type that uses BCL types, you will get the appropriate object from the System.Reflection namespace.

In F#, you can reflect over types or over values. The difference is a bit subtle and is best explained with an example. Those of you familiar with .NET reflection might like to think of reflection over types as using the Type, EventInfo, FieldInfo, MethodInfo, and PropertyInfo types and reflections over values as calling their members, such as GetProperty or InvokeMember to get values dynamically. Yet reflection over values offers a high-level, easy-to-use system.

  • Reflection over types: Lets you examine the types that make up a particular value or type.

  • Reflection over values: Let you examine the values that make up a particular composite value.

Reflection Over Types

The following example shows a function that will print the type of any tuple:

open Microsoft.FSharp.Reflection

let printTupleTypes (x: obj) =
    let t = x.GetType()
    if FSharpType.IsTuple t then
        let types = FSharpType.GetTupleElements t
        printf "("
        types
        |> Seq.iteri
            (fun i t ->
            if i <> Seq.length types - 1 then
                printf " %s * " t.Name
            else
                printf "%s" t.Name)
        printfn " )"
    else
        printfn "not a tuple"

printTupleTypes ("hello world", 1)

First, you use the objects GetType method to get the System.Type that represents the object. You can then use this value with the function FSharpType.IsTuple to get to test if it is a tuple. You then use function FSharpType.GetTupleElements to get an array of System.Type that describes the elements that make up the tuple. These could represent F# types, so you could recursively call the function to investigate what they are. In this case, you know they are types from the .NET BCL you simply print out the type names. This means when compiled and run, the sample outputs the following:

( String * Int32 )

Reflection Over Values

Imagine instead of displaying the types of a tuple that you wanted to display the values that make up the tuple. To do this, you would use reflection over values, and you would need to use the function FSharpValue.GetTupleFields to get an array of objects that are the values that make up the tuple. These objects could be tuples, or other F# types, so you could recursively call the function to print out the objects value. However, in this case, you know there are fundamental values from the BCL library, so you simply use the F# printfn function to print them out. The F# printf module is described later in the chapter. The following example implements such a function:

open Microsoft.FSharp.Reflection
let printTupleValues (x: obj) =
    if FSharpType.IsTuple(x.GetType()) then
        let vals = FSharpValue.GetTupleFields x
        printf "("
        vals
        |> Seq.iteri
            (fun i v ->
                if i <> Seq.length vals - 1 then
                    printf " %A, " v
                else
                    printf " %A" v)
        printfn " )"
    else
        printfn "not a tuple"

printTupleValues ("hello world", 1)

The result of this code, when compiled and executed, is as follows:

( "hello world", 1 )

Reflection is used both within the implementation of fsi, the interactive command-line tool that is part of the F# tool suite, and within the F# library the printf function family. If you want to learn more about the way you can use reflection, take a look at the source for printf, available in the distribution in the files sourcefsharpprintf.fs and sourcefsharplayout.ml.

The Microsoft.FSharp.Collections.Seq Module

The Microsoft.FSharp.Collections.Seq module contains functions that work with any collection that supports the IEnumerable interface, which is most of the collections in the .NET Framework's BCL. The module is called Seq because F# gives the alias seq to the IEnumerable interface to shorten it and make it easier to type and read. This alias is used when type definitions are given.

Note

FSLib contains several modules designed to work with various types of collections. These include Array, Array2 (two-dimensional arrays), Array3 (three-dimensional arrays), Hashtbl (a hash table implementation), IEnumerable, LazyList, List, Map, and Set. I'll cover only Seq because it should generally be favored over these collections because of its ability to work with lots of different types of collections. Also, although each module has functions that are specific to it, many functions are common to them all.

Some of these functions can be replaced by the list comprehension syntax covered in Chapters 3 and 4. For simple tasks and working with untyped collections, it's generally easier to use list comprehension, but for more complicated tasks you will want to stick to these functions. You will take a look at the following functions:

map and iter:

These two functions let you apply a given function to every item in the collection.

concat:

This function lets you concatenate a collection of collections into one collection.

fold:

This function lets you create a summary of a list by folding the items in the collection together.

exists and forall:

These functions let you make assertions about the contents of a collection.

filter, find and tryFind:

These functions let you pick elements in the list that meet certain conditions.

choose:

This function lets you perform a filter and map at the same time.

init and initInfinite:

These functions let you initialize collections.

unfold:

This provides a more flexible way to initialize lists.

cast:

This is a way to convert from the nongeneric version of IEnumerable, rather than IEnumerable<T>.

The map and iter Functions

You'll look at map and iter first. These apply a function to each element in a collection. The difference between them is that map is designed to create a new collection by transforming each element in the collection, while iter is designed to apply an operation that has a side effect to each item in the collection. A typical example of a side effect would be writing the element to the console. The following example shows both map and iter in action:

let myArray = [|1; 2; 3|]

let myNewCollection =
    myArray |>
    Seq.map (fun x -> x * 2)

printfn "%A" myArray

myNewCollection |> Seq.iter (fun x -> printf "%i ... " x)

The results of this code, when compiled and executed, are as follows:

[|1; 2; 3|]
2 ... 4 ... 6 ...

The concat Function

The previous example used an array, because it was convenient to initialize this type of collection, but you could use any of the collection types available in the BCL. The next example uses the List type provided in the System.Collections.Generic namespace and demonstrates how to use the concat function, which has type #seq< #seq<'a> > -> seq<'a> and which collects IEnumerable values into one IEnumerable value:

open System.Collections.Generic

let myList =
    let temp = new List<int[]>()
    temp.Add([|1; 2; 3|])
    temp.Add([|4; 5; 6|])
    temp.Add([|7; 8; 9|])
    temp

let myCompleteList = Seq.concat myList

myCompleteList |> Seq.iter (fun x -> printf "%i ... " x)

The results of this code, when compiled and executed, are as follows:

1 ... 2 ... 3 ... 4 ... 5 ... 6 ... 7 ... 8 ... 9 ...

The fold Function

The next example demonstrates the fold function, which has type ('b -> 'a -> 'b) -> 'b -> #seq<'a> -> 'b. This is a function for creating a summary of a collection by threading an accumulator value through each function call. The function takes two parameters. The first of these is an accumulator, which is the result of the previous function, and the second is an element from the collection. The function body should combine these two values to form a new value of the same type as the accumulator. In the next example, the elements of myPhrase are concatenated to the accumulator so that all the strings end up combined into one string.

let myPhrase = [|"How"; "do"; "you"; "do?"|]

let myCompletePhrase =
    myPhrase |>
    Seq.fold (fun acc x -> acc + " " + x) ""

printfn "%s" myCompletePhrase

The result of this code, when compiled and executed, is as follows:

How do you do?

The exists and forall Functions

The next example demonstrates two functions that you can use to determine facts about the contents of collections. These functions are exists and forall, which both have the type ('a -> bool) -> #seq<'a> -> bool. You can use the exists function to determine whether any element in the collection exists that meets certain conditions. The conditions that must be met are determined by the function passed to exists, and if any of the elements meet this condition, then exists will return true. The function forall is similar except that all the elements in the collection must meet the condition before it will return true. The following example first uses exists to determine whether there are any elements in the collections that are multiples of 2 and then uses forall to determine whether all items in the collection are multiples of 2:

let intArray = [|0; 1; 2; 3; 4; 5; 6; 7; 8; 9|]

let existsMultipleOfTwo =
    intArray |>
    Seq.exists (fun x -> x % 2 = 0)

let allMultipleOfTwo =
    intArray |>
    Seq.forall (fun x -> x % 2 = 0)

printfn "existsMultipleOfTwo: %b" existsMultipleOfTwo
printfn "allMultipleOfTwo: %b" allMultipleOfTwo

The results of this code, when compiled and executed, are as follows:

existsMultipleOfTwo: true
allMultipleOfTwo: false

The filter, find, and tryFind Functions

The next example looks at three functions that are similar to exists and forall. These functions are filter of type ('a -> bool) -> #seq<'a> -> seq<'a>, find of type ('a -> bool) -> #seq<'a> -> 'a and tryfind of type ('a -> bool) -> #seq<'a> -> 'a option. They are similar to exists and forall, because they use functions to examine the contents of a collection. Instead of returning a Boolean, these functions actually return the item or items found. The function filter uses the function passed to it to check every element in the collection. The filter function then returns a list that contains all the elements that have met the condition of the function. If no elements meet the condition, then an empty list is returned. The functions find and tryfind both return the first element in the collection to meet the condition specified by the function passed to them. Their behavior is altered when no element in the collection meets the condition. find throws an exception, whereas tryfind returns an option type that will be None if no element is found. Since exceptions are relatively expensive in .NET, you should prefer tryfind over find.

In the following example, you'll look through a list of words. First, you use filter to create a list containing only the words that end in at. Then you'll use find to find the first word that ends in ot. Finally, you'll use tryfind to check whether any of the words end in tt.

let shortWordList = [|"hat"; "hot"; "bat"; "lot"; "mat"; "dot"; "rat";|]

let atWords =
    shortWordList
    |> Seq.filter (fun x -> x.EndsWith("at"))

let otWord =
    shortWordList
    |> Seq.find (fun x -> x.EndsWith("ot"))

let ttWord =
    shortWordList
    |> Seq.tryFind (fun x -> x.EndsWith("tt"))

atWords |> Seq.iter (fun x -> printf "%s ... " x)
printfn ""
printfn "%s" otWord
printfn "%s" (match ttWord with | Some x -> x | None -> "Not found")

The results of this code, when compiled and executed, are as follows:

hat ... bat ... mat ... rat ...
hot
Not found

The choose Function

The next Seq function you'll look at is a clever function that allows you to do a filter and a map at the same time. This function is called choose and has the type ('a -> 'b option) -> #seq<'a> -> seq<'b>. To do this, the function that is passed to choose must return an option type. If the element in the list can be transformed into something useful, the function should return Some containing the new value. When the element is not wanted, the function returns None.

In the following example, you'll take a list of floating-point numbers and multiply them by 2. If the value is an integer, it is returned. Otherwise, it is filtered out. This leaves you with just a list of integers.

let floatArray = [|0.5; 0.75; 1.0; 1.25; 1.5; 1.75; 2.0 |]

let integers =
    floatArray |>
    Seq.choose
        (fun x ->
            let y = x * 2.0
            let z = floor y
            if y - z = 0.0 then
                Some (int z)
            else
                None)

integers |> Seq.iter (fun x -> printf "%i ... " x)

The results of this code, when compiled and executed, are as follows:

1 ... 2 ... 3 ... 4 ...

The init and initInfinite Functions

Next, you'll look at two functions for initializing collections, init of type int -> (int -> 'a) -> seq<'a> and initInfinite of type (int -> 'a) -> seq<'a>. You can use the function initFinite to make a collection of a finite size. It does this by calling the function passed to it the number of times specified by the number passed to it. You can use the function initInfinite to create a collection of an infinite size. It does this by calling the function passed to it each time it is asked for a new element this way. In theory, a list of unlimited size can be created, but in reality you are constrained by the limits of the machine performing the computation.

The following example shows init being used to create a list of ten integers, each with the value 1. It also shows a list being created that should contain all the possible 32-bit integers and demonstrates using the function take to create a list of the first ten.

let tenOnes = Seq.init 10 (fun _ -> 1)
let allIntegers = Seq.initInfinite (fun x -> System.Int32.MinValue + x)
let firstTenInts = Seq.take 10 allIntegers

tenOnes |> Seq.iter (fun x -> printf "%i ... " x)
printfn ""
printfn "%A" firstTenInts

The results of this code, when compiled and executed, are as follows:

1 ... 1 ... 1 ... 1 ... 1 ... 1 ... 1 ... 1 ... 1 ... 1 ...
[-2147483648; −2147483647; −2147483646; −2147483645; −2147483644; −2147483643;
 −2147483642; −2147483641; −2147483640; −2147483639]

The unfold Function

You already met unfold in Chapter 3. It is a more flexible version of the functions init and initInfinite. The first advantage of unfold is that it can be used to pass an accumulator through the computation, which means you can store some state between computations and do not simply have to rely on the current position in the list to calculate the value, like you do with init and initInfinite. The second advantage is that it can be used to produce a list that is either finite or infinite. Both of these advantages are achieved by using the return type of the function passed to unfold. The return type of the function is 'a * 'b option, meaning an option type that contains a tuple of values. The first value in the option type is the value that will be placed in the list, and the second is the accumulator. If you want to continue the list, you return Some with this tuple contained within it. If want to stop it, you return None.

The following example, repeated from Chapter 2, shows unfold being used to compute the Fibonacci numbers. You can see the accumulator being used to store a tuple of values representing the next two numbers in the Fibonacci sequence. Because the list of Fibonacci numbers is infinite, you never return None.

let fibs =
    (1,1) |> Seq.unfold
        (fun (n0, n1) ->
            Some(n0, (n1, n0 + n1)))

let first20 = Seq.take 20 fibs
printfn "%A" first20

The results of this code, when compiled and executed, are as follows:

[1; 1; 2; 3; 5; 8; 13; 21; 34; 55; 89; 144; 233; 377; 610; 987;
 1597; 2584; 4181; 6765]

The example demonstrates using unfold to produce a list that terminates. Imagine you want to calculate a sequence of numbers where the value decreases by half its current value, such as a nuclear source decaying. Imagine beyond a certain limit the number becomes so small that you are no longer interested in it. You can model such a sequence in the following example by returning None when the value has reached its limit:

let decayPattern =
    Seq.unfold
        (fun x ->
            let limit = 0.01
            let n = x - (x / 2.0)
if n > limit then
                Some(x, n)
            else
                None)
        10.0

decayPattern |> Seq.iter (fun x -> printf "%f ... " x)

The results of this code, when compiled and executed, are as follows:

10.000000 ... 5.000000 ... 2.500000 ... 1.250000 ...
0.625000 ... 0.312500 ... 0.156250 ... 0.078125 ... 0.039063 ...

The generate Function

The generate function of type (unit -> 'b) -> ('b -> 'a option) -> ('b -> unit) -> seq<'a> is a useful function for creating IEnumerable collections. It allows you to generate collections from some kind of cursor, such as file stream or database record set. The cursor can be a file stream, as shown in these examples, or perhaps more commonly a database cursor. In fact, it can be any type that will generate a sequence of elements. The generate function takes three functions: one to open the cursor (the opener function in the following example), one to do the work of actually generating the collection (the generator function), and one to close the cursor (the closer function). The collection can then be treated as any other IEnumerable collection, but behind the scenes, the functions you have defined will be called to go to the data source and read the elements from it. The following example shows the function being used to read a comma-separated list of words from a file:

open System
open System.Text
open System.IO

// test.txt: the,cat,sat,on,the,mat
let opener() = File.OpenText("test.txt")
let generator (stream : StreamReader) =
    let endStream = ref false
    let rec generatorInner chars =
        match stream.Read() with
        | −1 ->
        endStream := true
        chars
        | x ->
            match Convert.ToChar(x) with
            | ',' -> chars
            | c -> generatorInner (c :: chars)
let chars = generatorInner []
    if List.length chars = 0 && !endStream then
        None
    else
        Some(new string(List.toArray (List.rev chars)))

let closer (stream : StreamReader) =
    stream.Dispose()

let wordList =
    Seq.generate
        opener
        generator
        closer

wordList |> Seq.iter (fun s -> printfn "%s" s)

The results of this code, when compiled and executed, are as follows:

the
cat
sat
on
the
mat

The cast Function

The .NET Framework's BCL contains two versions of the IEnumerable interface, one defined in System. Collections.Generic and an older one defined in System.Collections. All the samples shown so far have been designed to work with the new generic version from System.Collections.Generic. However, sometimes it might be necessary to work with collections that are not generic, so the F# IEnumerable module also provides a function to work with that converts from nongeneric collections to a generic one.

Before using this function, I strongly recommend that you see whether you can use the list comprehension syntax covered in Chapters 3 and 4 instead. This is because the list comprehension syntax can infer the types of many untyped collections, usually by looking at the type of the Item indexer property, so there is less need for type annotations, which generally makes programming easier.

If for any reason you'd prefer not to use the list comprehension syntax you can convert a non generic collection to a generic one using the function cast, which is demonstrated in the following example:

open System.Collections
open System.Collections.Generic
let floatArrayList =
    let temp = new ArrayList()
    temp.AddRange([| 1.0; 2.0; 3.0 |])
    temp

let (typedFloatSeq: seq<float>) = Seq.cast floatArrayList

Using cast function always required using type annotations to tell the compiler what type of list you are producing. Here you have a list of floats, so you use the type annotation IEnumerable<float> to tell the compiler it will be an IEnumerable collection containing floating-point numbers.

The Microsoft.FSharp.Text.Printf Module

The Printf module provides functions for formatting strings in a type-safe way. The functions in the Printf module take a string with placeholders for values as their first argument. This returns another function that expects values for the placeholders. You form placeholders by using a percentage sign and a letter representing the type that they expect. Table 7-2 shows the full list.

Table 7.2. Printf Placeholders and Flags

Flag

Description

%b

bool, formatted as "true" or "false"

%s

string, formatted as its unescaped contents

%d, %i

Any basic integer type (that is, sbyte, byte, int16, uint16, int32, uint32, int64, uint64, nativeint, or unativeint) formatted as a decimal integer, signed if the basic integer type is signed

%u

Any basic integer type formatted as an unsigned decimal integer

%x, %X, %o

Any basic integer type formatted as an unsigned hexadecimal, (a-f)/Hexadecimal (A-F)/Octal integer

%e, %E

Any basic floating-point type (that is, float or float32), formatted using a C-style floating-point format specification, signed value having the form [-]d.dddde[sign]ddd where d is a single decimal digit, dddd is one or more decimal digits, ddd is exactly three decimal digits, and sign is + or −

%f

Any basic floating-point type, formatted using a C-style floating-point format specification, signed value having the form [-]dddd.dddd, where dddd is one or more decimal digits. The number of digits before the decimal point depends on the magnitude of the number, and the number of digits after the decimal point depends on the requested precision

%g, %G

Any basic floating-point type, formatted using a C-style floating-point format specification, signed value printed in f or e format, whichever is more compact for the given value and precision

%M

System.Decimal value

%O

Any value, printed by boxing the object and using its ToString method(s)

%A

Any value, values will be pretty printed allowing the user to see the values of properties and fields

%a

A general format specifier; requires two arguments: A function that accepts two arguments: a context parameter of the appropriate type for the given formatting function (such as a System.IO.TextWriter) and a value to print that either outputs or returns appropriate text. The particular value to print

%t

A general format specifier; requires one argument: a function that accepts a context parameter of the appropriate type for the given formatting function (such as a System.IO.TextWriter) and that either outputs or returns appropriate text

0

A flag that adds zeros instead of spaces to make up the required width

-

A flag that left justifies the result within the width specified

+

A flag that adds a + character if the number is positive (to match the – sign for negatives)

' '

Adds an extra space if the number is positive (to match the – sign for negatives)

The following example shows how to use the printf function. It creates a function that expects a string and then passes a string to this function.

Printf.printf "Hello %s" "Robert"

The results of this code are as follows:

Hello Robert

The significance of this might not be entirely obvious, but the following example will probably help explain it. If a parameter of the wrong type is passed to the printf function, then it will not compile:

Printf.printf "Hello %s" 1

The previous code will not compile, giving the following error:

Prog.fs(4,25): error: FS0001: This expression has type
    int
but is here used with type
    string

This also has an effect on type inference. If you create a function that uses printf, then any arguments that are passed to printf will have their types inferred from this. For example, the function myPrintInt, shown here, has the type int -> unit because of the printf function contained within it:

let myPrintInt x =
    Printf.printf "An integer: %i" x

The basic placeholders in a Printf module function are %b for a Boolean; %s for a string; %d or %i for an integer; %u for an unsigned integer; and %x, %X, or %o for an integer formatted as a hexadecimal. It is also possible to specify the number of decimal places that are displayed in numeric types. The following example demonstrates this:

let pi = System.Math.PI

Printf.printfn "%f" pi
Printf.printfn "%1.1f" pi
Printf.printfn "%2.2f" pi
Printf.printfn "%2.8f" pi

The results of this code are as follows:

3.141593
3.1
3.14
3.14159265

The Printf module also contains a number of other functions that allow a string to be formatted in the same ways as printf itself, but allow the result to be written to a different destination. The following example shows some of the different versions available:

// write to a string
let s = Printf.sprintf "Hello %s
" "string"
printfn "%s" s
// prints the string to a .NET TextWriter
Printf.fprintf System.Console.Out "Hello %s
" "TextWriter"
// create a string that will be placed
// in an exception message
Printf.failwithf "Hello %s" "exception"

The results of this code are as follows:

Hello string
Hello channel
Hello TextWriter
Microsoft.FSharp.FailureException: Hello exception
   at [email protected](String s)
   at [email protected](A inp))
   at <StartupCode>.FSI_0003._main()
stopped due to error

The Microsoft.FSharp.Control.Event Module

You can think of an event in F# as a collection of functions that can be triggered by a call to a function. The idea is that functions will register themselves with the event, the collection of functions, to await notification that the event has happened. The trigger function is then used to give notice that the event has occurred, causing all the functions that have added themselves to the event to be executed.

I will cover the following features of the Event module:

Creating and handling events:

The basics of creating and handling events using the create and add functions.

The filter function:

A function to filter the data coming into events.

The partition function:

A function that splits the data coming into events into two.

The map function:

A function that maps the data before it reaches the event handler.

Creating and Handling Events

The first example looks at a simple event being created using by call the constructor of the Event object, you should pass a type parameter to the construct representing the type of event you want. This object contains a Trigger function and a property that represents the event itself called Publish. You use the event's Publish property's Add function to add a handler method, and finally you trigger the event using the trigger function:

let event = new Event<string>()
event.Publish.Add(fun x -> printfn "%s" x)
event.Trigger "hello"

The result of this code is as follows:

hello

In addition to this basic event functionality, the F# Event module provides a number of functions that allow you to filter and partition events to give fine-grained control over which data is passed to which event handler.

The filter Function

The following example demonstrates how you can use the Event module's filter function so that data being passed to the event is filtered before it reaches the event handlers. In this example, you filter the data so that only strings beginning with H are sent to the event handler:

let event = new Event<string>()
let newEvent = event.Publish |> Event.filter (fun x -> x.StartsWith("H"))

newEvent.Add(fun x -> printfn "new event: %s" x)

event.Trigger "Harry"
event.Trigger "Jane"
event.Trigger "Hillary"
event.Trigger "John"
event.Trigger "Henry"

The results of this code, when compiled and executed, are as follows:

new event: Harry
new event: Hillary
new event: Henry

The partition Function

The Event module's partition function is similar to the filter function except two events are returned, one where data caused the partition function to return false and one where data caused the partition function to return true. The following example demonstrates this:

let event = new Event<string>()
let hData, nonHData = event.Publish |> Event.partition (fun x -> true)

let x  = Event.partition


hData.Add(fun x -> printfn "H data: %s" x)
nonHData.Add(fun x -> printfn "None H data: %s" x)
event.Trigger "Harry"
event.Trigger "Jane"
event.Trigger "Hillary"
event.Trigger "John"
event.Trigger "Henry"

The results of this code are as follows:

H data: Harry
None H data: Jane
H data: Hillary
None H data: John
H data: Henry

The map Function

It is also possible to transform the data before it reaches the event handlers. You do this using the map function provided in the Event module. The following example demonstrates how to use it:

let event = new Event<string>()
let newEvent = event.Publish |> Event.map (fun x -> "Mapped data: " + x)
newEvent.Add(fun x -> printfn "%s" x)

event.Trigger "Harry"
event.Trigger "Sally"

The results of this code are as follows:

Mapped data: Harry
Mapped data: Sally

This section has just provided a brief overview of events in F#. You will return to them in more detail in Chapter 8 when I discuss user interface programming, because that is where they are most useful.

The Power Pack Library FSharp.PowerPack.dll

The power pack contains a number of useful features that were not included in the FSharp.Core.dll either because of space issues or because they were considered experimental and likely to evolve more rapidly than the FSharp.Core.dll will. This includes modules that are suitable for cross compilation with OCaml, extra collections, extra mathematical function, asynchronous workflows (covered in Chapter 10) and functions for supporting text parsing via fslex and fsyacc. Next, I will cover Microsoft.FSharp.Math: A namespace that contains several modules related to mathematics. These include arbitrary precision integers and rationales, vectors, matrices, and complex numbers.

The Microsoft.FSharp.Math Namespace

The Microsoft.FSharp.Math namespace is designed to enable F# to ensure that the F# libraries include definitions of some of the foundational constructs used across a wide range of graphics, mathematical, scientific, and engineering applications. First, you will look briefly at the modules that make it up, and then you'll dive into a more detailed example.

It contains arbitrary precision numbers. These are numbers whose values have no upper limit and include the modules BigInt and BigNum. A typical use of these would be in a program that searches for large prime numbers, perhaps for use in cryptography.

The modules Matrix, Vector, RowVector, and Notations all contain operations related to matrices and vectors. Matrices are sets of numbers arranged in rows and columns to form a rectangular array. Vectors are a column of numbers and are like a matrix with one column but are a separate type. A vector is a quantity characterized by magnitude and direction, so a two-dimensional vector is specified by two coordinates, a three-dimensional vector by three coordinates, and so on. Therefore, vectors are represented as a matrix made up of one column with the number of rows depending on the dimension of the vector.

There is a module, Complex, for working with complex numbers. The complex numbers are the base for many types of fractal images, so I will demonstrate how you can use the F# complex number library to draw the most famous fractal of all, the Mandelbrot set. The Mandelbrot set is generated by repeated iteration of the following equation:

Cn+1 = Cn2 + c

The next number in the series is formed from the current number squared plus the original number. If repeated iteration of this equation stays between the complex number C(1, 1i) and C(−1, −1i), then the original complex number is a member of the Mandelbrot set. This can be implemented in F# with the following:

open Microsoft.FSharp.Math
open Microsoft.FSharp.Math.Notation

let cMax = complex 1.0 1.0
let cMin = complex −1.0 −1.0
let iterations = 18
let isInMandelbrotSet c0 =
    let rec check n c =
        (n = iterations)
        or (cMin < c) && (c < cMax) && check (n + 1) ((c * c) + c0)
    check 0 c0

The function isInMandelbrotSet tests whether a complex number is in the Mandelbrot set by recursively calling the check function with the new c value of ((c * c) + c0) until either the complex number passes one of the constants cMax or cMin or the number of iterations exceeds the constant iterations. If the number of iterations specified by iterations is reached, then number is a member of the set. Otherwise, it is not.

Because the complex numbers consist of two numbers, they can be represented in a two-dimensional plane. The Mandelbrot complex numbers exist between C(1, 1i) and C(−1, −1i), so the plane that you need to draw has the origin, which is the point 0, 0, in the center, and its axis extends out in either direction until reaching a maximum of 1.0 and a minimum of −1.0, such as the plane on the right of Figure 7-1. However, when it comes to pixels on a computer screen, you must deal with a plane where the origin is in the top-right corner and it extends rightward and downward. Because this type plane is made up of pixels, which are discrete values, it is represented by integers typically somewhere in the range 0 to 1600. Such a plane appears on the left of Figure 7-1.

A bitmap plane vs. a complex plane

Figure 7.1. A bitmap plane vs. a complex plane

The application must map the points in the bitmap plane to points in the complex plane so that you can tell whether a pixel is part of the complex plane.

It is easy to perform this mapping in just a few lines of F# code:

open Microsoft.FSharp.Math
open Microsoft.FSharp.Math.Notation

let scalingFactor = 1.0 / 200.0
let offset = −1.0

let mapPlane (x, y) =
     let fx = ((float x) * scalingFactor) + offset
     let fy = ((float y) * scalingFactor) + offset
     complex fx fy

Once this is complete, you just need to cycle through all the points in your bitmap plane, mapping them to the complex plane using the mapPlane function. Then you need to test whether the complex number is in the Mandelbrot set using the function isInMandelbrotSet. Finally, you set the color of the pixel. The full program is as follows:

open System
open System.Drawing
open System.Windows.Forms
open Microsoft.FSharp.Math

let cMax = complex 1.0 1.0
let cMin = complex −1.0 −1.0
let iterations = 18

let isInMandelbrotSet c0 =
    let rec check n c =
        (n = iterations)
        || (cMin < c)
        && (c < cMax)
        && check (n + 1) ((c * c) + c0)
    check 0 c0

let scalingFactor = 1.0 / 200.0
let offset = −1.0

let mapPlane (x, y) =
    let fx = ((float x) * scalingFactor) + offset
    let fy = ((float y) * scalingFactor) + offset
    complex fx fy

let form =
    let image = new Bitmap(400, 400)
    for x = 0 to image.Width - 1 do
        for y = 0 to image.Height - 1 do
            let isMember = isInMandelbrotSet ( mapPlane (x, y) )
            if isMember then
                image.SetPixel(x,y, Color.Black)
    let temp = new Form() in
    temp.Paint.Add(fun e -> e.Graphics.DrawImage(image, 0, 0))
    temp

[<STAThread>]
do Application.Run(form)

This program produces the image of the Mandelbrot set in Figure 7-2.

The Mandelbrot set

Figure 7.2. The Mandelbrot set

Summary

I covered a lot of ground in this chapter, since the F# libraries have a diverse range of functionalities. First, you looked through the FSharp.Core.dll library with its useful Collections, Reflection, and Math modules. Then you looked at FSharp.PowerPack.dll, which provides functions that are excellent building blocks for all applications. Its Seq module is something that any nontrivial F# program will not be able to do without.

The next three chapters will look at how you can use F# with various .NET APIs for common programming tasks. You'll start with a look at implementing user interfaces in Chapter 8, then you'll move to data access in Chapter 9, and distributed applications in Chapter 10.

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

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