Quick Tour: 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.

Literal Constants are much as in other languages.  See the literals section in the language specification for full details:

3, 0xFF, 4096

Integer Literals (int32)

"Hello", "World\n", "Tab\tTab"

String Literals (with escapes)

@"c:\Program Files\My Application"

String Literals (verbatim)

3.1215, 1000.045, 1.0e6, 1.

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 value 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

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 of a type called "unit". Multiple expressions can be chained together using the sequencing ";" operator. For loops and while loops are much as in other languages.

while (!x > 3) do
  x := !x - 1;
  printf "x = %s\n" !x;
done;

A while loop

for i = 0 to Array.length(x) - 1 do
  printf "x[%d] = %d\n" i x.(i);
done;

A for loop

printf "Hello";
printf " ";
printf "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
     printf "Open Close";

A conditional statement

F# functions and values are typically defined using let and/or let rec keywords.

let x = 1 + 2

A simple value definition

let myConcat x y = System.String.Join(x,y)

A function definition whose body uses a call to a .NET library function

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 other languages, except that you can both define functions locally and return these function values as part of the overall result of the expression. Local function definitions may "capture" variables. The use of local bindings is very common in F# code.

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.exists even l) then 
    printf "there is an even number in the list\n";
  if (List.exists sameAsLength l) then 
    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, as can many other values (see the Advanced section).

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 value and expression is given a static type. Some examples of types are given in the table below (Note: syntactic types are essentially always prefixed with a colon :).

: int -> int

A function type

: 'a

A type variable, 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. For example System.Object is a type in F#, as its abbreviation obj, as is System.String and its abbreviation string. This means that a number of F# types that appear to be built-in to the F# language are actually simply equivalent to .NET types (e.g., type string = System.String, type int = System.Int32). Some of the standard predefined abbreviations for types in the .NET or F# library are 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 float = 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

Arbitrarily-sized integers

type bignum = Microsoft.FSharp.Math.BigNum

Arbitrarily-sized rationals

type obj = System.Object

Used as a uniform representation of all values, also top type for all reference types

F# is statically typed. This means that all expressions are given a type through the static typing rules of the language. F# code is also dynamically typed in the sense that certain values permit the runtime discovery of type information through type tests, and dynamic types such as obj can represent essentially any F# value. Types whose values do not permit any additional discovery of information through runtime type tests are called simple types. Types such as int and string are simple types, as indeed are all .NET value types, .NET delegate types, sealed (final) .NET reference types, F# record types and F# union types .

Some .NET types such as obj (i.e., System.Object) typically require the use of upcasts, to generate values of that type, and downcasts and type tests to recover interesting properties of values of that type. That is, the design of these type makes essential use of the fact that values of this type carry dynamic type information. For example, neither the F# type obj (equivalent to System.Object) nor the F# type exn (equivalent to System.Exception) are simple F# types. These require the use of type tests or pattern matching tests in order to recover information from values of these types.

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 from the type string list. In F# code you can write generic instantiations using the ML syntax "string list" or the C# syntax "list<string>". Type variables are written 'a, e.g., 'a list or Dictionary<'key,'a>. Types are frequently only written as annotations and in these situations partially anonymous types can be used: Dictionary<_,_>.

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 checks that types "match up" in obvious ways, i.e., that a function definition is consistent and that the definition of a function is sufficiently general to match all the ways in which it is used. Type inference is a global process over an entire source file, or, in the case of entries to F# Interactive, over the scope of a single entry delimited by ';;' terminators.

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, or hover your mouse over definitions in a supported interactive environment such as Visual Studio.

Calls and accesses to .NET and F# methods and properties (collectively known as members) involve a form of type-directed overload resolution, resolved based on left-to-right, outside-to-in analysis of a file, i.e., type information subsequent to the use of an overloaded member is not taken into account for its resolution.

In addition, some operators such as + are overloaded. Operator overloading is resolved over the same scope as type inference, typically an entire file. Where operator overloading is not resolved the overloading typically defaults to work over 32-bit integers.

Code that uses .NET library calls (and in particular libraries which make heavy use of the dot-notation) will need to be annotated with extra type annotations in order to assist the type inference process. The F# type checker will indicate when further type annotations are needed.

