A Quick Tour of F#: Some Basics

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.