© Robert Pickering and Kit Eason 2016

Robert Pickering and Kit Eason, Beginning F# 4.0, 10.1007/978-1-4842-1374-2_7

7. The F# Libraries

Robert Pickering and Kit Eason

(1)St. Germain-En-Laye, France

Although F# can use all the classes available in the .NET BCL, it also ships with its own set of libraries, which can be found under the FSharp namespace. 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 ( https://msdn.microsoft.com/en-us/library/ee353413.aspx ) 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:

  • FSharp.Core.Operatorsis a module containing functions that are mathematical operators .

  • FSharp.Reflectionis a module containing functions that supplement the .NET Framework’s reflection classes to give a more accurate view of F# types and values.

  • FSharp.Collections.Seqis a module containing functions for any type that supports the IEnumerable interface .

  • FSharp.Core.Printfis a module for formatting strings .

  • FSharp.Control.Eventis a module for working with events in F#.

The FSharp.Core.Operators Module

In F#, operators are defined by libraries rather than built into the language. The FSharp.Core.Operators module contains some of the language’s operators . It also contains some useful operators, such as functions, and it is these that I will cover 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 of 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, it will wrap, rather than causing an error. If you prefer to use checked operators that raise an exception when a value overflows, you can do so by opening the module FSharp.Core.Operators.Checked:

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

The above example will now throw an error when executed, whereas if the module 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 for F# record, struct, and discriminated union types , default equality is structural equality, meaning that the contents of the instances 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. For OO-style types, the default equality remains referential as in C#.

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 record instances, 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)

When you compile and execute this example, you get the following results:

(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)

When you compile and execute this example, you get the following result:

(robert2 > robert3): true

If you need to determine whether two records, structs, or discriminated union instances are physically equal, 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)

From F# 4.0 onwards, you can override structual equality behavior for records by simply opening the FSharp.Core.NonStructuralOperators namespace. This namespace contains versions of the = and <> operators, which use referential equality. Thus, if you simply add

open FSharp.Core.Operators.NonStructuralComparison

the results of executing the same code become

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

Alternatively, you can force reference equality for a particular type by adding the [<ReferenceEquality>] attribute to the type .

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:

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.

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

When you compile and execute this example, you get the following results:

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

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 are as follows:

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

The Conversion Functions

The Operators module offers a number of overload functions for converting between the primitive types. For example, the function float is overloaded 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 compiler knows which type of enumeration to convert it to. You can see this in the following example 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

When you compile and execute this example, you get the following results:

0
Sunday

The Bitwise Or and And Operators

The other common tasks that you need to perform with enumerations are to combine them using a bitwise “or” and “and” operations. 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 the ||| 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 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 lets 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 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 object’s GetType method to get the System.Type that represents the object. You can then use this value with the function FSharpType.IsTuple 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 BCL, so you simply print out the type names. This means that, 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 want to display the values that make up the tuple. To do this, you use reflection over values, and you need to use the function FSharpValue.GetTupleFields to get an array of objects that are the values that make up the tuple. These objects can be tuples, or other F# types, so you can recursively call the function to print out the values of the objects. 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 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)

When you compile and execute this example, you get the following result:

 ( "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’s 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 on GitHub ( https://github.com/fsharp/fsharp/blob/master/src/fsharp/FSharp.Core/printf.fs ) .

The FSharp.Collections.Seq Module

The FSharp.Collections.Seq module contains functions that work with any collection that supports the IEnumerable interface, which is most of the collections in the BCL. The module is called Seq (short for “sequence”) 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

Microsoft.FSharp.Collections contains several modules designed to work with various types of collections. These include Array, Array2D (two-dimensional arrays), Array3D (three-dimensional arrays), IEnumerable (Seq), List, Map, and Set. I’ll cover only Seq, but all of the basic operations (iteration, mapping, filtering, and so on) are implemented for each of the different collection types and work in a similar, often identical, way.

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 collection 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 collections.

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

The map and iter Functions

Let's 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 is 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)

When you compile and execute this example, you get the following results:

 [|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 (i.e. not an “F# list” but a “.NET” or “C#” list) 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)

When you compile and execute this example, you get the following results:

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

When you compile and execute this example, you get the following result:

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

When you compile and execute this example, you get the following results:

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 look through a list of words. First, you use filter to create a list containing only the words that end in at. Then you use find to find the first word that ends in ot. Finally, you 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")

When you compile and execute this example, you get the following results:

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 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)

When you compile and execute this example, you get the following results:

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 init 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 unbounded 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. Typically initInfinite will fail once the value of the integer that it sends into the generator function exceeds the maximum integer size for .NET.

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

When you compile and execute this example, you get the following results:

 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 don’t 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

When you compile and execute this example, you get the following results:

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

This example demonstrates using unfold to produce a list that never terminates. Now 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 that 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)

When you compile and execute this example, you get the following results:

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

The cast Function

The 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 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

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


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

Using the cast function often requires 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 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”.

%c

Any character.

%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 that 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 second argument is the particular value to print.

%t

A general format specifier that 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"

When you compile and execute this example, you get the following result:

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

This 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 (which is automatically opened for you) 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
fprintf System.Console.Out "Hello %s " "TextWriter"
// create a string that will be placed
// in an exception message
failwithf "Hello %s" "exception"

The results of this code are as follows:

Hello string
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 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 a call to the constructor of the Event object. You should pass a type parameter to the constructor 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"

When you compile and execute this example, you get the following results:

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 -> x.StartsWith "H")


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 .

Summary

We 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 operators, and its Collections and Reflection modules. In particular, the Seq module is something that any nontrivial F# program would not be able to do without since it provides invaluable functions such as iter, map, and concat. You looked at the significance of structural versus reference equality, and how you can override the default behaviors. You tried out the Printf module and the corresponding format strings, which give you strongly typed string formatting. Finally, you learned to define, raise, and filter events.

The next three chapters will look at how you can use F# with various .NET APIs for common programming tasks. You’ll look at data access in Chapter 8, parallel programming 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
3.145.35.194