This section takes a quick tour through some of the
most important expressions and definitional forms used in F# code,
in particular
See also the tutorials on
calling .NET libraries and
using F# code from .NET libraries.
Constants as much as in other languages:
3, 0xFF, 4096 | Integer Constants |
"Hello", "World\n", "Tab\tTab" | String Constants |
3.1215, 1000.045, 1.0e6 | Floating Point Constants |
Lists are a commonly used data structure. They are not mutable, i.e.
you can't delete an element of a list - instead you create a new list with the element
deleted. List values often share storage under the hood, i.e. a list values only allocate
more memory when you actually execute construction operations.
[] | The empty list |
["Hello"; "World"] | A list value of length 2 |
"Hello" :: ["World"] | A list value produced by adding an element to the front of a list |
["Hello"] @ ["World"] | Appending two lists |
Tuples let you group several values into a single value.
(3,4) | A pair of integers |
("Hello", 4, "World") | A triple of a string, an integer and another string |
let AddThree(a,b,c) = a + b + c | A function that adds up all three integers in a triple |
"Hello" :: ["World"] | A list value produced by adding an element to the front of a list |
let AddPairs(a,b) (d,e) = (a+d, b+e) | A function that produces a new pair by adding two pairs point-wise |
Conditional Expressions evaluate one of two expressions depending on the value of a guard:
if (4 > 3) then "Hello\n" else "Goodbye\n" | A conditional expression |
if (4 = 3) then AddThree(1,3,5) else 6 + 7 | A conditional expression where the branches are expressions |
F# statements are simply expressions that return a special value called of a
type called "unit". Multiple expressions can be chained together using the
sequencing ";" operator;
while (!x > 3) do
x := !x - 1;
print_endline("x = "^string_of_int !x);
done; | A while loop |
for i = 0 to Array.length(x) - 1 do
Printf.printf("x[%d] = %d\n" i x.(i);
done; | A for loop |
print_string "Hello";
print_string " ";
print_string "World\n"; | A sequence of statements |
form.Text <- "Open File"; | An assignment to a mutable field or a .NET property |
if (form.Text = "Close" or
form.Text = "Open") then
print_endline "Open Close"; | A conditional statement |
F# functions and values are typically defined using the let and/or let rec
syntax. Definitions may be local, i.e. you can define new functions and values
within the expression that is the definition of another value. These definitions
are similar to local definitions in
Visual Basic, C# or C, except that you can both define functions and return
these function values as part of the overall result of the expression. The heavy use
of local bindings is common in F# code as it is by far the easiest way to ensure that
code and data is private. Local definitions may of course "capture"
variables.
let x = 1 + 2 | A simple value definition |
let myConcat x y = String.Join(x,".",y) | A function definition whose body uses a call to a .NET library function |
let myListAnalyzer l =
let even n = n mod 2 = 0 in
let len = List.length l in
let sameAsLength n = (n = len) in
if (List.exist even l) then
Printf.printf
"there is an even number in the list\n";
if (List.exist sameAsLengthl) then
Printf.printf
"yes, the length occurs in the list\n"; | A definition with several inner definitions |
Recursion is a way of specifying a function or
value whose definition depends on itself. The most common use of recursion is to
define function values that walk over data structures. Functions can also
be mutually recursive.
let rec fib n =
if n < 2 then 1 else fib (n-2) + fib(n-1) | A simple recursive definition |
Types are at the heart of F# programming.
Every term is given a static type, and for a certain subset of types known
as regular F# types this information is essentially fully descriptive of the useful type information
carried by values of that type type: in this sense most F# code is statically typed.
Regular F# types include record types, discriminated union types, abstract types,
variable types, tuple types, function types and type abbreviations. Some regular F#
types are shown in the table below.
int -> int | A function type |
'a | A variable type, for use from generic code |
string | A primitive type |
int list
list<int> | Two ways of writing the same constructed type |
int * int | A tuple type |
Essentially all .NET types are also types in F# code.
All .NET value types, all .NET delegate types, and all sealed (final) .NET reference types
are indeed regular F# types, and indeed a number of F# type that appear to be
built-in to the F# language are actually simply equivalent to
.NET types (e.g. string = System.String, int = System.Int32). The
full table of type identifiers pre-assigned to .NET or F# library is shown below.
type int = int32 = System.Int32 | 32-bit integers |
type exn = System.Exception | Exceptions |
type bool = System.Boolean | Boolean values |
type string = System.String | Unicode strings |
type 'a array = 'a[] | One-dimensional arrays, where 'a[] is C#'s array type |
type sbyte = System.SByte | 8-bit signed integers |
type byte = System.Byte | 8-bit unsigned integers |
type int16 = System.Int16 | 16-bit signed integers |
type uint16 = System.UInt16 | 16-bit unsigned integers |
type uint32 = System.UInt32 | 32-bit unsigned integers |
type int64 = System.Int64 | 64-bit signed integers |
type uint64 = System.UInt64 | 64-bit unsigned integers |
type float32 = System.Single | 32-bit IEEE floating point numbers |
type float64 = System.Double | 64-bit IEEE floating point numbers |
type nativeint = System.IntPtr | natively sized integers |
type unativeint = System.UIntPtr | natively sized unsigned integers |
type decimal = System.Decimal | very large integers |
type bigint = Microsoft.FSharp.Math.BigInt | arbitrary sized integers |
type bignum = Microsoft.FSharp.Math.BigNum | arbitrary sized rationals |
type obj = System.Object | used as a uniform representation of all values, also top type for all reference types |
Some .NET types such as System.Object typically require the use of upcasts
to generate values of that type, and downcasts and type tests, i.e. the design of the
type makes essential use of the fact that .NET objects carry dynamic type information.
to recover interesting properties of values of that type. For example, neither the
F# type obj (which is equivalent to System.Object) nor the F# type exn
(which is equivalent to System.Exception) are regular F# types: you will essentially
always have to do type tests or pattern matching tests
in order to recover information from values of these types,
(in the case of exception values some pattern matching is also possible, but this is
effectively equivalent to doing type tests).
Some F# and .NET types are
generic (also called polymorphic), e.g the F# type list
and the .NET type System.Collections.Generic.Dictionary.
Thus the type int list is different to the
type string list. In F# code you can write generic instantiations using the Caml syntax
"string list" or the C# syntax "list<string>".
F# is statically typed, but frequently code will not contain many
type annotations. This is because F# uses type inference to automatically determine
the types of expressions. Type inference demands that types 'match up' in obvious ways.
Type inference is a global process over an entire source file.
Type inference automatically generalizes code to be as generic as possible.
To see the types inferred for the top level definitions in your program
use the -i compiler switch
Frequently code that uses .NET library calls (and in particular libraries which make
heavy use of overloading) will need to be annotated with extra type annotations in order
to assist the type inference process. The F# compiler type checker will indicate when further
type annoations are needed.
Function types
are the types given to first-class function values and are
written int -> int. They are similar to .NET delegate types,
except they aren't given names). All F# function identifiers can
be used as first-class function values, and anonymous function values
can be created using the (fun ... -> ...) expression form.
F# code commonly uses iterated or curried
function types where multiple arguments become successive applications of
individual values to a function value, e.g. int -> int -> int.
int -> int -> int | A curried function type, i.e. the type for a value accepting a pair of integer arguments |
(int -> int) -> int | A function type for a value that accepts another function as an argument |
val map: ('a -> 'b) -> 'a list -> 'b list | The specification of the type of a generic function, e.g. in an interface file |
(fun x -> print_string "x = "; print_int x) | A first-class function value |
print_endline "Hello World" | Calling a function |
let text = "x = " in
List.iter (fun x -> print_string text;
print_endline x) ["abc";"def"] | Calling a function, giving it a first-class function value which captures a free variable to a library function |
let text = "x = " in
let print x = print_string text;
print_endline x in
List.iter print ["abc"; "def"] | Using a defined function as a first-class function value |
Pattern matching is a way of performing discriminations on values that goes
well beyond what is possible with conditionals and simple switch statements. Pattern
matching can decompose on the concrete structure of types or on the structured results
of abstract operations (e.g. on operations that return option types).
let (x,y) = ("abc","def") in
print_endline x;
print_endline y | Binding multiple values by matching against a tuple value |
match ("abc","def") with
(x,y) ->
print_endline x;
print_endline y | An equivalent way of performing the same operation |
match myList with
| [] -> print_endline "nil";
| _ :: _ -> print_endline "cons"; | Matching against a list value |
let rec printList(l) =
match l with
| [] -> Console.WriteLine("nil");
| h :: t ->
Console.WriteLine("cons: " ^ h);
printList(t) | A recursive function to print a list using a .NET library call |
let rec printTable t =
for i = 0 to t.buckets.Length do
printBucketChain t.buckets.(i);
done
and printBuckets b =
match l with
| [] -> Console.WriteLine("nil");
| (a,b) :: t ->
Printf.printf "key: %d, val: %d" a b;
printBuckets t | A pair of recursive functions to print the contents of a hash table |
F# type definitions are either record types, dicriminated unions or
type abbreviations. F# types can use the type constructors defined
by these type definitions.
type 'a list =
| Cons of 'a * 'a list
| Nil
type 'a option =
| Some of 'a
| None | The definitions of some generic types in the F# core library. Here 'a is a type variabl |
type ('a, 'b) Hashtbl =
{ mutable contents: ('a,'b) buckets array;
mutable count: int }
and ('a,'b) buckets = ('a * 'b) list | The (private) definition of the represenation of hash tables used in the F# library. Here 'a is the key type and 'b the data carried in the range of the table |
type ('a, 'b) t = ('a, 'b) Hashtbl | An abbreviation used in the Hashtbl module. |
type person =
{ Name: string;
DateOfBirth: System.DateTime; } | A record type definition |
type person =
{ mutable Name: string;
mutable Count: int; } | A record type definition with a mutable field |
type Agent =
| Publisher of company
| Author of person | A discriminated union type definition |
The items in a single F# source code
file specify a module. As with OCaml the default
name of the module is formed by capitalizing
the first character of the base name of the file that was compiled. Other F# modules
must open modules which they import, or else explicitly refer to items from
those modules by using long identifiers.
List.map | A long identifier to access a value from an F# module |
open List | Making the values and other identifiers from an F# module available in the scope of a file |
Microsoft.FSharp.MLLib.List.map | Another way of accessing a value using a long identifier with a fully qualified namespace. The namespaces Microsoft.FSharp and Microsoft.FSharp.MLLib are automatically opened in all F# code. |
module MyComponent | Name the module for the current file (when placed at the head of a file). |
module L = List | Abbreviate a module for the purposes of this file. |
module Stack = struct
type 'a stack = Stack of 'a list
let pop (Stack(a::b)) = Stack(b)
...
end
| Define a nested module. |
Interface signatures are files that define
the public types and values that are exported from a compilation unit. They are
placed in a file ending qith suffix .mli or .fsi.
type out_channel
val input_line: out_channel -> string | A signature containing an abstract type anf an operation on that type |
type myType = { Name: string } | A signature element revealing a concrete type definition |
val map: ('a -> 'b) -> 'a list -> 'b list | A signature element for a generic function value |
Files specifying interface signatures should be added to the command line when compiling
the module they are constraining.
Files specifying interface signatures should be added to the command line when compiling
the module they are constraining.
Members are values associated with a named type
and are accessed via a dot-notation. Members can be associated with most F# named types, e.g.
records, unions and classes (see below). Members can be static, that invoked through the type name
rather than through an instance of the type.
type Data =
{ first: string;
second: string; }
with
member x.Together = x.first + x.second
end | Augmenting a type with a property member |
type Data =
with
member x.First = x.first
member x.Second = x.second
member x.GetReversedTogether() = x.second + x.first
end | A post-hoc augmentation of a type with 2 properties and one method member. Post-hoc augmentations are only allowed within the same file as the definition of the type) |
Classes and Interfaces are types that can specify and/or
fill a 'dictionary' of 'abstract' members. Function values are much like simple
interfaces where there is only one 'abstract' member (e.g. called Invoke).
Classes can also carry data and non-abstract members and can provide implementations for
abstract members. Other F# named types such as unions and records can also
provide implementations of the abstract members on the type System.Object.
Members that provide implementations
for abstract members are called 'default' or 'override' members (these are synonymous).
Classes can also fill abstract members by inheriting from other classes.
Classes also support a new notation. Most classes and interfaces in F# programming arise from
using .NET libraries.
type MyData =
class
val first : string
val second : string
new(a,b) = { first=a; second=b }
member x.First = x.first
member x.Second = x.second
end | Simple record-like class with non-abstract propery members. The 'x.First' notation
binds the variable 'x' to the object being accessed. |
let x = new MyData("a","b")
do Printf.printf "x.First = %s\n" x.First
| Creating and using a value of the above type |
type MyDataBase =
class
new() = { }
abstract GetFirst : unit -> string
abstract GetSecond : unit -> string
default x.GetSecond() = "My Default"
end | A class with abstract members and one default (note: abstract properties were not fully implemented at time of writing) |
type MyDataFilled =
class
inherit MyDataBase
new() = { inherit MyDataBase(); }
override x.GetFirst() = "Some Data"
override x.GetSecond() = "Some More Data="
end | A class inheriting abstract members and filling them |
let someData =
{ new MyDataBase()
with GetFirst() = "This"
and GetSecond() = "Better" }
do Printf.printf "someData.First = %s\n" (someData.GetFirst())
| Creating and using a value of the above type using an object expression |
Interfaces are classes with only abstract members. Classes, obejct
expressions and F# record and union types may implement interfaces. An implemented interface
acts as its own virtual slot, i.e. a class may provide a default implementation for an interface
and may override existing implementations of interfaces.
type IDataBase =
interface
abstract GetFirst : unit -> string
abstract GetSecond : unit -> string
end | An interface with two members |
type MyDataFilled =
class
new () = { }
interface IDataBase with
member x.GetFirst() = "Some Data"
member x.GetSecond() = "Some More Data="
end
end | A class inheriting abstract members and filling them |
Object expressions can be used to implement classes and interfaces, and indeed
most 'fringe' classes in other OO languages can be replaced by object expressions.
let mkObject(x) =
{ new IDataBase
with GetFirst() = if x > 3 then "All" else "None"
and GetSecond() = if x < 2 then "This" else "That" }
let x = mkObject(3)
do Printf.printf "x.First = '%s'\n" (x.GetFirst())
| An object expression, closing over a variable |
See also the information in the advanced section of the manual.
|