CHAPTER 2

images

Your First F# Program – Getting Started With F#

This chapter covers simple interactive programming with F# and .NET. To begin, download and install a version of the F# distribution from www.fsharp.net. (You may have a version on your machine already—for instance, if you have installed Visual Studio.) The sections that follow use F# Interactive, a tool you can use to execute fragments of F# code interactively, and one that is convenient for exploring the language. Along the way, you will see examples of the most important F# language constructs and many important libraries.

Creating Your First F# Program

Listing 2-1 shows your first complete F# program. You may not follow it all at first glance, but we explain it piece by piece after the listing.

Listing 2-1. Analyzing a String for Duplicate Words

/// Split a string into words at spaces
let splitAtSpaces (text: string) =
    text.Split ' '
    |> Array.toList

/// Analyze a string for duplicate words
let wordCount text =
    let words = splitAtSpaces text
    let wordSet = Set.ofList words
    let numWords = words.Length
    let numDups  = words.Length - wordSet.Count
    (numWords, numDups)

/// Analyze a string for duplicate words and display the results.
let showWordCount text =
    let numWords, numDups = wordCount text
    printfn "--> %d words in the text" numWords
    printfn "--> %d duplicate words" numDups

Paste this program into F# Interactive, which you can start by using the command line, by running fsi.exe from the F# distribution or by using an interactive environment, such as Visual Studio. If you’re running from the command line, remember to enter ;; to terminate the interactive entry—you don’t need to do this in Visual Studio or other interactive environments.

images Tip  You can start F# Interactive in Visual Studio by selecting F# Interactive in the View menu or by pressing Ctrl+Alt+F in an F# file or script. A tool window appears, and you can send text to F# Interactive by selecting the text and pressing Alt+Enter.


C:UsersdsymeDesktop> fsi.exe

Microsoft (R) F# 3.0 Interactive build 11.0.50522.1
Copyright (c) Microsoft Corporation. All Rights Reserved.

For help type #help;;

> <paste in the earlier program here> ;;

val splitAtSpaces : text:string -> string list
val wordCount : text:string -> int * int
val showWordCount : text:string -> unit

Here, F# Interactive reports the type of the functions splitAtSpaces, wordCount, and showWordCount (you will learn more about types in a moment). The keyword val stands for value; in F# programming, functions are just values, a topic we return to in Chapter 3. Also, sometimes F# Interactive shows a little more information than we show in this book (such as some internal details of the generated values); if you’re trying out these code snippets, you can ignore that additional information. For now, let’s use the wordCount function interactively:


> let (numWords,numDups) = wordCount "All the king's horses and all the king's men";;

val numWords : int = 9
val numDups : int = 2

This code shows the results of executing the function wordCount and binding its two results to the names numWords and numDups, respectively. Examining the values shows that the given text contains nine words: two duplicates and seven words that occur only once. showWordCount prints the results instead of returning them as a value:


> showWordCount "Couldn't put Humpty together again";;

--> 5 words in the text
--> 0 duplicate words

From the output, you can more or less see what the code does. Now that you’ve done that, let’s go through the program in detail.

Documenting Code

Let’s start with the definition of the wordCount function in Listing 2-1. The first line of the definition isn’t code; rather, it’s a comment:

/// Analyze a string for duplicate words

Comments are either lines starting with // or blocks enclosed by (* and *). Comment lines beginning with three slashes (///) are XMLDoc comments and can, if necessary, include extra XML tags and markup. The comments from a program can be collected into a single .xml file and processed with additional tools.

Using let

Now, look at the first two lines of the function wordCount in Listing 2-1. These lines define the function wordCount and the local value words, both using the keyword let:

let wordCount (text:string) =
    let words = ...

let is the single most important keyword you use in F# programming: it’s used to define data, computed values, and functions. The left of a let binding is often a simple identifier, but it can also be a pattern. (See “Using Tuples” for examples.) It can also be a function name followed by a list of argument names, as in the case of wordCount, which takes one argument: text. The right of a let binding (after the =) is an expression.

VALUES AND IMMUTABILITY

Understanding Types

F# is a typed language, so it’s reasonable to ask what the type of wordCount is. F# Interactive has shown it already, but we can see it again:


