|
|
| Home Page | The F# Journal |
Introducing F#The F# programming language is a research project at Microsoft Research in Cambridge, England. The F# language combines the efficiency, scripting, strong-typing and productivity of ML with the stability, libraries, language interoperability and tools of Microsoft's .NET platform. Microsoft Visual Studio is the main development environment for F# programmers:
The F# development environment for Visual Studio provides two different modes of execution for F# code:
Articles in the F# Journal quote F# code as it would appear in an interactive session. Specifically, the interactive session provides a
For example, the following code is quoted as it would appear in an interactive session, including the result of the calculation and its type: > 2 + 3;; val it : int = 5 The remainder of this article describes the basic syntax of the F# programming language. Comments
The F# language provides two different syntaxes for comments. Comments can be enclosed in
The specific style of commenting with
For example, the following is an annotated definition of the Fibonacci function: /// The Fibonacci function let rec fib = function | 0 | 1 -> 0 | n -> fib(n-1) + fib(n-2) The comment appears in the graphical throwback provided by Intellisense:
When developing large projects or libraries, this style of commenting can be used to make the API easier to navigate and explore. Primitive types
The F# language provides a variety of primitive types, including
The
The following examples demonstrate the most important primitive types: > ();; val it : unit = () > true;; val it : bool = true > 'a';; val it : char = 'a' > 3;; val it : int = 3 > 2.4;; val it : float = 2.4 > "foobar";; val it : string = "foobar" The F# language provides an unusual feature called type inference . This allows the compiler to work out the type of an expresson without requiring explicit annotation. Note that none of the previous examples required any type annotations. This is typical of F# code. Arithmetic
The numeric types, such as
> 1 + 2 * 3;; val it : int = 7 > 3.4 + 2.3 * 7.7;; val it : float = 21.11
Some other types also provide operators. For example, the
> "foo" + "bar";; val it : string = "foobar" The syntax described so far allows an F# interactive session to be used as a calculator. The language becomes much more useful when functions and compound types, such as tuples, are used to compose programs. TuplesThe simplest set of inferred types in F# are the tuples. A tuple is simply a compound type composed of a fixed number of other types. Tuples are written as comma-separated values. For example, the pair (2, 3) : > 2, 3;; val it : int * int = (2, 3)
Note that the type has been inferred as
RecordsUser-defined data structures include records and variants. Both are defined with the type declaration. For example, the following declares a record type representing rational numbers: > type rational = {num: int; denom: int};;
type rational = { num : int; denom : int; }
The fields of a record
> let add p q =
{num = p.num * q.denom + q.num * p.denom;
denom = p.denom * q.denom};;
val add_ratio : ratio -> ratio -> ratio = <fun>
For example, 1/3 + 2/5 = 11/15 : > add_ratio {num=1; denum=3} {num=2; denum=5};;
val it : ratio = {num = 11; denum = 15}
Variants are the other user-defined type. VariantsThe declaration of a variant type lists all possible shapes for values of that type. Each case is identified by a name, called a constructor , that serves both for constructing values of the variant type and inspecting them by pattern-matching. Constructor names are conventionally capitalized to distinguish them from variable names (which start with a lowercase letter). For example, the following variant type represents a symbolic expression that may contain integers, variables, sums and products: > type expr =
| Int of int
| Var of string
| Plus of expr * expr
| Times of expr * expr;;
type expr =
| Int of int
| Var of string
| Plus of expr * expr
| Times of expr * expr;;
Note that the F# interactive session repeats the type definition.
Values of this type may be constructed using the constructors
> Plus(Int 1, Times(Int 2, Var "y"));; val it : expr = Plus(Int 1, Times(Int 2, Var "y")) Variant types are very useful and are used to declare a variety of concrete data structures such as binary trees. Pattern matchingThe utility of variant types lies in the ability to pattern match over them.
The F# language provides two syntaxes for pattern matching. The
The
match expression with | pattern -> ... | pattern -> ... | pattern -> ... The expression corresponding to the first pattern that matches is then evaluated and returned.
Patterns may contain primitive values, tuples, records, variant type constructors, variable names and a catchall pattern denoted
Subpatterns may contain alternatives, denoted
In the context of the variant type representing a symbolic expression, the following pattern match tests whether the expression
match e with | Int _ | Var _ -> true | Plus _ | Times _ -> false
For example,
> match Int 7 with | Int _ | Var _ -> true | Plus _ | Times _ -> false;; val it : bool = true The use of pattern matching to dissect data structures is much more useful in the context of functions. Functions and Variables
As F# is a functional programming language, functions are just like any other type. A function that accepts an
Functions are declared using one of three different syntaxes. The simplest syntax is
> fun n -> 2 * n;; val it : int -> int
This function may be applied to an argument by bracketing the function definition and using the syntax
> (fun n -> 2 * n) 3;; val it : int = 6
Variables are bound using the syntax
> let double = fun n -> 2 * n;; val double : int -> int Note that the information returned by the F# interactive session now contains the name of the variable that has been defined as well as the function type. Function definitions like this are so short that there is a shorthand notation: > let double n = 2 * n;; val double : int -> int
As values in F# programs are often immutable, a variable definition often refers to the previous definition of a variable with the same name. For example, the following computes
2 * 3 - 1
one step at a time, superceding the variable
> let x = 2;; val x : int = 2 > let x = 3 * x;; val x : int = 6 > let x = x - 1;; val x : int = 5
However, function definitions often need to refer to themselves (
recursion
). A
The following defines a recursive factorial function: > let rec factorial n =
if n=0 then 1 else n * factorial(n - 1);;
val factorial : int -> int
For example, 5! = 120 : > factorial 5;; val it : int = 120
The
> let rec factorial = function
| 0 -> 1
| n -> n * factorial(n - 1);;
val factorial : int -> int
Even with the minimal subset of the F# language covered so far, it is already possible to write some interesting example programs. Example
A function to raise a floating-point number
> let rec pow n x =
match n with
| 0 -> 1.
| n -> x * pow (n - 1) x;;
val pow : int -> float -> float
For example, 3 4 = 81 : > pow 4 3.;; val it : float = 81.0
This function used pattern matching and recursion but also made use of a more subtle feature known as
currying
. A curried function is a function that returns another function. In this case, the result of
So the
> let square = pow 2;; val square : float -> float > let cube = pow 3;; val square : float -> float For example, 5 2 = 25 : > square 5.;; val it : float = 25.0;; To read more articles about the F# programming language, please subscribe to the F#.NET Journal .
|
| © Flying Frog Consultancy Ltd., 2007 | Contact the webmaster |