This entry is part of "The Basic Syntax of F#" series:

I have written lots of blog entries about F#, but I haven’t yet described the basic syntax of the language! So today I’ll try to remedy that, describing about a dozen keywords/syntactic-forms that you will most commonly encounter when reading F# code. This blog entry won’t cover all the syntax, but it covers perhaps the most common 75% or so. Today I will also intentionally err on the side of simplicity, at the expense of total accuracy.

#light

Some F# files start with

#light

and this just explicitly enables the ‘lightweight syntax’ option. I won’t talk about the alternative to the lightweight syntax; everyone always uses this option, and it became the default a while ago, which means you can safely remove the explicit "#light" declaration from the tops of your files.

Comments

There are two kinds of comments in F#, seen here:

// a one-line comment

(* a multi-line

comment (* these can nest *) *)

"open" a namespace

The "open" keyword is used to open a namespace or module.

// must fully qualify the name System.Console

System . Console . WriteLine ( "Hello, world!" )



// after opening the namespace, don’t need to

open System

Console . WriteLine ( "Hello, world!" )

"let" defines functions and values

The ‘let’ keyword is used to define both functions and values. Here are examples of using ‘let’ to define values:

let x = 42 // immutable, x is always 42

let mutable y = 0 // mutable, can re-assign y’s value with <- operator

let z : string = null // ": string" is type annotation to declare type

You rarely need type annotations (F# is a type-inferred language), but the last example is one case where a type annotation might be necessary. (Without the annotation, ‘z’ would be inferred to have type "obj" (System.Object); the annotation says we want ‘z’ to have type "string".)

Here are examples of using ‘let’ to define functions:

let F x = x + 1

let G x y = x + y

let G2 ( x : float ) ( y : float ) : float = x + y

let H ( x,y ) = x + y

let rec Kaboom x = Kaboom ( x+ 1 )

"F" is a one-argument function. "G" and "G2" take two curried arguments (the latter specifies type annotations for the argument types and the result types of the function, to demonstrate the syntax), whereas "H" takes tupled parameters; to find out more on tuples and currying, you definitely want to read this blog entry. The ‘let rec’ keyword lets you define a recursive function.

The lightweight syntax makes whitespace/indentation significant, and indentation is the normal way to scope function bodies (and many other constructs). Also, functions can be defined at any scope. This code exemplifies all that:

let Area diameter = // define a function

// everything indented under here is the body of "Area"

let pi = 3 .14 // define a value inside the function

let Radius d = // define another function inside here

// this is the body of the "Radius" function

d / 2 .0

let r = Radius diameter

pi * r * r



// use the function

let answer = Area 5 .0

Lambdas are "fun"

The "fun" keyword is used to define a lambda (anonymous function). The syntax is "fun args -> body", and the precedence rules usually force you to enclose the whole thing in parentheses. Here’s an example:

let nums = [ 1 ; 2 ; 3 ; 4 ; 5 ]

let odds = List . filter ( fun x -> x% 2 = 1 ) nums

printfn "odds = %A" odds // odds = [1; 3; 5]

In the example, note that ‘%’ is the modulus operator, used here to determine if a number ‘x’ is odd, and List.filter is a function that applies a predicate (a function returning a bool) to a list, and returns a new list containing only the elements for which the predicate is true.

Pipe data with |>

One built-in operator is used very commonly: pipe. "x |> f" just means "f x". Thus the previous example would be written more idiomatically as

let nums = [ 1 ; 2 ; 3 ; 4 ; 5 ]

let odds = nums | > List . filter ( fun x -> x% 2 = 1 )

printfn "odds = %A" odds // odds = [1; 3; 5]

There is no real benefit to using the pipeline operator in this tiny example, but this operator is often used to "stream" data through a series of transformative functions. See this blog entry for details.

Pattern-matching with "match"

Pattern matching is a very powerful language feature that can be used in a variety of contexts, but it is most commonly used in a "match" expression:

match expr with

| pat_1 -> body_1

. . .

| pat_n -> body_n

The expression is tested against each pattern, and the first one that matches causes the corresponding body to be evaluated. The most common patterns you’ll see involve simple algebraic data types such as discriminated unions, especially matching on a "list" or an "option"; check out the first half of this blog entry for a description of discriminated unions and how to use pattern matching on them. (I probably will eventually write at least two full blog entries on pattern matching, but the paragraph above and the linked blog entry are enough for you to ‘get by’ as you are trying to pick up the language.)

Conditionals and loops

F# has if-then-else, while loops, and for loops, though you use them less frequently than in other languages (typically you use pattern-matching and recursion instead). And since F# is a functional language, these are all expressions that return values.

The syntax for if-then-else has the general form

if cond1 then

expr1

elif cond2 then

expr2

else

expr3

The ‘elif’ and ‘else’ parts are optional, and you can have as many ‘elif’ (else if) parts as you like. All the exprn must have the same type, and this is the type of the resulting whole expression. If the ‘else’ is omitted, the exprn must have type "unit" (which is akin to "void").

The syntax for while is

while cond do

expr

and the whole while expression always returns "unit". There is nothing like "break" or "continue" in F#.

There are a variety of for-loop syntactic forms, but the most common one is

for pat in expr do

bodyexpr

Here, expr is something you can enumerate (e.g. an IEnumerable<T>, known in F# as a seq<‘a>), pat is any pattern (but most commonly just a new identifier name), and the bodyexpr runs for each element in the enumerated sequence. So for example

for x in someArray do

printfn "%d" x

prints all the integers in "someArray". Like ‘while’ the whole ‘for’ expression returns "unit".

Constructing objects with "new"

You can use ‘new’ to construct objects just as in C#:

let x = new System . Uri ( http://hello.world/" ) x =SystemUri

though in F# the ‘new’ keyword is often optional.

Literals

There are lots of literal forms in the language, but the most common are shown here:

let b : bool = true // or false

let i : int = 42

let s : string = "hi"

let x : float = 3 .14 // "3." same as "3.0"

let ai : int array = [| 1 ; 2 ; 3 |] // "int array"=="array<int>"

let lf : float list = [ 1 .1 ; 2 .2 ; 3 .3 ] // "float list"=="list<float>"

(None of the type annotations are necessary, but they aid my exposition.) Boolean, integer, and string literals work just as you expect. The "float" type corresponds to System.Double and is the type of numerical literals containing a decimal point. Array literals are written with [|these brackets|] whereas list literals are written with [these brackets]; both have elements delimited by semicolons (or newlines). These just demo the most common types; I’ll talk more about all the built-in F# types in a future blog entry.

Exception constructs

F# has constructs like "try-catch-finally" and "using" (IDisposable) from C#. The basic syntaxes are

try

expr

with

| pat_i -> body_i exprpat_ibody_i try

expr

finally

cleanup use ident = expr

You can read a little more about these in this blog entry.

What’s left?

This brief intro probably covers 95% of common syntactic forms of the language, apart from types (defining new types, classes, members, etc.; as well as describing less-common builtin types) and pervasives (builtin operators and functions), both of which I intend to cover in future blog entries.