layout: post
title: “F# syntax in 60 seconds”
description: “A very quick overview on how to read F# code”
nav: why-use-fsharp
seriesId: “Why use F#?”

seriesOrder: 2

Here is a very quick overview on how to read F# code for newcomers unfamiliar with the syntax.

It is obviously not very detailed but should be enough so that you can read and get the gist of the upcoming examples in this series. Don’t worry if you don’t understand all of it, as I will give more detailed explanations when we get to the actual code examples.

The two major differences between F# syntax and a standard C-like syntax are:

  • Curly braces are not used to delimit blocks of code. Instead, indentation is used (Python is similar this way).
  • Whitespace is used to separate parameters rather than commas.

Some people find the F# syntax off-putting. If you are one of them, consider this quote:

“Optimising your notation to not confuse people in the first 10 minutes of seeing it but to hinder readability ever after is a really bad mistake.”
(David MacIver, via a post about Scala syntax).

Personally, I think that the F# syntax is very clear and straightforward when you get used to it. In many ways, it is simpler than the C# syntax, with fewer keywords and special cases.

The example code below is a simple F# script that demonstrates most of the concepts that you need on a regular basis.

I would encourage you to test this code interactively and play with it a bit! Either:

  • Type this into a F# script file (with .fsx extension)
    and send it to the interactive window. See the “installing and using F#” page for details.
  • Alternatively, try running this code in the interactive window. Remember to always use ;; at the end to tell
    the interpreter that you are done entering and ready to evaluate.
  1. // single line comments use a double slash
  2. (* multi line comments use (* . . . *) pair
  3. -end of multi line comment- *)
  4. // ======== "Variables" (but not really) ==========
  5. // The "let" keyword defines an (immutable) value
  6. let myInt = 5
  7. let myFloat = 3.14
  8. let myString = "hello" //note that no types needed
  9. // ======== Lists ============
  10. let twoToFive = [2;3;4;5] // Square brackets create a list with
  11. // semicolon delimiters.
  12. let oneToFive = 1 :: twoToFive // :: creates list with new 1st element
  13. // The result is [1;2;3;4;5]
  14. let zeroToFive = [0;1] @ twoToFive // @ concats two lists
  15. // IMPORTANT: commas are never used as delimiters, only semicolons!
  16. // ======== Functions ========
  17. // The "let" keyword also defines a named function.
  18. let square x = x * x // Note that no parens are used.
  19. square 3 // Now run the function. Again, no parens.
  20. let add x y = x + y // don't use add (x,y)! It means something
  21. // completely different.
  22. add 2 3 // Now run the function.
  23. // to define a multiline function, just use indents. No semicolons needed.
  24. let evens list =
  25. let isEven x = x%2 = 0 // Define "isEven" as an inner ("nested") function
  26. List.filter isEven list // List.filter is a library function
  27. // with two parameters: a boolean function
  28. // and a list to work on
  29. evens oneToFive // Now run the function
  30. // You can use parens to clarify precedence. In this example,
  31. // do "map" first, with two args, then do "sum" on the result.
  32. // Without the parens, "List.map" would be passed as an arg to List.sum
  33. let sumOfSquaresTo100 =
  34. List.sum ( List.map square [1..100] )
  35. // You can pipe the output of one operation to the next using "|>"
  36. // Here is the same sumOfSquares function written using pipes
  37. let sumOfSquaresTo100piped =
  38. [1..100] |> List.map square |> List.sum // "square" was defined earlier
  39. // you can define lambdas (anonymous functions) using the "fun" keyword
  40. let sumOfSquaresTo100withFun =
  41. [1..100] |> List.map (fun x->x*x) |> List.sum
  42. // In F# returns are implicit -- no "return" needed. A function always
  43. // returns the value of the last expression used.
  44. // ======== Pattern Matching ========
  45. // Match..with.. is a supercharged case/switch statement.
  46. let simplePatternMatch =
  47. let x = "a"
  48. match x with
  49. | "a" -> printfn "x is a"
  50. | "b" -> printfn "x is b"
  51. | _ -> printfn "x is something else" // underscore matches anything
  52. // Some(..) and None are roughly analogous to Nullable wrappers
  53. let validValue = Some(99)
  54. let invalidValue = None
  55. // In this example, match..with matches the "Some" and the "None",
  56. // and also unpacks the value in the "Some" at the same time.
  57. let optionPatternMatch input =
  58. match input with
  59. | Some i -> printfn "input is an int=%d" i
  60. | None -> printfn "input is missing"
  61. optionPatternMatch validValue
  62. optionPatternMatch invalidValue
  63. // ========= Complex Data Types =========
  64. // Tuple types are pairs, triples, etc. Tuples use commas.
  65. let twoTuple = 1,2
  66. let threeTuple = "a",2,true
  67. // Record types have named fields. Semicolons are separators.
  68. type Person = {First:string; Last:string}
  69. let person1 = {First="john"; Last="Doe"}
  70. // Union types have choices. Vertical bars are separators.
  71. type Temp =
  72. | DegreesC of float
  73. | DegreesF of float
  74. let temp = DegreesF 98.6
  75. // Types can be combined recursively in complex ways.
  76. // E.g. here is a union type that contains a list of the same type:
  77. type Employee =
  78. | Worker of Person
  79. | Manager of Employee list
  80. let jdoe = {First="John";Last="Doe"}
  81. let worker = Worker jdoe
  82. // ========= Printing =========
  83. // The printf/printfn functions are similar to the
  84. // Console.Write/WriteLine functions in C#.
  85. printfn "Printing an int %i, a float %f, a bool %b" 1 2.0 true
  86. printfn "A string %s, and something generic %A" "hello" [1;2;3;4]
  87. // all complex types have pretty printing built in
  88. printfn "twoTuple=%A,\nPerson=%A,\nTemp=%A,\nEmployee=%A"
  89. twoTuple person1 temp worker
  90. // There are also sprintf/sprintfn functions for formatting data
  91. // into a string, similar to String.Format.

And with that, let’s start by comparing some simple F# code with the equivalent C# code.