F# beginner tutorial

f# computer language and what is f# programming language
GregDeamons Profile Pic
GregDeamons,New Zealand,Professional
Published Date:03-08-2017
Your Website URL(Optional)
Syme_850-4C02.fm Page 7 Tuesday, September 18, 2007 7:44 PM CHA P TE R 2 ■ ■ ■ Getting Started with F and .NET In this chapter, we cover some simple interactive programming with F and .NET. By now you should have downloaded and installed a version of the F distribution as described in Chapter 1. In the sections that follow, we use F Interactive, a tool you can use to execute fragments of F code interactively and a convenient way to explore the language. Along the way, you’ll 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 light /// Analyze a string for duplicate words let wordCount text = let words = String.split ' ' text let wordSet = Set.of_list words let nWords = words.Length let nDups = words.Length - wordSet.Count (nWords,nDups) let showWordCount text = let nWords,nDups = wordCount text printfn " %d words in the text" nWords printfn " %d duplicate words" nDups You can paste this program into F Interactive, which you can start either by using the command line, by running fsi.exe from the F distribution, or by using an interactive envi- ronment such as Visual Studio. If running from the command line, remember to enter ;; to terminate the interactive entry: 7Syme_850-4C02.fm Page 8 Tuesday, September 18, 2007 7:44 PM 8 CHAPTER 2 ■ GETTING STARTED WITH F AND .NET C:\Users\dsyme\Desktop fsi.exe MSR F Interactive, (c) Microsoft Corporation, All Rights Reserved NOTE: See 'fsi help' for flags NOTE: NOTE: Commands: r string;; reference (dynamically load) the given DLL. NOTE: I string;; add the given search path for referenced DLLs. NOTE: use string;; accept input from the given file. NOTE: load string ...string;; NOTE: load the given file(s) as a single unit. NOTE: quit;; exit. NOTE: NOTE: Visit the F website at http://research.microsoft.com/fsharp. NOTE: Bug reports to fsbugsmicrosoft.com. Enjoy paste in the earlier program here ;; val wordCount : string - int int val showWordCount : string - unit Here F Interactive has reported the type of the functions wordCount and showWordCount (you’ll learn more about types in a moment). The keyword val stands for value; in F program- ming, functions are just values, a topic we return to in Chapter 3. Also, sometimes F Interactive will show 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, then you can just ignore that additional information. For now let’s just use the wordCount function interactively: let (nWords,nDups) = wordCount "All the king's horses and all the king's men";; val nWords : int val nDups : int nWords;; val it : int = 9 nDups;; val it : int = 2 nWords - nDups;; val it : int = 7 This code shows the results of executing the function wordCount and binding its two results to the names nWords and nDups, respectively. You can examine the values by just entering each as a single expression, which assigns the result to a value called it and displays the value. Syme_850-4C02.fm Page 9 Tuesday, September 18, 2007 7:44 PM CHAPTER 2 ■ GETTING STARTED WITH F AND .NET 9 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, we’ll go through the program in detail. ■Tip You can start F Interactive in Visual Studio by selecting Tools ➤ Add-in Manager and then selecting F Interactive for Visual Studio in the Add-in Manager dialog box. A tool window will then appear, and you can send text to F Interactive by selecting the text and pressing Alt+Return. Turning On the Lightweight Syntax Option The first line of the file simply turns on the F lightweight syntax option. This option is assumed throughout this book; in other words, you should have light at the head of all your source files: light This option allows you to write code that looks and feels simpler by omitting recurring F tokens such as in, done, ; (semicolon), ;; (double semicolon), begin, and end. The option instructs the F compiler and F Interactive to use the indentation of F code to determine where constructs start and finish. The indentation rules are very intuitive, and we discuss them in the Appendix, which is a guide to the F syntax. Listing 2-2 shows a fully qualified version of the first function. Listing 2-2. A Version of the wordCount Function That Doesn’t Use the light Syntax Option /// Analyze a string for duplicate words let wordCount text = let words = String.split ' ' text in let wordSet = Set.of_list words in let nWords = words.Length in let nDups = words.Length - wordSet.Count in (nWords,nDups) Double semicolons (;;) are still required to terminate entries to F Interactive even when using the light syntax option. However, if you’re using an interactive development environ- ment such as Visual Studio, then the environment typically adds this automatically when code is selected and executed. We show the double semicolons in the interactive code snippets used this book, though not in the larger samples.Syme_850-4C02.fm Page 10 Tuesday, September 18, 2007 7:44 PM 10 CHAPTER 2 ■ GETTING STARTED WITH F AND .NET ■Tip We recommend that you use four-space indentation for F code. Tab characters cannot be used in light code, and the F tools will give an error if they are encountered. In Visual Studio, selecting Tools ➤ Options reveals an Options tab for controlling the options used by F Interactive at start-up; for example, you can use light to turn on the lightweight syntax option automatically on start-up. Documenting Code Using XMLDocs The first real line of the program in Listing 2-1 is not code but 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 or can be converted immediately to HTML by the F command-line compiler (fsc.exe). We cover using the F command-line compiler in more detail in Chapter 7. ■Tip The F command-line compiler (fsc.exe) options for generating HTML documentation are generate-html and html-output-directory. To generate an XMLDoc file, use -doc. Understanding Scope and Using “let” The next two lines of the program in Listing 2-1 introduce the start of the definition of the function wordCount and define the local value words, both using the keyword let: let wordCount text = let words = ... let is the single most important keyword you’ll use in F programming: it is used to define data, computed values, functions, and procedures. The left of a let binding is often a simple identifier but can also be a pattern. (See the “Using Tuples” section for some simple 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. 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, though not the definition itself. Here are two examples of invalid definitions that try to access variables outside their scope. As you can 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:Syme_850-4C02.fm Page 11 Tuesday, September 18, 2007 7:44 PM CHAPTER 2 ■ GETTING STARTED WITH F AND .NET 11 let badDefinition1 = let words = String.split text error: text is not defined let text = "We three kings" words.Length let badDefinition2 = badDefinition2+1 error: badDefinition2 is not defined Sometimes it is convenient to write let definitions on a single line, even when using the light syntax option. You can 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, where pat stands for pattern and expr1 and expr2 stand for expressions. The light syntax option simply provides a veneer that lets you optionally omit the in if expr2 is column-aligned with the let keyword on a subsequent line, and a preprocessing stage inserts the in token for you. Within function definitions, values can be outscoped by declaring another value of the same name. For example, the following function computes (nnnn)+2: let powerOfFourPlusTwo n = let n = n n let n = n n let n = n + 2 n This code is equivalent to the following: 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 the name of the value is no longer accessible from the current scope.Syme_850-4C02.fm Page 12 Tuesday, September 18, 2007 7:44 PM 12 CHAPTER 2 ■ GETTING STARTED WITH F AND .NET Because let bindings are just one 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 In the previous example, n1 and n2 are values defined locally by let bindings within the expression that defines n3. These local values are not available for use outside their scope. For example, the following 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 implemen- tation details that you don’t want revealed outside your functions or other objects. We cover this topic in more detail in Chapter 7. VALUES AND IMMUTABILITY In other languages, a local value is called a local variable. However, in F you can’t change the immediate value of locals after they’ve been initialized, unless the local is explicitly marked as mutable, a topic we return to in Chapter 4. For this reason, F programmers and the language specification tend to prefer the term value to variable. As you’ll see in Chapter 4, data indirectly referenced by a local value can still be mutable even if the local value is not; for example, a local value that is a handle to a hash table cannot be changed to refer to a different table, but the contents of the table itself can be changed by invoking operations that add and remove elements from the table. However, many values and data structures in F programming are completely immutable; in other words, neither the local value nor its contents can be changed through external mutation. These are usually just called immutable values. For example, all basic .NET types such as integers, strings, and System.DateTime values are immutable, and the F library defines a range of immutable data structures such as Set and Map, based on binary trees. Immutable values bring many advantages. At first it might seem strange to define values you can’t change. However, knowing a value is immutable means you rarely need to think about the object identity of these values—you can pass them to routines and know they won’t be mutated. You can also pass them between multiple threads without worrying about unsafe concurrent access to the values, discussed in Chapter 14. You can find out more about programming with immutable data structures at http://www.expert-fsharp.com/ Topics/FunctionalDataStructures.Syme_850-4C02.fm Page 13 Tuesday, September 18, 2007 7:44 PM CHAPTER 2 ■ GETTING STARTED WITH F AND .NET 13 Understanding Types F is a typed language, so it’s reasonable to ask what the type of wordCount is, and indeed F Interactive has shown it already: val wordCount : string - int int 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 - represents a function. No explicit type has been given in the program for wordCount or its argument text, because the full type for wordCount has been “inferred” from its definition. We discuss type inference further in the “What Is Type Inference?” sidebar 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 the programmer through the complexity of an API and to place constraints on code to ensure it can be implemented efficiently. However, unlike many other typed languages, the type system of F 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, though doing so can be useful. Table 2-1 shows some of the most important type constructors. We discuss all these types in more detail in Chapter 3 and Chapter 4. Table 2-1. Some Important Types, Type Constructors, and Their Corresponding Values Family of Types Examples Description int int 32-bit integers. For example: -3, 0, 127. type option int option, optionint A value of the given type or the special value None. For example: Some 3, Some "3", None. type list int list, listint An immutable linked list of values of the given type. All elements of the list must have the same type. For example: , 3;2;1. type1 - type2 int - string A function type, representing a value that will accept values of the first type and compute results of the second type. For example: (fun x - x+1). type1 ... typeN int string A tuple type, such as a pair, triple, or larger combination of types. For example: (1,"3"), (3,2,1). type int An array type, indicating a flat, fixed-size mutable collection of values of type type. unit unit A type containing a single value (), akin to void in many imperative languages. 'a, 'b 'a, 'b, 'Key, 'Value A variable type, used in generic code.Syme_850-4C02.fm Page 14 Tuesday, September 18, 2007 7:44 PM 14 CHAPTER 2 ■ GETTING STARTED WITH F AND .NET 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. Instantiations of generic types can be written using either prefix notation (such as int list) or postfix notation (such as listint). Variable types such as 'a are placeholders for any type. We discuss generics and variable types in more detail in Chapter 3 and Chapter 5. WHAT IS TYPE INFERENCE? Type inference works by analyzing your code to collect constraints. These are collected over the scope of particular parts of your program, such as each file for the F command-line compiler and each chunk entered in F Interactive. These constraints must be consistent, thus ensuring your program is well-typed, and you’ll get a type error if not. Constraints are collected from top to bottom, left to right, and outside in, which is important because long identifier lookups, method overloading, and some other elements of the language are resolved using the normalized form of the constraint information available at the place where each construct is used. Type inference also automatically generalizes your code, which means that when your code is reusable and generic in certain obvious ways, then it will be given a suitable generic type without you needing to write the generic type down. Automatic generalization is the key to succinct but reusable typed programming. We discuss automatic generalization in Chapter 5. Calling Functions Functions are at the heart of most F programming, and it’s not surprising that the first thing you do is call a library function, in this case, String.split: let wordCount text = let words = String.split ' ' text The function String.split takes two arguments. F Interactive reveals the type of String.split as follows: String.split;; val it: char list - string - string list To understand this type, let’s first investigate String.split by running F Interactive: String.split ' ' "hello world";; val it : string list = "hello"; "world" String.split 'a';'e';'i';'o';'u' "hello world";; val it : string list = "h"; "ll"; " w"; "rld" You can see that String.split breaks the given text into words using the given characters as delimiters. The first argument is the list of delimiters, and the second is the string to split.Syme_850-4C02.fm Page 15 Tuesday, September 18, 2007 7:44 PM CHAPTER 2 ■ GETTING STARTED WITH F AND .NET 15 String.split takes two arguments, but the arguments are given in a style where the argu- ments come sequentially after the function name, separated by spaces. This is quite common in F coding and is mostly a stylistic choice, but it also means functions can be partially applied to fewer arguments, leaving a residue function, which is a useful technique you’ll look at more closely in Chapter 3. In the earlier code, you can also see examples of the following: � Literal characters such as ' 'and 'a' � Literal strings such as "hello world" � Literal lists of characters such as 'a';'e';'i';'o';'u' � Literal lists of strings such as the returned value "hello"; "world" We cover literals and lists in detail in Chapter 3. Lists are an important data structure in F, and you’ll see many examples of their use in this book. WHAT IS “STRING” IN “STRING.SPLIT”? The name String references the F module Microsoft.FSharp.Core.String in the F library. This contains a set of simple operations associated with values of the string type. It is 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 String.split and open String. Other modules under these namespaces include List, Option, and Array. Since String is a standard .NET type, you can also use functions provided by the .NET Framework runtime located under System.String and other important namespaces such as System.Text. RegularExpresions. Throughout this book, we use both the .NET Framework library and the F additions extensively. We give an overview of the most commonly used .NET and F libraries in Chapter 10. Using Data Structures The next portion of the code is as follows: let wordCount text = let words = String.split ' ' text let wordSet = Set.of_list words This gives you your first taste of using data structures from F code, and the last of these lines lies at the heart of the computation performed by wordCount. It uses the function Set.of_list 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, though internally it is implemented using a data structure based on trees. You can see the results of converting data to a set by using F Interactive:Syme_850-4C02.fm Page 16 Tuesday, September 18, 2007 7:44 PM 16 CHAPTER 2 ■ GETTING STARTED WITH F AND .NET Set.of_list "b";"a";"b";"b";"c" ;; val it : Setstring = set "a"; "b"; "c" Set.to_list (Set.of_list "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. Using Properties and the Dot-Notation The next two lines of the wordCount function compute the result we’re after—the number of duplicate words. This is done by using two properties, Length and Count, of the values you’ve computed: let nWords = words.Length let nDups = words.Length - wordSet.Count F performs resolution on property names at compile time (or interactively when using F Interactive, where there is no distinction between compile time and run time). 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 : 'a list) = inp.Length Here the 'a indicates that the length function is generic; that is, it can be used with any type of list. We cover generic code in more detail in Chapter 3 and Chapter 5. Type annotations can be useful documentation and, when needed, should generally be added at the point where a variable is declared. 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 we discuss members in full in Chapter 6. Sometimes explicitly named functions play the role of members. For example, we could have written the earlier code as follows:Syme_850-4C02.fm Page 17 Tuesday, September 18, 2007 7:44 PM CHAPTER 2 ■ GETTING STARTED WITH F AND .NET 17 let nWords = List.length words let nDups = List.length words - Set.size wordSet You will see both styles in F code. Some F libraries don’t use members at all or use them only sparingly. However, judiciously using members and properties 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 does not contain enough type annotations to resolve the dot-notation, you will see an error such as the following: let length inp = inp.Length;; let length inp = inp.Length;; stdin(1,17): error: Lookup on object of indeterminate type. 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 simply by adding a type annotation as shown earlier. Using Tuples The final part of the wordCount function returns the results nWords and nDups as a tuple. let nWords = words.Length let nDups = words.Length - wordSet.Size (nWords,nDups) Tuples are the simplest but perhaps most useful of all F data structures. A tuple expression is simply 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 of site1 and sites are as follows: val site1 : string int val sites : (string int) (string int) (string int) 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:Syme_850-4C02.fm Page 18 Tuesday, September 18, 2007 7:44 PM 18 CHAPTER 2 ■ GETTING STARTED WITH F AND .NET fst site1 val it : string = "www.cnn.com" let relevance = snd site1 val relevance : int relevance;; val it : 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 following code: let url,relevance = site1 let site1,site2,site3 = sites In this case, the names in the tuples on the left of the definitions are bound to the respec- tive 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 if you try to use a triple where a pair is expected, then you’ll get a type-checking error before your code is run: let a,b = (1,2,3);; error: this pattern matches values of type 'int int' but is here used with values of type 'int int int'. The tuples have different lengths. Tuples are often used to return multiple values from functions, as in the wordCount example earlier. They are also often used for multiple arguments to functions, and frequently the tupled output of one function becomes the tupled input of another function. Here is an example that shows a different way of writing the showWordCount function defined and used earlier: let showResults (nWords,nDups) = printfn " %d words in the text" nWords printfn " %d duplicate words" nDups let showWordCount text = showResults (wordCount text) The function showResults accepts a pair as input, decomposed into nWords and nDups, matching the results of wordCount.Syme_850-4C02.fm Page 19 Tuesday, September 18, 2007 7:44 PM CHAPTER 2 ■ GETTING STARTED WITH F AND .NET 19 VALUES AND OBJECTS In F everything is a value. In some other languages everything is an object. In practice, you can use the words largely interchangeably, though F programmers tend to reserve object for special kinds of values: � Values whose observable properties change as the program executes, usually through the explicit mutation of underlying in-memory data or through external state changes � Values that refer to data or state that reveal an identity, such as a unique integer stamp or the underlying .NET object identity, where that identity may differ from otherwise identical values � Values that can be queried to reveal additional functionality, through the use of casts, conversions, and interfaces F thus supports objects, but not all values are referred to as objects. We discuss identity and mutation further in Chapter 4. Using Imperative Code The showWordCount and showResults functions defined in the previous section output the results using a library function called printfn: printfn " %d words in the text" nWords printfn " %d duplicate words" nDups For those familiar with OCaml, C and C++ printfn will look familiar as a variant of printf— printfn also adds a newline character at the end of printing. Here the pattern %d is a place- holder 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, where the F compiler checks that the subse- quent arguments match the requirements of the placeholders. There are also other ways to format text with F. For example, you could have used the .NET libraries directly: System.Console.WriteLine(" 0 words in the text", box(nWords)) System.Console.WriteLine(" 0 duplicate words", box(nDups)) Here 0 acts as the placeholder, though 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 sequen- tial expressions to cause effects in the outside world. As with let ... in ... expressions, it is sometimes convenient to write sequential code on a single line. You can do this by separating two expressions by a semicolon (;), and again this is the primitive construct of the language. 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 + twoSyme_850-4C02.fm Page 20 Tuesday, September 18, 2007 7:44 PM 20 CHAPTER 2 ■ GETTING STARTED WITH F AND .NET When executed, this code will print Hello World precisely once, when the right side of the definition of two is executed. F does not 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 is 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" nWords; printfn " %d duplicate words" nDups) ■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 light syntax option is used and the indi- vidual fragments of your sequential code are placed on separate lines beginning at the same column position. Using .NET Libraries from F The true value of F lies not just in what you can do inside the language but in what you can connect to outside the language. For example, F does not come with a GUI library. Instead, F is connected to .NET and via .NET to most of the significant programming technologies avail- able on major computing platforms. To emphasize this, our second sample uses two of the powerful libraries that come with the .NET Framework: System.Net and System.Windows.Forms. The full sample is in Listing 2-3 and 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() htmlSyme_850-4C02.fm Page 21 Tuesday, September 18, 2007 7:44 PM CHAPTER 2 ■ GETTING STARTED WITH F AND .NET 21 let google = http("http://www.google.com") textB.Text - http("http://news.bbc.co.uk") This example uses several important .NET libraries and will help you to explore some interesting F language constructs. We walk you through this listing in the following sections. 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 We discuss namespaces in more detail in Chapter 7. The earlier declaration simply means you can access any content under this path without quoting the long path. If it had not used open, you would 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 such as Microsoft.FSharp. Core.String without using long paths. We discuss modules in more detail in Chapter 7. MORE ABOUT OPEN Using open is an easy way to access the contents of namespaces and modules. However, there are some subtleties. For example, open doesn’t actually load or reference a library—instead, it reveals functionality from already-loaded libraries. Libraries themselves are loaded by referring to a particular DLL using r in a script or -r as a command-line option. Libraries and namespaces are orthogonal concepts: multiple libraries can contribute functionality to the same namespace, and each library can contribute functionality to multiple namespaces. Often one particular library contributes most of the functionality in a particular namespace. For example, most of the functionality in the System.Windows.Forms namespace comes from a library called System.Windows.Forms.dll. As it happens, this library is automatically referenced by F, which is why you haven’t needed an explicit reference to the library so far. You can place your code in a namespace by using a namespace declaration at the top of your file, discussed further in Chapter 7. In an earlier example, you saw that String in String.split referenced a value in the module Microsoft.FSharp.Core.String. By default, all F code is interpreted with an implicit open of the following namespaces and modules: � Microsoft.FSharp.Core: Contains modules such as String, Int32, Int64, and Option and contains types such as 'a option and 'a ref � Microsoft.FSharp.Core.Operators: Contains values such as +, -, , box, unbox, using, and lock � Microsoft.FSharp.Collections: Contains modules such as List, Seq, HashSet, Map, and Set and contains types such as 'a listSyme_850-4C02.fm Page 22 Tuesday, September 18, 2007 7:44 PM 22 CHAPTER 2 ■ GETTING STARTED WITH F AND .NET � Microsoft.FSharp.Control: Contains modules such as Lazy, Async, and IEvent and contains types such as 'a lazy and IEvent'a � Microsoft.FSharp.Text: Contains modules such as Printf If two namespaces have types, subnamespaces, and/or modules with identical names, then when you open these, you can access the contents of both using the same shortened paths. For example, the namespace System contains a type String, and the namespace Microsoft.FSharp.Core contains a module String. In this case, long identifier lookups such as String.split search the values and members under both of these, preferring the most recently opened if there is an ambiguity. Finally, if you ever have name collisions, you can define your own short aliases for modules and types, such as by using module MyString = My.Modules.String and type SysString = System.String. 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 will 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 use constructors; you will also see values being constructed using names such as Create or via one or more functions in a related module such as String.create or Array.init. You’ll see examples of each throughout this book. A form is an object; that is, its properties change during the course of execution, and it is 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 configu- ration 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 corre- spond 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 have constructed the form object as follows: open System.Windows.Forms let form = new Form() form.Visible - true form.TopMost - true form.Text - "Welcome to F"Syme_850-4C02.fm Page 23 Tuesday, September 18, 2007 7:44 PM CHAPTER 2 ■ GETTING STARTED WITH F AND .NET 23 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 to configure objects, such as forms, that support many potential configuration parameters that evolve over time. The object created here was 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 are said to alias the same object). For example, the following code sets the title of the same form, despite it being accessed via a different name: let form2 = form form2.Text - "F Forms are Fun" VALUES, METHODS, AND PROPERTIES Here are the differences between values, methods, and properties: � Simple values: Functions, parameters, and top-level items defined using let or pattern matching. Examples: form, text, wordCount. � Methods: Function values associated with types. Interfaces, classes, and record and union types can all have associated methods. Methods can be overloaded (see Chapter 6) and must be applied immediately to their arguments. Examples: System.Net.WebRequest.Create and resp.GetResponseStream. � Properties: A shorthand for invoking method members that read or write underlying data. Interfaces, classes, and record and union types can all have associated properties. Examples: System.DateTime.Now and form.TopMost. � Indexer properties: A property can take arguments, in which case it is an indexer property. Indexer properties named Item can be accessed using the ._ syntax. Examples: vector.3 and matrix.3,4. The next part of the sample creates a new RichTextBox control and stores it in a variable called 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. let textB = new RichTextBox(Dock= DockStyle.Fill) form.Controls.Add(textB) 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:Syme_850-4C02.fm Page 24 Tuesday, September 18, 2007 7:44 PM 24 CHAPTER 2 ■ GETTING STARTED WITH F AND .NET 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 textB.Text - html;; The final line will set the contents of the text box form to the HTML contents of the Microsoft home page. Let’s take a 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. We cover .NET I/O in more detail in Chapter 4, but 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 as follows: val http : string - string ■Note Static members are items qualified by a concrete type or module. Examples include System. String.Compare, System.DateTime.Now, List.map, and String.split. Instance members are methods, properties, and values qualified by an expression. Examples include form.Visible, resp. GetResponseStream(), and cell.contents.Syme_850-4C02.fm Page 25 Tuesday, September 18, 2007 7:44 PM CHAPTER 2 ■ GETTING STARTED WITH F AND .NET 25 XML HELP IN VISUAL STUDIO In a rich editor such as Visual Studio 2005, you can easily find out more about the functionality of .NET libraries by hovering your mouse over the identifiers in your source code. For example, if you hover over Dock in textB.Dock, you’ll see the XML help shown here: Summary In this chapter, you took a look at some simple interactive programming with F and .NET. Along the way, you met many of the constructs you’ll use in your day-to-day F programming. In the next chapter, you’ll take a closer look at these and other constructs that are used to perform compositional and succinct functional programming in F. ■Note In Chapter 3 you’ll use some of the functions you defined in this chapter, so if you’re using F Interactive, you might want to leave your session open as you proceed.Syme_850-4C02.fm Page 26 Tuesday, September 18, 2007 7:44 PM