wordCount;;

val it : (string -> int * int) = <fun:it@36>

This indicates that wordCount takes one argument of type string and returns int * int, which is F#’s way of saying “a pair of integers.” The keyword val stands for value, and the symbol -> indicates that wordCount is a function. No explicit type is given in the program for the type of the argument text, because the full type for wordCount is inferred from its definition. We discuss type inference further in “What Is Type Inference?” and in more detail in later chapters.

Types are significant in both F# and .NET programming more generally for reasons that range from performance to coding productivity and interoperability. Types are used to help structure libraries, to guide you through the complexity of an API, and to place constraints on code to ensure that it can be implemented efficiently. Unlike in many other typed languages, F#’s type system is both simple and powerful, because it uses orthogonal, composable constructs, such as tuples and functions, to form succinct and descriptive types. Furthermore, type inference means you almost never have to write types in your program, although doing so can be useful.

Table 2-1 shows some of the most important Type constructors, which are the operators F# offers for defining new types (classes and delegates are well-known examples from other programming languages). Chapters 3 and 4 discuss all these types in more detail.

images

Some type constructors, such as list and option, are generic, which means they can be used to form a range of types by instantiating the generic variables, such as int list, string list, int list list, and so on. You can write instantiations of generic types using either prefix notation (such as int list) or postfix notation (such as list<int>). Variable types such as 'a and 'T are placeholders for any type. Chapters 3 and 5 discuss generics and variable types in more detail.

WHAT IS TYPE INFERENCE?

Calling Functions

Functions are at the heart of most F# programming. It’s not surprising that the first thing the wordCount function does is call a function—in this case, the splitAtSpaces function, which is the first function defined in the program:

let wordCount (text: string) =
    let words = splitAtSpaces text

Let’s first investigate the splitAtSpaces function by running F# Interactive:


> splitAtSpaces "hello world";;

val it : string list = ["hello"; "world"]

You can see that splitAtSpaces breaks the given text into words, splitting at spaces.

In the sample code, you can also see examples of:

  • Literal characters, such as ' ' and 'a'
  • Literal strings, such as "hello world"
  • Literal lists of strings, such as the returned value [ "hello"; "world" ]

Chapter 3 covers literals and lists in detail. Lists are an important data structure in F#, and you see many examples of their use in this book.

Lightweight Syntax

The F# compiler and F# Interactive use the indentation of F# code to determine where constructs start and finish. The indentation rules are very intuitive; we discuss them in the appendix, which is a guide to the F# syntax. Listing 2-2 shows a version of the wordCount function that explicits all the scopes of names using the in keyword.

Listing 2-2. A version of the wordCount function using explicit “in” tokens

/// Analyze a string for duplicate words
let wordCount text =
    let words = splitAtSpaces text in
    let wordSet = Set.ofList words in
    let numWords = words.Length in
    let numDups = numWords - wordSet.Count in
    (numWords, numDups)

Double semicolons (;;) are still required to terminate entries to F# Interactive. If you’re using an interactive development environment such as Visual Studio, however, the environment typically adds them automatically when code is selected and executed. We show the double semicolons in the interactive code snippets used in this book, although not in the larger samples.

Sometimes it’s convenient to write let definitions on a single line. Do this by separating the expression that follows a definition from the definition itself, using in. For example:

let powerOfFour n =
    let nSquared = n * n in nSquared * nSquared

Here’s an example use of the function:


> powerOfFour 3;;

val it : int = 81

Indeed, let pat = expr1 in expr2 is the true primitive construct in the language, with pat standing for pattern, and expr1 and expr2 standing for expressions. The F# compiler inserts the in if expr2 is column-aligned with the let keyword on a subsequent line.

images Tip  We recommend that you use four-space indentation for F# code. Tab characters can’t be used, and the F# tools give an error if they’re encountered. Most F# editors convert uses of the Tab key to spaces automatically.

Understanding Scope

Local values ,such as words and wordCount, can’t be accessed outside their scope. In the case of variables defined using let, the scope of the value is the entire expression that follows the definition, although not the definition itself. Here are two examples of invalid definitions that try to access variables outside their scope. As you see, let definitions follow a sequential, top-down order, which helps ensure that programs are well-formed and free from many bugs related to uninitialized values:  

