Chapter 2
Introduction to F# & Essential Tools
IntroductionIn this chapter, we introduce the F# language and learn how to get our first program up and running. In doing so, we will discuss the basic rules around F# program structure and the essential tools you will need to develop rudimentary applications. As we progress through the book, and our experience grows, we will delve more deeply into code examples, tool examples, etc. Overview of F#F# is a strongly typed, first-class .NET programming language designed by Don Syme and others at Microsoft Research. It has its origins in the ML approach to language design and is a close relative of OCaml. While billed primarily as a functional language, F# is in actuality a multi-paradigm language. This means that it supports imperative, OO, and functional styles of programming. A fully supported .NET language in its own right, F# interoperates naturally with code written in other .NET languages such as C# and VB.NET. F# is available as a free download, and will be integrated into Microsoft Visual Studio 2010. You can find the most recent F# Language Reference here. For this book, I started with the September CTP version 1.9.6.2 and transitioned to the October CTP version 1.9.7.8. All of the code has been tested against 1.9.7.8. Please consult the language reference for the basic rules and regulations of F#, e.g. the rules around defining a valid symbolic name, etc. F# Program StructureYou create F# programs using a standard text editor or IDE. With an IDE such as Visual Studio 2008 or Visual Studio 2010, you get the benefit of syntax highlighting, IntelliSense, etc. As with other programming languages, F# adheres to a specific set of syntax and structural rules, uses a well-defined set of keywords, etc. We will cover the core keywords, syntax, and structure of F# programs starting in this chapter, and will continue to build on this knowledge throughout the rest of the text. #lightDue to its heritage, F# is compatible with OCaml. This means that you can write F# programs that are fully compliant with OCaml’s syntax and semantic rules. Many developers, however, find OCaml’s syntax to be quite “chatty” or “heavy”; therefore, F# supports a more streamlined programming syntax and semantic, known as “light” mode. You instruct F# to adhere to this light mode by placing the #light directive at the top of each of your F# program files. For all intents and purposes, all of the F# programs that you encounter and write will use the #light directive. If you fail to use place the #light directive at the top of each of your F# program files, you will default to OCaml-compatible “verbose mode.” This will require the use of particular keywords and symbols to terminate expressions, etc. In the spirit of keeping things streamlined and to the point, we will not cover the verbose mode syntax in this book, nor will we cover structures and/or symbols that are in F# solely to support OCaml compatibility. Whitespace MattersWhen you write F# programs and use the #light[1] directive, whitespace becomes significant to the structure and execution of the program. Whereas other languages, e.g. C#, use braces or other symbols to group compound statements, F# uses whitespace indentation for the same purpose.[2] Code that is vertically aligned by whitespace indentation is considered to be semantically related. Python programmers will feel right at home with this concept. Note that F# does not recognize TAB as a valid whitespace character when used for aligning code blocks in #light mode. This means that you need to use spaces to align your code. Before you groan too loudly, read the next two points: 1. The number of spaces doesn’t seem to matter. You can use 1 or more. I typically use 4, which matches the equivalent TAB. 2. When working in a Visual Studio F# Project (more on this shortly), the IDE converts TAB to spaces automatically. So, you can use the TAB key to align your code, and Visual Studio does the right thing in terms of ensuring your F# files have the right kind of whitespace. I assume you can set up your editor of choice to replace TABs with spaces as well. CommentsF# provides several different types of comment structures: single line comments, multiline comments and document (doc) comments. Let’s take a look at each type below. Single Line CommentsSingle line comments begin with a //. Everything following the // is considered to be a comment. The comment extends to the end of the source code line. // This is a single-comment. Everything from the start is treated as a comment. Multiline CommentsMultiline comments begin with (* and end with *). (* This is a multiline comment with only one line *) (* This is another Document (Doc) CommentsDoc comments start with /// and are used to embed comments in the code from which XML or HTML documentation can be produced automatically. These comments are normally placed before the definitions of functions, data types, classes, discriminated unions, etc. We will discuss each of these structures in depth in future chapters. /// This is a doc comment that can be converted to useful documentation. KeywordsF# has a small set of reserved keywords. These keywords cannot be used as value or identifier names, as they have special meaning to the compiler:
The following keywords are part of OCaml, and should be treated as reserved in F#:
And, for completeness, we note that the keywords below are not currently used, but are reserved for future use:
Don’t worry that you may not understand most (or any!) of these keywords at this point. We will explore each of them throughout the course of this book. For each keyword, we will discuss its use in F# and how it applies to functional programming in general. Statements vs. ExpressionsIn many programming languages, there is a sharp distinction drawn between statements and expressions. It is an important distinction, since it dictates where you can use given programming structures, and how these structures are ultimately evaluated. Let’s come up with working definitions that we can rely on throughout the text: StatementsStatements are lines of code that impart a directive – they “do” something. Statements do not evaluate to a value, and cannot be passed into functions as parameters. F# has no statements. ExpressionsExpressions are lines of code that evaluate to a value, e.g.: let x = 100 In F#, every program construct is an expression. This means that all program constructs evaluate to a value of a concrete data type. The fact that all constructs in F# are expressions is important, because it influences the way we write our programs. If you do not understand the difference between statements and expressions, re-read this small section until you are comfortable with the difference. The take-away here is that F# has no statements, only expressions. Note that one oddball case that comes up in the statement/expression scheme of things concerns let. By itself, let does not always evaluate to an expression, e.g., it cannot be used as the last line of a function. In these cases, you must supply the let’s symbol, e.g., x in the example above. Accessing Existing .NET TypesIn some of the examples in this book, and certainly in real-world programs, you will need to access functionality present in external libraries and modules. To gain access to the functionality contained in these libraries and modules, we use the F# keyword open. openThe open keyword (like the C# using statement, brings an existing namespace into scope. This affords us the convenience of using the namespace’s interfaces, classes, etc. without having to fully qualify the names each and every time. The open statement is called an import statement, and takes the following form: open module[3]-or-namespace Here, namespace must be a valid .NET namespace – either a built-in namespace or one of your creation (or third-party creation). Note that to gain access to a given namespace, you must have a reference to the proper .NET type-container such as a DLL, e.g., System.Windows.Forms.dll. If the IDE reports that it cannot resolve an open expression, make sure you have a reference to the DLL containing the namespace you’re trying to open. Let’s look at an example. To gain access to the classes in the .NET System namespace, we can either fully qualify the name, e.g., System.Environment, or we can open the System namespace and use the Environment class without qualification. The following example demonstrates the difference: let[4] fpath =
open System let fpath2 = Via open System, we get access to System’s publicly available types, classes, interfaces, etc. without needing to fully qualify their names. The open declaration makes the names available in the code that follows the declaration, up to the end of the enclosing namespace, module, or file. When you use multiple import declarations, they should appear on separate lines. There is no special line-termination character. Dealing with AmbiguityThere are times when you open multiple namespaces, and the same name (function, type, etc.) appears in more than one of them. Unfortunately, the F# compiler does not emit an error or warning when ambiguities or name clashes occur. Instead, F# uses a “last one wins” strategy, giving preference to the more recently opened module or namespace. As of this writing, the MSDN documentation suggests “being careful” when using namespaces that could export colliding names. If you do open namespaces that create ambiguity, you can explicitly specify the construct you want by spelling out the entire, qualified name. When in doubt, be verbose! Namespaces Open by DefaultThere are certain namespaces that are so common that F# makes them available automatically. These are as follows: · Microsoft.FSharp.Core o Contains basic F# type definitions for built-in types such as int and double. · Microsoft.FSharp.Core.Operators o Contains core arithmetic operations such as + for addition and * for multiplication. · Microsoft.FSharp.Collections o Contains immutable collection classes such as List and Array. · Microsoft.FSharp.Control o Contains types for control constructs such as lazy evaluation and asynchronous workflows (covered later). · Microsoft.FSharp.Text o Contains functions for formatted IO, such as the printf function. Your F# programs can use the publicly available constructs from these default libraries without using an open statement and without fully qualifying their names. Tools of the TradeAfter downloading and installing F#, you should see a menu item under the Windows Start menu that has the title Microsoft F# CTP 1.9.7.8. Under this menu item, you will find the core F# tools. F# Interactive (Console)F# ships with an interactive, command-line style interactive programming window called the F# Interactive Console. The console executable, fsi.exe, lives under the F# installation bin directory. On my machine, the full path to the console is: C:\Program Files\FSharp-1.9.7.8\bin\fsi.exe This console enables you to enter and execute F# expressions, and receive feedback. The F# Interactive Console runs stand-alone; therefore, you do not need to have Visual Studio running to launch it. Using the F# Interactive Console is a great way to explore the F# language, interactively test blocks of code, incrementally prototype algorithms, etc. You can, and I suggest you should, use the F# Interactive Console to run and test all of the examples in this book. To ensure that your F# environment is up and running correctly, click the Windows Start menu, find the Microsoft F# CTP folder (see above screen shot - your CTP version may be newer than mine – or you may be working with the final release) and click on the F# Interactive (Console) menu item. You should see the following console window appear:
This is the F# Interactive Console Window. The normal prompt is a greater than symbol (>), which indicates that F# is ready to accept a new expression. Let’s use a simple example to see how this works. Any programming book worth its salt includes a “hello, world!” program, so in the venerable tradition of Kernighan and Ritchie (K&R), let’s make sure F# is installed correctly by entering in the following line of F# code in the F# Interactive Console: printfn “hello, world!”;; Note that you terminate expressions in the F# Interactive Console using a pair of semicolons (;;). F# will respond by printing the line of text, and by providing some feedback, as shown in the following screen shot:
Don’t worry too much about the feedback. Here F# is telling us that the expression it just evaluated (the “it”) returned F#’s equivalent of void (unit, also expressed as ()). We’ll cover these concepts shortly. Note that F# is a case-sensitive language, i.e., case is significant. You cannot, for example, enter PRINTFN “hello, world!”;;. F# will produce an error letting you know that PRINTFN is not a recognized command or function: error FS0039: The value or constructor 'Printfn' is not defined.
Notice the F# responded by outputting a dash (-), indicating that it’s waiting for more input. At this point, you can enter another F# expression, or terminate the current expression using ;;. If you enter another expression, F# adds this to the current “expression set”. It continues to add expressions to its expression set until you terminate the set using ;;, at which point it will execute all the expression in the set at once. In the following screen shot, we entered 2 expressions for F# to evaluate.
The F# Interactive Console has a handful of directives that it understands. For a list of these directives, enter #help;; at the command prompt (>), as shown below:
The first three entries are used to access pre-existing libraries and assemblies. For example, if you want to access .NET libraries that provide Web access, you need to access the System.Web.dll. This DLL is not available by default; therefore, to gain access to it in the F# Interactive Console, you would enter the following command: #r “System.Web.dll” Note that the double quotes surrounding the library name are necessary for this command to work properly, as shown in the following screen capture:
At the moment, there’s nothing useful we can do with this library, since we don’t have enough F# syntax as context. I just wanted you to get a feel for what these command do. We’ll see how to leverage the .NET class libraries in gory detail in Chapter 13. We use the #path command to extend the search path that F# uses in order to find libraries and assemblies, and the #load command to load an F# file into memory, compile it, and make it available for use. We can use the #time command to activate basic profiling for F# expressions, measuring their execution times For example, Issue the #time “on” command to activate profiling, and #time “off” to deactivate it. We use both in the following example:
As you can see from the output, activating timing causes the F# Interactive Console to output CPU time and generation-centric garbage collection information. At the time of this writing, there is no way to use the F# Interactive Console to get help on a keyword or built-in function. If you want to influence how the F# Interactive Console behaves, you can launch it yourself and provide command-line switches. For example, if you want to launch the console so that standard output is suppressed, provide the --quiet switch, as in the following example:
For a full list of all the switches available, enter fsi --help on the standard command line. Unfortunately, once the F# Interactive Console is up and running, there doesn’t seem to be a way to reset its environment to eliminate all bound references, etc. When I need to do this, I generally close my current session and start anew. When you enter something the F# does not understand completely, it will display an error message in red text, as shown below:
You will receive error output even if you are running in quiet mode; however, that is the extent of the console’s debugging capabilities. The F# Interactive Console does not have a provision for debugging F# code snippets. To debug scripts or programs, you need to use the full Visual Studio IDE and debugger. Once you’re done using the F# Interactive Console, you can exit by closing the console window or by executing the #quit;; command. F# Interactive Console and Visual StudioIf you have Visual Studio installed, and you install F#, F# will integrate with Visual Studio by adding a set of project templates and a handful of new menu items. These changes enable you to create F# solutions and projects, and to use the Visual Studio IDE and the F# Interactive Console in unison. To display the F# Interactive Console in Visual Studio 2008, go the main menu and select View -> Other Windows -> F# Interactive. One convenient feature the integrated console window supports is the ability for you to reset its environment: right-click your mouse over the window and select Reset Session from the context menu. The best and easiest way to work with the integrated console is to develop code in the IDE editor, select it, and then right-click the mouse over the selection to bring up the editor’s context menu. If you select the menu item Send to Interactive, Visual Studio will send the selected code to the integrated F# Interactive Console, and will automatically append a ;; to the end of the entire block, which you use to terminate a set of statements in the console window. In response, the console will execute the F# code and provide immediate feedback. Note that when working in the F# Interactive Console directly (vs. working in the IDE and using Send to Interactive), you must explicitly terminate each logical expression with ;; (two semicolons). A logical expression can span more than one line. This means that you can have several F# sources lines terminated by a single ;; pair. Throughout this text, we will see examples of single-line and multi-line expressions used in the F# Interactive Console. When working outside of the F# Interactive Console, a normal newline character terminates F# expressions implicitly – you do not need to use ;;, e.g.,. when you’re building an F# application in the IDE and build it to produce a DLL or EXE. As you start using the F# Interactive Console, you will notice that for each expression/program you enter, F# will provide feedback. This feedback will consist of all expression/program output plus F#’s evaluation of the data types involved. I found (and still sometimes find) F#’s typing signatures a little confusing to decipher, so I will spend some time explaining what the type signatures mean as we encounter them. F# Projects in Visual StudioAfter installing F#, you will find that Visual Studio offers a new set of project templates. This is what the New Project window looks like on my machine after installation:
With the current CTP version of F# (1.9.6.16), Visual Studio offers the following project types: · F# Application. Select this type of project to create a command-line (console) application (EXE). · F# Library. Select this type of project to create an F# library. A library is a DLL that can be included in other .NET projects. When you select this project type, the resulting solution will contains two files: o Module1.fs. This is the default source file for the library that will be compiled into the output DLL. o Scritp.fsx. This is a “script” file that you can use to work with F# interactively. The code that you enter into this file is not included in the final output DLL. · F# Tutorial. Select this type of project to create an F# source file that illustrates key features of the F# language. This project creates an EXE. We will wait until the chapter on functions (Chapter 8) to provide full IDE-based examples, since at this point you will be prepared to write full, simple F# programs. The F# CompilerWhen you use Visual Studio to build an F# application, it
executes the underlying F# compiler, fsc.exe. On my machine,
the compiler can be found here: Like any other .NET compiler, the F# compiler supports a variety of configuration options. For more information about the F# compiler and its options, please see the F# compiler documentation on MSDN. Printing Values using PrintfAs you work through examples in this book and on the wild Web, you will be displaying values to the F# console a good deal of the time. Be advised that F# ships with a module called Printf that enables you to display values in a variety of formats. For those of you coming from a C# or VB.NET background, check out Printf.sprint and Printf.kprintf. What You Need to Know· F# is a multi-paradigm language whose main thrust is to support functional programming constructs. · F# is a fully supported .NET language capable of interoperating with other .NET languages. · F# is based on a language called OCaml. You can use OCaml syntax to write F# programs; however, OCam’l’s syntax is verbose. F# supports a less verbose “light” syntax that most developers favor. To ensure you are using the light version, you must place the #light directive at the very top of each of your F# source code files. We use the light syntax in this book. · The primary syntactical construct in F# is an expression, i.e., something that evaluates to a value. F# does not have statements, per se. · You can use the F# Interactive Console (fsi.exe) to explore F#. We recommend you use the console to experiment with simple expressions while learning the F# language. · The F# Interactive Console can be run stand-alone, integrated with Visual Studio 2008, or both. · F#’s compiler is called fsc.exe. Similar to compilers for most other languages, it has a variety of options that you can use to control how it operates. · Using Visual Studio, you can create console applications, libraries (DLLs) and applications (EXEs) with F#, just like you would do with other .NET languages. You can even link and execute modules compiled from F# seamlessly with modules written in other .NET languages.
[1] If you are not using the #light directive, you are defaulting to using full OCaml syntax, which obviates the need for significant whitespace, but imposes the burden of semantically significant keywords. [2] Python programmers will feel at home here. [3] In F#, a module is a way to package code. It is a grouping of F# code, such as values, types, and function values. Grouping code in modules helps keep related code together and helps avoid name conflicts in your program. We will cover modules later. [4] We will cover let shortly. It simply introduces a new variable. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
FeedbackWe welcome your feedback. If you have comments or questions about this chapter, please feel free to e-mail us at Keep Reading |