Function values are computations that accept arguments and return values.  They are the building blocks for most F# programming.  Function values are first class (if you like, you can think of them as simple objects) – you may apply function values, store them in data structures, pass them on to other functions such as transforming functions like List.map, "partially apply" them (i.e., apply only some arguments, leaving a residue function), and use them to construct instances of other objects such as .NET delegate values. 

(fun x -> x + 1)

A first-class function value

let f x = x + 1

Defining a value as a function

let f = (fun x -> x + 1)

An equivalent definition

let res = f 3

Calling a function

printf "res = %d" (f 3)

Printing the result of calling a function

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.

int -> int

A function type, i.e., the type of function values taking and returning integers

(int -> int) -> int

A function type for a value that accepts another function as an argument

F# code commonly uses iterated or curried function values and types where multiple arguments become successive applications of individual values to a function value, e.g., int -> int -> int.

(fun x y -> x + y * 2)

A first-class iterated function value

let f x y = x + y * 2

An equivalent definition

int -> int -> int

A curried function type, i.e., the type for a value accepting a pair of integer arguments

val map: ('a -> 'b) -> 'a list -> 'b list

The specification of the type of a generic function, e.g., in an interface file

Pattern matching is a way of performing discriminations on values and extracting information from the selected components. 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
printf "x = %s, y = %s" x y

Binding multiple values by matching against a tuple value

match ("abc","def") with
  (x,y) ->
     printf "x = %s, y = %s" x y

An equivalent way of performing the same operation

match myList with
| [] -> printf "nil";
| _ :: _ -> printf "cons";

Matching against a list value

type weekday = Monday | Tuesday | Wednesday | Thursday | Friday
let workRate (d:weekday) = 
  match d with 
  | Monday -> 0.1
  | Tuesday -> 0.8
  | Wednesday -> 0.9
  | Thursday -> 0.9
  | Friday -> 0.4

Defining and matching against an enumeration type

MLLib.Pervasives contains function and value definitions that are available by default in all F# code.  These include the basic arithmetic operators. The most important of these are shown below.

+, -, *, /, %

Overloaded arithmetic operators

&&, ||

Shortcut And/Or operators

&&&, |||, ^^^, ~~~, <<<, >>>

Overloaded bitwise operators

Pervasives also contains the definitions of the important structural equality and structural comparison operators and functions. For F# types these typically operate according to the term-structure of the values being compared.

=, <>

Structural equality operators

compare, < > <=; >=

Structural comparison functions/operators

The structural and generic nature of these default comparison and equality operators can be seen as follows:

1 = 1

true

(1,1) = (1,2)

false

(1 :: List.map (fun x -> x + 1) [2;3]) = [1;3;4]

true

(1,1) < (1,2)

true

(1,1) < (1,2,3)

Type Error

None < Some(1)

true

The following two important operators are defined in MLLib.Pervasives:

let (|>) x f = f x

Pipeline operator

let (>>) f g x = g(f x)

Function composition operator

These are very important in F# code and will be used in many samples you see. They are used to pipeline and compose functions. For example:

    let allMembers = 
      System.AppDomain.CurrentDomain.GetAssemblies()
      |> Array.to_list |> List.map (fun a -> a.GetTypes()) |> Array.concat
      |> Array.to_list |> List.map (fun ty -> ty.GetMembers()) |> Array.concat;;
  

F# type definitions are used to define record types, dicriminated unions, type abbreviations and object model types. F# types can use the type constructors defined by these type definitions. We begin with records.

type person =
 { name: string;
   dateOfBirth: System.DateTime; }

A record type definition

 open System;;
 let jethro =
   { name="Jethro";
     dateOfBirth=new DateTime(2003,12,03); }

Creating a value for the above person type

type customer =
 { name: string;
   preferences: (string * string) list; }

Another record type definition

 open System;;
 { new person 
   with name="Jethro";
   and  dateOfBirth=new DateTime(2003,12,03); }

Alternative syntax for creating a value for the person type, used when disambiguation is required betweeen records with overlapping sets of names.