let badDefinition1 =
    let words = splitAtSpaces text
    let text = "We three kings"
    words.Length

gives


error FS0039: The value or constructor 'text' is not defined

and

let badDefinition2 = badDefinition2 + 1

gives


let badDefinition2 = badDefinition2 + 1
error FS0039: The value or constructor 'badDefinition2' is not defined

Within function definitions, you can outscope values by declaring another value of the same name. For example, the following function computes (n*n*n*n)+2:

let powerOfFourPlusTwo n =
    let n = n * n
    let n = n * n
    let n = n + 2
    n

This code is equivalent to:

let powerOfFourPlusTwo n =
    let n1 = n * n
    let n2 = n1 * n1
    let n3 = n2 + 2
    n3

Outscoping a value doesn’t change the original value; it just means that the name of the value is no longer accessible from the current scope.

Because let bindings are simply a kind of expression, you can use them in a nested fashion. For example:

let powerOfFourPlusTwoTimesSix n =
    let n3 =
        let n1 = n * n
        let n2 = n1 * n1
        n2 + 2
    let n4 = n3 * 6
    n4

Here, n1 and n2 are values defined locally by let bindings within the expression that defines n3. These local values aren’t available for use outside their scope. For example, this code gives an error:

let invalidFunction n =
    let n3 =
        let n1 = n + n
        let n2 = n1 * n1
        n1 * n2
    let n4 = n1 + n2 + n3     // Error! n3 is in scope, but n1 and n2 are not!
    n4

Local scoping is used for many purposes in F# programming, especially to hide implementation details that you don’t want revealed outside your functions or other objects. Chapter 7 covers this topic in more detail.

Using Data Structures

The next portion of the code is:

let wordCount (text:string) =
    let words = splitAtSpaces text
    let wordSet = Set.ofList words
    ...

This gives you your first taste of using data structures from F# code. The last of these lines lies at the heart of the computation performed by wordCount. It uses the function Set.ofList from the F# library to convert the given words to a concrete data structure that is, in effect, much like the mathematical notion of a set, although internally, it’s implemented using a data structure based on trees. You can see the results of converting data to a set by using F# Interactive:


> Set.ofList ["b"; "a"; "b"; "b"; "c" ];;

val it : Set<string> = set [ "a"; "b"; "c" ]

> Set.toList (Set.ofList ["abc"; "ABC"]);;

val it : string list = ["ABC"; "abc"]

Here you can see several things:

  • F# Interactive prints the contents of structured values, such as lists and sets.
  • Duplicate elements are removed by the conversion.
  • The elements in the set are ordered.
  • The default ordering on strings used by sets is case sensitive.

The name Set references the F# module Microsoft.FSharp.Collections.Set in the F# library. This contains operations associated with values of the Set<_> type. It’s common for types to have a separate module that contains associated operations. All modules under the Microsoft.FSharp namespaces Core, Collections, Text, and Control can be referenced by simple one-word prefixes, such as Set.ofList. Other modules under these namespaces include List, Option, and Array.

Using Properties and the Dot-Notation

The next two lines of the wordCount function compute the result you’re after—the number of duplicate words. This is done by using two properties, Length and Count, of the values you’ve computed:

  let numWords = words.Length
  let numDups = numWords - wordSet.Count

F# performs resolution on property names at compile time (or interactively when you’re using F# Interactive, in which there is no distinction between compile time and runtime). This is done using compile-time knowledge of the type of the expression on the left of the dot—in this case, words and wordSet. Sometimes, a type annotation is required in your code in order to resolve the potential ambiguity among possible property names. For example, the following code uses a type annotation to note that inp refers to a list. This allows the F# type system to infer that Length refers to a property associated with values of the list type:

let length (inp:'T list) = inp.Length

Here, the 'T indicates that the length function is generic; that is, it can be used with any type of list. Chapters 3 and 5 cover generic code in more detail.

As you can see from the use of the dot-notation, F# is both a functional language and an object-oriented language. In particular, properties are a kind of member, a general term used for any functionality associated with a type or value. Members referenced by prefixing a type name are called static members, and members associated with a particular value of a type are called instance members; in other words, instance members are accessed through an object on the left of the dot. We discuss the distinction between values, properties, and methods later in this chapter, and Chapter 6 discusses members in full.

images Note  Type annotations can be useful documentation; when you use them, they should generally be added at the point where a variable is declared.

Sometimes, explicitly named functions play the role of members. For example, you could write the earlier code as:

let numWords = List.length words
let numDups = numWords - Set.count wordSet

You see both styles in F# code. Some F# libraries don’t use members at all or use them only sparingly. Judiciously using members and properties, however, can greatly reduce the need for trivial get/set functions in libraries, can make client code much more readable, and can allow programmers who use environments such as Visual Studio to easily and intuitively explore the primary features of libraries they write.

If your code doesn’t contain enough type annotations to resolve the dot-notation, you see an error such as:


> let length inp = inp.Length;;

error FS0072: Lookup on object of indeterminate type based on information prior to this
program point. A type annotation may be needed prior to this program point to constrain the
type of the object. This may allow the lookup to be resolved. You can resolve this by adding a
type annotation as shown earlier.

Using Tuples

The final part of the wordCount function returns the results numWords and numDups as a tuple:

    ...
    let numWords = words.Length
    let numDups = numWords - wordSet.Count
    (numWords, numDups)

Tuples are the simplest, but perhaps the most useful, of all F# data structures. A tuple expression is a number of expressions grouped together to form a new expression:

let site1 = ("www.cnn.com", 10)
let site2 = ("news.bbc.com", 5)
let site3 = ("www.msnbc.com", 4)
let sites = (site1, site2, site3)

Here, the inferred types and computed values are:


val site1 : string * int = ("www.cnn.com", 10)
val site2 : string * int = ("news.bbc.com", 5)
val site3 : string * int = ("www.msnbc.com", 4)
val sites : (string * int) * (string * int) * (string * int) =
  (("www.cnn.com", 10), ("news.bbc.com", 5), ("www.msnbc.com", 4))

Tuples can be decomposed into their constituent components in two ways. For pairs—that is, tuples with two elements—you can explicitly call the functions fst and snd, which, as their abbreviated names imply, extract the first and second parts of the pair:


> fst site1;;

val it : string = "www.cnn.com"

> let relevance = snd site1;;

val relevance : int = 10

The functions fst and snd are defined in the F# library and are always available for use by F# programs. Here are their simple definitions:

let fst (a, b) = a
let snd (a, b) = b

More commonly, tuples are decomposed using patterns, as in the code:

let url, relevance = site1
let siteA, siteB, siteC = sites

In this case, the names in the tuples on the left of the definitions are bound to the respective elements of the tuple value on the right; so again, url gets the value "www.cnn.com" and relevance gets the value 10.

Tuple values are typed, and strictly speaking, there are an arbitrary number of families of tuple types: one for pairs holding two values, one for triples holding three values, and so on. This means that if you try to use a triple where a pair is expected, you get a type-checking error before your code is run:


> let a, b = (1, 2, 3);;

error FS0001: Type mismatch. Expecting a
    'a * 'b    
but given a
    'a * 'b * 'c    
The tuples have differing lengths of 2 and 3

Tuples are often used to return multiple values from functions, as in the wordCount example earlier. They’re also often used for multiple arguments to functions, and frequently the tupled output of one function becomes the tupled input of another function. This example shows a different way of writing the showWordCount function defined and used earlier:

let showResults (numWords, numDups) =
    printfn "--> %d words in the text" numWords
    printfn "--> %d duplicate words" numDups

let showWordCount text = showResults (wordCount text)

The function showResults accepts a pair as input, decomposed into numWords and numDups. The two outputs of wordCount become the two inputs of showResults.

VALUES AND OBJECTS

Using Imperative Code

The showWordCount and showResults functions defined in the previous section output results using a library function called printfn:

printfn "--> %d words in the text" numWords
printfn "--> %d duplicate words" numDups

If you’re familiar with OCaml, C, or C++, printfn will look familiar as a variant of printfprintfn also adds a newline character at the end of printing. Here, the pattern %d is a placeholder for an integer, and the rest of the text is output verbatim to the console.

F# also supports related functions, such as printf, sprintf, and fprintf, which are discussed further in Chapter 4. Unlike C/C++, printf is a type-safe text formatter in which the F# compiler checks that the subsequent arguments match the requirements of the placeholders. There are also other ways to format text with F#. For example, you can use the .NET libraries directly:

System.Console.WriteLine("--> {0} words in the text", box numWords)
System.Console.WriteLine("--> {0} duplicate words", box numDups)

Here, {0} acts as the placeholder, although no checks are made that the arguments match the placeholder before the code is run. The use of printfn also shows how you can use sequential expressions to cause effects in the outside world.

As with let ... in ... expressions, it’s sometimes convenient to write sequential code on a single line. Do this by separating two expressions with a semicolon (;). The first expression is evaluated (usually for its side effects), its result is discarded, and the overall expression evaluates to the result of the second. Here is a simpler example of this construct:

let two = (printfn "Hello World"; 1 + 1)
let four = two + two

When executed, this code prints Hello World precisely once, when the right side of the definition of two is executed. F# doesn’t have statements as such—the fragment (printfn "Hello World"; 1 + 1) is an expression, but when evaluated, the first part of the expression causes a side effect, and its result is discarded. It’s also often convenient to use parentheses to delimit sequential code. The code from the script could, in theory, be parenthesized, with a semicolon added to make the primitive constructs involved more apparent:

(printfn "--> %d words in the text" numWords;
 printfn "--> %d duplicate words" numDups)

images Note  The token ; is used to write sequential code within expressions, and ;; is used to terminate interactions with the F# Interactive session. Semicolons are optional when the individual fragments of your sequential code are placed on separate lines beginning at the same column position.

Using Object-Oriented Libraries from F#

The value of F# lies not just in what you can do inside the language but also in what you can connect to outside the language. For example, F# doesn’t come with a GUI library. Instead, F# is connected to .NET and via .NET to most of the significant programming technologies available on major computing platforms. You’ve already seen one use of the .NET libraries, in the first function defined earlier:

/// Split a string into words at spaces
let splitAtSpaces (text:string) =
    text.Split ' '
    |> Array.toList

Here, text.Split is a call to a .NET library instance method called Split defined on all string objects.

To emphasize this, the second sample uses two of the powerful libraries that come with the .NET Framework: System.Net and System.Windows.Forms. The full sample, in Listing 2-3, is a script for use with F# Interactive.

Listing 2-3. Using the .NET Framework Windows Forms and networking libraries from F

open System.Windows.Forms

let form = new Form(Visible = true, TopMost = true, Text = "Welcome to F#")

let textB = new RichTextBox(Dock = DockStyle.Fill, Text = "Here is some initial text")
form.Controls.Add textB


open System.IO
open System.Net

/// Get the contents of the URL via a web request
let http (url: string) =
    let req = System.Net.WebRequest.Create(url)
    let resp = req.GetResponse()
    let stream = resp.GetResponseStream()
    let reader = new StreamReader(stream)
    let html = reader.ReadToEnd()
    resp.Close()
    html

textB.Text <- http "http://news.bbc.co.uk"

The above example uses several important .NET libraries and helps you explore some interesting F# language constructs. The following sections walk you through this listing.

Using open to Access Namespaces and Modules

The first thing you see in the sample is the use of open to access functionality from the namespace System.Windows.Forms:

open System.Windows.Forms

Chapter 7 discusses namespaces in more detail. The earlier declaration means you can access any content under this path without quoting the long path. If it didn’t use open, you’d have to write the following, which is obviously a little verbose:

let form = new System.Windows.Forms.Form(Visible = true, TopMost = true, Text = "Welcome to F#")

You can also use open to access the contents of an F# module without using long paths. Chapter 7 discusses modules in more detail.

MORE ABOUT OPEN

Using new and Setting Properties

The next lines of the sample script use the keyword new to create a top-level window (called a form) and set it to be visible. If you run this code in F# Interactive, you see a top-level window appear with the title text Welcome to F#:

let form = new Form(Visible = true, TopMost = true, Text = “Welcome to F#”)

Here, new is shorthand for calling a function associated with the type System.Windows.Forms.Form that constructs a value of the given type—these functions are called constructors. Not all F# and .NET types have constructors; you also see values being constructed using names such as System.Net.WebRequest.Create, String.init, or Array.init. You see examples of each throughout this book.

A form is an object; that is, its properties change during the course of execution, and it’s a handle that mediates access to external resources (the display device, mouse, and so on). Sophisticated objects such as forms often need to be configured, either by passing in configuration parameters at construction or by adjusting properties from their default values after construction. The arguments Visible=true, TopMost=true, and Text=”Welcome to F#” set the initial values for three properties of the form. The labels Visible, TopMost, and Text must correspond to either named arguments of the constructor being called or properties on the return result of the operation. In this case, all three are object properties, and the arguments indicate initial values for the object properties.

Most properties on graphical objects can be adjusted dynamically. You set the value of a property dynamically using the notation obj.Property <- value. For example, you could also construct the form object as:

open System.Windows.Forms
let form = new Form()
form.Visible <- true
form.TopMost <- true
form.Text <- “Welcome to F#”

Likewise, you can watch the title of the form change by running the following code in F# Interactive:

form.Text <- “Programming is Fun!”

Setting properties dynamically is frequently used to configure objects, such as forms, that support many potential configuration parameters that evolve over time.

The object created here is bound to the name form. Binding this value to a new name doesn’t create a new form; rather, two different handles now refer to the same object (they’re said to alias the same object). For example, the following code sets the title of the same form, despite its being accessed via a different name:

let form2 = form
form2.Text <- “F# Forms are Fun”

VALUES, FUNCTIONS, METHODS, AND PROPERTIES

The next part of the sample creates a new RichTextBox control and stores it in a variable called textB:

let textB = new RichTextBox(Dock = DockStyle.Fill)
form.Controls.Add(textB)

A control is typically an object with a visible representation—or, more generally, an object that reacts to operating-system events related to the windowing system. A form is one such control, but there are many others. A RichTextBox control is one that can contain formatted text, much like a word processor.

Fetching a Web Page

The second half of Listing 2-3 uses the System.Net library to define a function http to read HTML Web pages. You can investigate the operation of the implementation of the function by entering the following lines into F# Interactive:


> open System;;
> open System.IO;;
> open System.Net;;

> let req = WebRequest.Create("http://www.microsoft.com");;

val req : WebRequest

> let resp = req.GetResponse();;

val resp : WebResponse

> let stream = resp.GetResponseStream();;

val stream : Stream

> let reader = new StreamReader(stream);;

val reader : StreamReader

> let html = reader.ReadToEnd();;

val html : string =
  "<html><head><title>Microsoft Corporation</title><meta http-eq"+[959 chars]

> textB.Text <- html;;

The final line sets the contents of the text-box form to the HTML contents of the Microsoft home page. Let’s look at this code line by line.

The first line of the code creates a WebRequest object using the static method Create, a member of the type System.Net.WebRequest. The result of this operation is an object that acts as a handle to a running request to fetch a Web page—you could, for example, abandon the request or check to see whether the request has completed. The second line calls the instance method GetResponse. The remaining lines of the sample get a stream of data from the response to the request using resp.GetResponseStream(), make an object to read this stream using new StreamReader(stream), and read the full text from this stream. Chapter 4 covers .NET I/O in more detail; for now, you can test by experimentation in F# Interactive that these actions do indeed fetch the HTML contents of a Web page. The inferred type for http that wraps up this sequence as a function is:


http;;

val it : (string -> string) = <fun:it@312-1>

images Note  Static members are items qualified by a concrete type or module. Examples include System.String.Compare, System.DateTime.Now, and List.map. Instance members are methods, properties, and values qualified by an expression. Examples include form.Visible, resp.GetResponseStream(), and cell.contents.

XML HELP IN VISUAL STUDIO

Summary

This chapter looked at some simple interactive programming with F# and .NET. Along the way, you met many of the constructs you use in day-to-day F# programming. The next chapter takes a closer look at these and other constructs that are used to perform compositional and succinct functional programming in F#.

images Note  In Chapter 3, you will use some of the functions defined in this chapter. If you’re using F# Interactive, you may want to leave your session open as you proceed.

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

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