Inside F#

Brian's thoughts on F# and .NET

The basic syntax of F# – keywords and constructs

Posted by Brian on February 8, 2009

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/&quot;)

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    

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.

About these ads

6 Responses to “The basic syntax of F# – keywords and constructs”

  1. Frank said

    Thanks! I\’d most appreciate a short overview defining new classes because I have trouble getting my head around it. Specifically with the more special cases I need to keep my Expert F# book nearby. With special cases I mean extensions, subclasses, records vs normal classes, multiple constructors and the \’with\’ keyword in type definitions. And cross join that with the optional \’class\’/\’interface\’ … \’end\’ delimiter keywords. See what I mean? ;-)

  2. Larry Smith said

    A very nice article, and useful. However, with my F# newbie hat on, I have some suggestions (and a question) that would make things even clearer. (And I hope that all my comments are correct!)* "The "open" keyword is used to open a namespace or module." This is comparable to "using" in C#. (But you haven\’t defined what a "module" is.)* In "let F x = x + 1", note the lack of parentheses. F# functions are, in general, invoked with no parentheses. Thus you\’d write "F 3", not F(3).* In "let G x y = x + y", you\’d normally write "G 2 3".* In "let H(x,y) = x + y", I\’d emphasize that H takes a *single* parameter, which is a tuple. The parens are necessary (they define a tuple). However, if you\’re invoking a .NET Framework method, you need to parenthesize your parameters, since technically F# calls external routines with a single parameter, which is a tuple. (Am I right here?)* In "let rec Kaboom x = Kaboom (x+1)", you need the parentheses the create the expression "x+1". Else F# would have taken this as "(Kaboom x) + 1". But one question I don\’t know the answer to: Is "(x+1)" technically a one-tuple, as opposed to a parenthesized expression? Or is there no difference? Which doesn\’t quite make sense, based upon my comment in the previous point.* The "let Area diameter = …" function. All functions *must* return values (even if it\’s the empty tuple "()" otherwise known in F# as "unit" (similar to "void" in C/C++/C#/etc). However, you normally return a value by stating it in the last line of the function. So in the Radius function, we write "d / 2.0", rather than, say, "return d / 2.0", as we might in other languages. Similarly, the Area function itself returns the value "pi * r * r".* "let nums = [1; 2; 3; 4; 5]". This defines a (linked) list with 5 elements.* "printfn "odds = %A" odds". The printfn function works similarly to the printf statement in C. The "%A" placeholder is a universal "format Anything as best you can". Also, the printfn function puts a newline (\\n) at the end of the output; that\’s the trailing "n" in "printfn". The "printf" function doesn\’t append a newline. There\’s also the sprintf/sprintfn functions for formatting data into a string.* "match expr with …". This is similar to if/then/elseif/… statements (also "switch" statements), but has additional features.* "while cond do … the whole while expression always returns "unit". You haven\’t defined "unit" above.* "for x in someArray do printfn "%d" x". The "%d" placeholder displays an int ("%d" as in "%digits"). So other than these comments, again a very nice article, and appreciated. Keep \’em coming!

  3. Wilfried said

    Nice article. And Larry, that are good comments. They address precisely the points the F# gurus tend to forget to mention.F# is so different than the more common languages that a comprehensive explanation of the basics is really needed. It is not just like the "curly bracket" languages where you just start typing right away.

  4. Ravi said

    Thanks Brian! It was a very nice recap of the things I learnt last week..

  5. Laurent said

    The "function" keyword is missing in this article. Since I use it very often, I think it should be added. "function" is the same as "fun x -> match x with". Example:let rec fact = function 0 -> 1 | n -> n * fact (n – 1)

  6. mix said

    Thanks, guys, for this article and comments to it. It helped me a lot!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
Follow

Get every new post delivered to your Inbox.

Join 30 other followers

%d bloggers like this: