Brian's profileInside F#BlogGuestbookNetwork Tools Help

Blog


    February 08

    The basic syntax of F# - keywords and constructs

    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

    Most F# files start with

    #light

    and this just enables the 'lightweight syntax' option.  I won't talk about the alternative to the lightweight syntax; everyone always uses this option, and it will be the default in the next release of F#.  For now, just ensure this is always at the top of the file.

    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/")

    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.

    Comments (6)

    Please wait...
    Sorry, the comment you entered is too long. Please shorten it.
    You didn't enter anything. Please try again.
    Sorry, we can't add your comment right now. Please try again later.
    To add a comment, you need permission from your parent. Ask for permission
    Your parent has turned off comments.
    Sorry, we can't delete your comment right now. Please try again later.
    You've exceeded the maximum number of comments that can be left in one day. Please try again in 24 hours.
    Your account has had the ability to leave comments disabled because our systems indicate that you may be spamming other users. If you believe that your account has been disabled in error please contact Windows Live support.
    Complete the security check below to finish leaving your comment.
    The characters you type in the security check must match the characters in the picture or audio.
    Brian McNamara has turned off comments on this page.
    mixwrote:
    Thanks, guys, for this article and comments to it. It helped me a lot!
    15 July
    Laurentwrote:
    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)
    12 Feb.
    Ravi Kumarwrote:
    Thanks Brian! It was a very nice recap of the things I learnt last week..
    10 Feb.
    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.
    10 Feb.
    Larry Smithwrote:
    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!
    9 Feb.
    Frankwrote:
    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? ;-)
    9 Feb.

    Trackbacks

    Weblogs that reference this entry
    • None