| Brian's profileInside F#BlogGuestbookNetwork | Help |
|
|
February 08 The basic syntax of F# - keywords and constructsI 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. #lightMost 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. CommentsThere are two kinds of comments in F#, seen here: // a one-line comment (* a multi-line comment (* these can nest *) *) "open" a namespaceThe "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 valuesThe '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 loopsF# 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. LiteralsThere 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 constructsF# 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)
Brian McNamara
has turned off comments on this page.
TrackbacksWeblogs that reference this entry
|
|
|