The syntax matches that of object expressions.
type state =
 { mutable Items: string list;
   mutable Count: int; }

A record type definition with two mutable fields

let newState() =
  { Items =[]; Count=0}

Creating a new value (object) for the above state type

F# discriminated unions define a new type composed of a fixed number of distinct alternatives.

Discriminated unions with only one constructor are often used as a substitute for records (by using only one alternative) during prototyping due to the succinct associated syntax.

type 'a list = 
   | Cons of 'a * 'a list 
   | Nil
type 'a option = 
   | Some of 'a 
   | None

The definitions of some discriminated unions in the F# core library. Here 'a is a type variable.

type room = string
type number = int64
type date = System.DateTime
type meeting =
 | Personal of room   * date
 | Phone    of number * date

Defining a new discriminated union and two abbreviations

let review = Personal("Jasmine",new System.DateTime(2006,05,26))

Constructing a value of the above type

let dateOfMeeting (a:meeting) =
  match a with
  | Personal(_,d) -> d
  | Phone(_,d)    -> d

Pattern matching against a value of the above type

F# type abbreviations simply define a macro-like abbreviation for a type or family of types. Type error messages attempt to preserve the use of abbreviations where possible.

type name = string

A very simple type abbreviation.

type 'a callback = 'a -> unit

An abbreviation for a family of types.

The items in a single F# source code file specify a module. The default name of the module is formed by capitalizing the first character of the name of the file that was compiled. Modules can be named explicitly using a module declaration at the head of the file. Modules can themselves contain nested modules.

Other F# modules open modules and namespaces they reference, or 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

A long identifier including a 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

A module alias, for the purposes of this file.

module Stack = begin
  type 'a stack = Stack of 'a list
  let pop (Stack(a::b)) = Stack(b)
  ...
end

Define a nested module.

Signatures specify the "public" types and values in a module, i.e., the values and types that can be referenced by other modules. Signatures are stores in signature files with suffixes .mli or .fsi.  Signature files should be included on the command line when compiling the modules the constrain.

type out_channel
val input_line: out_channel -> string

A signature containing an abstract type and an operation on that type

type myType = { Name: string }

A signature specification revealing a concrete type definition

val map: ('a -> 'b) -> 'a list -> 'b list

A signature specification for a generic function value

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 is, invoked or accessed 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 two 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 (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. Many classes and interfaces in F# programming arise from using .NET libraries.

let x = new MyData("a","b")
do printf "x.First = %s\n" x.First

Creating and using a value of the above type

type Vector2D(dx:float,dy:float) = 
    member x.DX = dx
    member x.DY = dy
    member x.Length = sqrt(dx*dx+dy*dy)

An implicitly constructed object type. class/end can optionally be added around the body of the type.

type MyDataBase() = 
    abstract GetFirst : unit -> string
    abstract GetSecond : unit -> string
    default x.GetSecond() = "My Default"

An implicitly constructed object type with abstract members and one default implementation

type MyDataFilled() = 
    inherit MyDataBase()
    override x.GetFirst() = "Some Data"
    override x.GetSecond() = "Some More Data="

An implicitly constructed object type implementing two abstract members

type MyDataBase = 
  class 
    new() = { }
    abstract GetFirst : unit -> string
    abstract GetSecond : unit -> string
    default x.GetSecond() = "My Default"
  end

A fully explicit object type with abstract members and one default

let someData = 
  { new MyDataBase() with
        member self.GetFirst() = "This" 
        member self.GetSecond() = "Better" }
do printf "someData.First = %s\n" (someData.GetFirst())

Creating and using a value of the above type using an object expression

Interfaces are similar to classes but only have abstract members. Classes, object 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 = 
    abstract GetFirst : unit -> string
    abstract GetSecond : unit -> string

An interface with two members. interface/end can optionally be added around the body of the type.

type MyDataFilled() = 
   interface IDataBase with 
      member x.GetFirst() = "Some Data"
      member x.GetSecond() = "Some More Data="

A class implementing an interface

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
        member self.GetFirst() = if x > 3 then "All"  else "None"
        member self.GetSecond() = if x < 2 then "This" else "That" }
let x = mkObject(3)
do 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.