FSharp
Everything that is indented with space to the same level belongs to the same block. Lines can be be split at any point where it is clear that they are not complete.
Reference material
- http://fsharpnews.org/ - F# news
- http://www.tryfsharp.org/ - F# tutorials and online compiler
- http://fsharpforfunandprofit.com/ - F# in development of mainstream commercial business software
- http://dotnetpad.net/ - Codepad clone for .NET languages
- http://fssnip.net/ - F# source code snippets
- http://tryfs.net/ - Try F# in your web browser
- http://en.wikibooks.org/wiki/F_Sharp_Programming - F# Programming Wiki
- http://msdn.microsoft.com/en-us/library/dd233181.aspx - F# Language reference
- http://fsharp.org/specs/language-spec/ - F# Language specification
Contents
- 1 Values and variables
- 2 Types
- 3 Functions
- 4 Closure
- 5 Encapsulation of mutable state
- 6 Loops
- 7 Pattern matching
- 8 Processing data in a functional way
- 9 Function composition
- 10 Lazy evaluation
- 11 Objects
- 12 Exceptions
- 13 Garbage collection
- 14 Attributes
- 15 Comments
- 16 Hints and tips
Values and variables
- When you have significant amount of mutable state you also have code that explains how to do something.
- What you want is code that explains what to do and let the compiler figure out how.
Values
let a = 2 This is a value and it is constant within the scope of the value. You can safely pass values to other threads, computers or to the other side of the world. Always use values instead of variables when possible.
Mutable values
let mutable b = 2 This is a variable and it can be updated with a new value. b <- b + 1 now b = 3
Mutable reference cells
The ref type allows you to store mutable data on the heap to avoid two problems with mutable values, that they cannot be returned from functions except as copies and they cannot be captured in inner-functions.
let a = ref 0 // Create a reference cell named a with the value 0 a := 3 // Set the reference cell to 3 let b = !a // Set b to the value of reference cell a incr a // Increment the value of a reference cell a by 1 decr a // Decrement the value of a reference cell a by 1 let c = a // This will not create a new reference cell with the value of a, it will create an alias to the same cell let c = ref !a // This will create a new reference cell with the same value as a
Types
The compiler will usually infer types automatically. let a = 7 - Signed 32 bit integer is the default for numbers without decimal point or any suffix. let b = "Hello" - String let c = 3.1415 - 64 bit floating-point is the default for numbers with a decimal point.
In some cases it is not possible to infer the type or you want to make a point out of it. let (a : uint64) = xPos let (point : int*int) = 10,20 // Tuple
Primitive types
The fundamental primitive types that are used in the F# language
Type Suffix .NET Type Range byte uy System.Byte 0 to 255 sbyte y System.SByte −128 to 127 int16 s System.Int16 −32 768 to 32 767 uint16 us System.UInt16 0 to 65 535 int, int32 System.Int32 −231 to 231 − 1 uint32 u System.UInt32 0 to 232 − 1 int64 L System.Int64 −263 to 263 − 1 uint64 UL System.UInt64 0 to 264 − 1 float System.Double 64 bit floating-point IEEE 64, approximately 15 significant digits. Suffix is used on hex values. float32 f System.Single 32 bit floating-point IEEE 32, approximately 7 significant digits. Suffix is used on hex values. decimal M System.Decimal A fixed-precision floating-point type with precisely 28 digits of precision. bool System.Boolean True/false unit Indicates the absence of an actual value. This type has only one formal value, which is denoted (). nativeint A native pointer as a signed integer. unativeint A native pointer as an unsigned integer.
let a = 0x12345678 Hexadecimal literal constant 32 bit int let b = 0o12345s Octal literal constant 16 bit signed int let c = 0b11110000uy Binary literal constant 8 bit unsigned byte let d = 0x00000000LF Hexadecimal encoding of float 64 bit floating-point let e = true Boolean
Option
The option type has two possible values Some('a) or None where 'a can be any type. Sometimes a function does not have a valid value to return, it can then return the option type with value None. The Option type is a discriminated union.
let safeDiv x y = match y with | 0 -> None // Rules are evaluated in top down order so no other rule can match 0 after this | _ -> Some(x / y) // _ is a wildcard so this rule matches everything not matched by earlier rules match safeDiv 10 2 with | None -> printfn "Division by zero" | Some(n) -> printfn "Result: %A" n // The result of the division is captured in the value n
String
The string type can be treated as if it were a seq< char >
let a = "This is a string" let b = a.[2] // a = "i" let c = a.[3..8] // a = "s is a" let d = a.[5..] // a = "is a string"
Literal strings
let a = "Line1\nLine2" Normal string with escape characters let a = @"__/\__" Verbatim string let a = """A "B" C""" String with quotes let a = "ABCD"B Byte array let a = 'c' Character let a = "This is \ Multiline string definition that skips whitespace after \ one long \ sentence" Escape characters \n Newline char 10 \r Return char 13 \t Tab char 9 \b Backspace char 8 \\ Backslash '\' \NNN Trigraph char NNN \uNNNN Unicode char 0xNNNN \UNNNNNNNN Long unicode char 0xNNNNNNNN
bigint
Type Suffix .NET Type Range bigint I System.Numerics Arbitrary-sized integer let a = bigint 2 // Creating a value using conversion let b = 2I // Creating a value using suffix
Complex
Type Suffix .NET Type Range Complex System.Numerics float*float open System.Numerics let (c : Complex) = new Complex(1.0, 2.0)
Tuple
A tuple is an ordered collection of unnamed types. It is a simple way of returning compound types from a function.
Creating a value that the compiler will infere as type int*int let point = 100,150 The tuple can be decomposed by let x,y = point or matched against match point with | 0,0 -> "Origin" | x,_ when x < 0 -> "Left side" | 100,150 -> "Bullseye" | _ -> "Somewhere else"
Discriminated unions
The discriminated union is a type that can only be one of a set of possible values. Each possible value is called a union case. Since discriminated unions can only be one of a set of values, the compiler can do additional checks to make sure your code is correct. Remember that if you use a wildcard when matching against union cases the compiler can't warn you about incomplete pattern matches.
type Num = Positive|Negative|Zero let numList = [-1;2;0;3] let result = numList |> List.map (function | 0 -> Zero | n when n > 0 -> Positive | _ -> Negative ) result will now contain [Negative; Positive; Zero; Positive] The real advantage over using a string is that the compiler can verify that the values really exist and will reject typing mistakes like "Positvie".
You can also associate data with a union case.
type mnemonic = | Add | Sub | And | Or | Not | Ldc of int | Beq of int let program = [Ldc 10; Ldc 20; Add]
Decomposing using pattern matching
Let m = Ldc 93 let (Ldc n = m) val n : int = 93
Enumerating discriminated unions using reflection
open Microsoft.FSharp.Reflection type op = LDC of int | ADD | SUB | AND | OR let cases = FSharpType.GetUnionCases typeof<op> for case in cases do printf "%s" case.Name let fields = case.GetFields() if fields.Length > 0 then printf " of" for f in fields do printf " %s" f.PropertyType.Name printfn ""
Enumerations
An enumeration is just a wrapper over a primitive integral type, making it fast and efficient. Enumerations are defined in the same way as discriminated unions but each case must be given a constant value of the same type.
open System type Numbers = | One = 1 | Two = 2 | Three = 3 printfn "%A" (Enum.GetNames(typeof<Numbers>))
Records
Records give you a way to organize values by type and name by using fields.
- Type inference can infer the type of a record.
- Record fields are immutable by default.
- Records cannot be subclassed.
- Records can be used as part of standard pattern matching
- Records (and discriminated unions) get structural comparison and equality semantics for free.
type Person = { FirstName : string; LastName : string; Age : int } member this.FullName = this.FirstName + " " + this.LastName // How to add methods and properties let id = { FirstName = "Ivor"; LastName = "Cutler"; Age = 103 } let id2 = { id with Age = 77 } // How to make a clone of id but with a different Age
Generic types
In the F# language, there are two distinct kinds of type parameters.
- Standard generic type parameter, indicated by an apostrophe ('), as in 'T and 'U. They are equivalent to generic type parameters in .NET. Resolved at run time.
- Statically resolved and is indicated by a caret symbol, as in ^T and ^U. Statically resolved at compile time, allow you to specify that a type argument must have a particular member or members in order to be used by using constraints.
F# Collection types
Array
A fixed-size, zero-based, mutable collection of consecutive data elements that are all of the same type.
; is the delimiter between items. .. is used to specify a range of numbers. [| start of array |] end of array
Creating single-dimensional arrays Resulting array let a = [||] [||] (Empty array) let a = [|"a";"b";"c"|] [|"a";"b";"c"|] let a = [|1..6|] [|1; 2; 3; 4; 5; 6|] (Counting from 1 to 6) let a = [|1..2..6|] [|1; 3; 5|] (Counting from 1 to 6 increment 2) let a : int [] = Array.zeroCreate 3 [|0; 0; 0|] let a = Array.init 5 (fun i -> i * i) [|0; 1; 4; 9; 16|] let a = [|for i = 1 to 3 do yield i * 3|] [|3; 6; 9|]
Access the third item in the array. let b = a.[2]
Slicing arrays by index range let a = [|"a";"b";"c";"d"|] Resulting array let c = a.[1..2] [|"b";"c"|] let d = a.[..2] [|"a";"b";"c"|] let e = a.[1..] [|"b";"c";"d"|]
Creating a two-dimensional array let a:int[,] = Array2D.zeroCreate 4 4
List
An ordered, immutable series of elements of the same type. Implemented as a linked list. Lists are fast and memory efficient when items are added or removed from the start. Random access and operations at the end of the list are slow. Lists supports structural equality so can be compared by content.
Creating lists works the same as creating arrays. let a = [] let a = [1;2;3] ...
Adding one element to the front of a list using the cons operator ::. This is extremely efficient since the new element just links to the original list. If you need to repeatedly add to the end of the list you can add to the front and reverse the list after it is complete. let a = [1;2;3] let b = 0 :: a // b = [0;1;2;3] Decomposing a list into head and tail let a = [1;2;3] let h::t = a // h = 1, t = [2;3] Joining two lists using the Append operator. let a = [1;2;3] let b = [4;5;6] let c = a @ b // c = [1;2;3;4;5;6] Always use the cons operator when possible.
seq
A logical series of elements that are all of one type. Sequences are lazy, meaning that they are evaluated when they are used so a sequence can perform better than a list if not all the elements are used. A sequence does not have to exist in memory, it can be computed on the fly and can be infinite. Sequences supports referece equality and can't be directly compared by content, convert to list before adding to a hashtable or set.
Creating sequences works mostly the same as creating lists. The sequence expression can be recursive using the yield! operator. let a = seq{1..3} // a = seq [1; 2; 3] ...
Map
An immutable dictionary of elements. Elements are accessed by key. Map.add will return a new dictionary including the new key/item pair.
// Build a map of items and their frequency let histogram = Seq.fold (fun acc key -> if Map.containsKey key acc then Map.add key (acc.[key] + 1) acc else Map.add key 1 acc ) Map.empty >> Seq.sortBy (fun kvp -> -kvp.Value) // Sort sequence by frequency in reverse order "Testing shows the presence, not the absence of bugs" |> histogram |> Seq.iter (printfn "%A")
Set
An immutable set that's based on binary trees, where comparison is the F# structural comparison function.
// Build a set of unique items from the array. Partial function application (currying) is performed. // This function returns a function that consumes one parameter. let uniqueItems = Seq.fold (fun acc item -> Set.add item acc) Set.empty let a = uniqueItems "Simplicity is prerequisite for reliability."
.NET collection types
Resizeable array
The List type is a mutable array that will dynamically change size. Data access is fast since it is an array.
open System.Collections.Generic let words = new List<string>() words.AddRange [|"add";"sub";"and";"or"|] printfn "%A" words.[2]
Bit array
A compact mutable array of type bool.
open System.Collections let ba = BitArray(65536) printfn "%A" ba.[300] ba.[300] <- true printfn "%A" ba.[300]
Dictionary
The Dictionary mutable type contains key-value pairs. It is a fast way of looking up data using a key instead of an index. It is slower than an array but much faster than searching an array for a key.
open System.Collections.Generic let table = new Dictionary<string, int>() table.["kibi"] <- 1024 // Alternative syntax "table.Add ("kibi",1024)" table.["mebi"] <- 1024*1024 table.["gibi"] <- 1024*1024*1024 printfn "A traditional megabyte is %A bytes" table.["mebi"]
HashSet
The HashSet type is a mutable and efficient collection for storing an unordered set (no duplicates) of items.
open System.Collections.Generic let hs = new HashSet<int>() let addhs n = match hs.Add n with | true -> printfn "Value %A is unique" n | false -> printfn "Value %A is a duplicate" n addhs 3 addhs 7 addhs 3
Stack
The Stack type is a mutable last-in-first-out collection and is implemented as an array. When elements are added to a Stack(T), the capacity is automatically increased as required by reallocating the internal array. The capacity can be decreased by calling TrimExcess.
If Count is less than the capacity of the stack, Push is an O(1) operation. If the capacity needs to be increased to accommodate the new element, Push becomes an O(n) operation, where n is Count. Pop is an O(1) operation.
open System.Collections.Generic let s = new Stack<int>() s.Push 2 s.Push 3 s.Push (s.Pop() * s.Pop())
Queue
The Queue typ is a mutable first-in, first-out collection of objects.
open System.Collections.Generic let s = new Queue<string>() s.Enqueue "Hello" s.Enqueue "World" printfn "First one is %A" (s.Dequeue()) printfn "Next one will be %A" (s.Peek()) printfn "Here it comes %A" (s.Dequeue())
Pass by reference
let a:int[,] = Array2D.zeroCreate 4 4 let b:int[,] = Array2D.zeroCreate 4 4 for y = 0 to 3 do for x = 0 to 3 do a.[x,y] <- 1 b.[x,y] <- 2 let swap (left : 'a byref) (right : 'a byref) = let temp = left left <- right right <- temp swap &a.[1,2] &b.[2,3]
Functions
Everything is a function and returns a value. The last expression at the point of exit is returned to the caller.
This is a function called max that takes two numbers as input and returns the larger of them. let max a b = if a > b then a else b The if then else is used as a function to return a or b if a > b then a // If a is larger than b then this is the last expression that will be executed so a is returned from the function else b // If b is larger or equal to a then this is the last expression that will be executed so b is returned from the function
All functions return a value, if it makes no sense to return data the function should return a value of type unit let a = ()
If the value returned from a function is not required it can be deleted by using the ignore function. like this ignore (max 1 3)
Recursive functions
Recursive functions must be defined with the rec keyword. This recursive function counts down from n to 0 let rec count n = printfn "%A" n if n = 0 then () else count (n - 1) If the last statement in the function is the recursive call then the call can be converted to a jump by the compiler. This function does not return anything so the type unit is returned with the keyword ().
Mutually recursive functions
Two functions that depend on each other will cause the type inference to fail unless using the and keyword.
let rec exec i = match i with | Ldc num -> s.Push num | Add -> s.Push (s.Pop() + s.Pop()) | Inc -> run [Ldc 1; Add] and run p = List.iter (exec) p
Currying
If you supply fewer than the required number of arguments you create a new function that expects the remaining arguments. This is referred to as currying and is characteristic of functional programming languages.
let mul x y = x * y let mul3 = mul 3 // mul3 is now a new function that takes 1 input and multiplies it by 3 let a = mul3 2 // a is now 6
Closure
Values that are in scope where a function is defined will be captured by the closure of the function and will be accessible to the function. Mutable variables stored on the stack will not be captured, use reference cells instead.
Encapsulation of mutable state
It is considered good practice to encapsulate mutable state.
This will create a reference cell, then return a function that refers to the reference cell let count = let c = ref 0 (fun () -> c := !c + 1; !c) let a = count()
Loops
Try to avoid loops when it is simple to do so because most loops require change of state or a manipulation of count/index values that is a common source of bugs.
let arr = [|"One";"Two";"Three"|] // Array to be printed The only way to escape a while loop is using a mutable variable or an exception let mutable i = 0 // Mutable state that is undesirable while(i < 3) do // Comparison of index values that can go wrong printfn "%A" arr.[i] i <- i + 1 // Calculation of index values that can go wrong Classic for loop for t = 0 to arr.Length - 1 do // Calculation of index values that can go wrong printfn "%A" arr.[t] This type of loop is much safer but still uses the value t for t in arr do printfn "%A" t Functional equivalent that uses no mutable state, indexes or values Array.iter (printfn "%A") arr
Pattern matching
Pattern matching should be used in all cases where a simple if then else is not enough. If a match is not found a match failure exception will be raised so make sure there is a match for all possible input. Rules are checked in order from the top down.
| marks the start of a new rule to match. _ is a wildcard and matches anything. -> points to the action to be taken if there is a rule match. when specifies an additional expression that has to be true for a rule to match.
Anonymous function that pattern matches over its argument let binDigit = function | "0" -> Some 0 | "1" -> Some 1 | _ -> None
General syntax let fTest fn = match fn with | None -> sprintf "File not found" | Some(n) -> sprintf "File '%A' was found" n
Guards are additional expressions following the when keyword that has to be true for a rule to match. let testNum n = match n with | n when n = 0 -> "Number is ZERO" | n when n < 0 -> "Number is negative" | n when (n &&& (n - 1)) = 0 -> "Number is a power of two" | _ -> "Number seems unremarkable" printfn "%A" (testNum 3)
Named pattern using as let rec fib = function | 0 | 1 as n -> n | n -> fib(n - 1) + fib(n - 2)
Composable pattern matching on a single line let a = 3 a |> function 2 -> "two" | 3 -> "three" | _ -> "other"
Active patterns
Active patterns are special functions that can be matched against instead of literal constants.
- Single-case active patterns converts data from one form to another.
- Partial active pattern optionally converts data from one from to another and returns the Option type.
- Parameterized active patterns take parameters.
(| Starts the definition of an active pattern function. The name should start with an uppercase letter. |) Ends the definition of an active pattern function. _ Wildcard
Single-case active pattern definition which convert from one type to another
let (|Len|) (t:string) = t.Length match "hie" with | Len 3 -> "length of 3" | "hi" | "hello" -> "greeting" | Len n -> sprintf "other string of length %A" n
Complete active pattern definition
let (|Even|Odd|) input = if input % 2 = 0 then Even else Odd
Partial active pattern definition that tests if a number is a power of two
let (|IsPow2|_|) n = match n with | 0 -> None | n when (n &&& (n - 1)) = 0 -> Some () // Return the unit type because we have no value to return | _ -> None let testNum n = match n with | n when n = 0 -> "Number is ZERO" | n when n < 0 -> "Number is negative" | IsPow2 -> "Number is a power of two" | _ -> "Number seems unremarkable"
Processing data in a functional way
The easiest method is to run a function on every item of a collection of items to create a new collection.
let a = [1..5] // The list to process let mul2 n = n * 2 // Function that will be run on each item in the list let b = List.map mul2 a // Mapping the input list a through the function mul2 to the output list b // The operation is // 1 -> mul2 -> 2 // 2 -> mul2 -> 4 // 3 -> mul2 -> 6 // 4 -> mul2 -> 8 // 5 -> mul2 -> 10
Using the fun keyword we can create an anonymous function in place This is helpful if the function is simple and only is required once. let b = List.map (fun n -> n * 2) a
The other method is to use a recursive function.
let mul2 l = let rec loop listIn listOut = match listIn with | [] -> listOut | h::t -> loop t (h * 2 :: listOut) List.rev (loop l []) mul2 [1;2;3;4;5] // Gives [2;4;6;8;10] Using a recursive function inside a normal function makes it easy to hide implementation details but is not required. | [] matches an empty list and -> listOut returns the result. | h::t will match any list and decompose it into head and tail where head is the first item of the list and tail is the remainder. -> loop t (h * 2 :: listOut) will call loop with the tail as the new listIn and append h*2 to the front of listOut as the new listOut. loop l [] starts the recursive function with the input to mul2 as listIn and an empty list as listOut. The output list is built in the reverse order and List.rev will reverse the list just before it is returned from mul2.
C int n; // Create n and initialize it to an undefined value if a = 3 then n = 1 else n = 0; // Set n to 0 or 1 F# let n = if a = 3 then 1 else 0 // Create n and initialize it to 0 or 1
Function composition
Combining simple functions to build more complex ones has many advantages.
- Debugging and verification is simpler ebcause each function gets smaller and easier to understand.
- Reuse of code is easier because each function becomes less specific.
- Modification of code becomes simpler because it is clearer where to do the change and what effects it will have.
Pipe-forward operator
The pipe-forward operator and the pipe-backward operator, are not actually composing functions but have a similar use.
let drawbm fileName = drawBitmap(parseData(readData(fileName))) The trouble with this kind of style is that we read from left to right but the code is performed from right to left Using the pipeline operator we can pipe the data from left to right let drawbm fileName = fileName |> readData |> parseData |> drawBitmap or write it in an aligned column like this let drawbm fileName = fileName |> readData |> parseData |> drawBitmap
Pipe-backward operator
The main purpose of the <| operator is to change precedence
printfn "%A" 2 + 3 This code will not compile because function parameters are evaluated left to right the compiler will group the code as (printfn "%A" 2) (+ 3) The <| operator has a low precedence so 2 + 3 will be performed before <| and the code works correctly printfn "%A" <| 2 + 3 The problem can also be solved by printfn "%A" (2 + 3)
Forward composition operator
>> joins two functions together and applies the left one first.
The pipe example requires a value to pipe into the first function let drawbm fileName = fileName |> readData |> parseData |> drawBitmap With composition the code can be written in a very clean point-free style let drawbm = readData >> parseData >> drawBitmap
Using the pipe-forward operator can be messy when it requires a lambda function Seq.map (fun x -> x |> Seq.take i |> Seq.toList) In this case the forward composition operator is the right solution Seq.map (Seq.take i >> Seq.toList)
Backward composition operator
<< joins two functions together and applies the right one first.
Lazy evaluation
Normally code is evaluated as soon as possible and each time it is referenced. With lazy evaluation the code is only evaluated if it is needed. This can make a large difference to performance.
let a = [1..1000000] // This list will use several megabytes of memory and take time corresponding to the total number of items in the list let b = seq{1..1000000} // This lazy sequence uses almost no memory and only takes time according to how many items are actually consumed later in the program
Using the lazy keyword to make lazy values.
let a = lazy [1..1000000] Now a will not be evaluated until it is required. let b = a.Value a is now evaluated fully and the whole list will be created so it is not equivalent to using a sequence let c = a.Value This time a will not be evaluated, c will just point to the list that was created the first time a was evaluated
Objects
There are 3 main types of relationships that can be modelled with object-oriented programming.
- Is a relationships can be modelled through inheritance.
- Has a relationships are modelled through aggregation (fields and properties).
- Can do relationship which specifies that type X can do the operations described by type Y. The can do relationship is modeled via an interface, which is a contract that a type can implement to establish that it can perform a certain set of actions. An interface is just a collection of methods and properties. A type can declare that it implements the interface if it provides an implementation for each method or property.
Examples
type Counter() = let mutable count = 0 member this.Next() = count <- count + 1 count
Exceptions
- Raise exception instead of returning error codes for serious errors.
- Use the option type to indicate failures that are expected in normal running of the code.
- Where possible throw existing exceptions from the System namespaces.
- Do not throw System.Exception when it will escape to user code. This includes avoiding the use of failwith, failwithf, which are handy functions for use in scripting and for code under development, but should be removed from F# library code in favour of throwing a more specific exception type.
- Do use nullArg, invalidArg and invalidOp as the mechanism to throw ArgumentNullException, ArgumentException and InvalidOperationException when appropriate.
Garbage collection
The garbage collector will automatically free any memory that is no longer referenced. It is still possible to have a memory leak in managed code if you unintentionally keep a reference to something that is not needed.
Resources allocated outside the managed environment must manually be freed.
To manually deal with freeing of resources, use the IDisposable interface and the use keyword.
Attributes
Attributes is used to add metainformation to the code. To define new attributes, create a class that inherits from System.Attribute. You can query attributes at run time by using .NET reflection.
Some common attributes
- [<EntryPoint>]
- Set the starting point of the application, the function will have the signature string[] -> int
- string[] will contain System.Environment.GetCommandLineArgs(), minus the first entry in that array.
- [<RequiredQualifiedAccess>]
- Require the references to an object to be prefixed with the name of the namespace in which the object is defined.
- System.Windows.Forms.ListBox instead of ListBox
- [<AutoOpen>]
- Open the module when the containing namespace is opened. The [<AutoOpen>] attribute may also be applied to an assembly indicate a namespace or module that is automatically opened when the assembly is referenced.
- [<Struct>]
- [<Measure>]
- Indicates that a type or generic parameter is a unit of measure definition or annotation.
- [<Obsolete>]
- Mark a construct as deprecated so the compiler will issue a warning when it is referenced
- [<ReferenceEquality>]
- When applied to an F# record or union type, indicates that the type should use reference equality for its default equality implementation.
- [<AbstractClass>]
- [< >]
- [< >]
- [< >]
Comments
// Normal comment /// XML documentation comment that can be extracted from the source or used for tooltips (* Start of multiline/selective comment *) End of multiline/selective comment
Hints and tips
The identity function
id is the identity function and takes a parameter and returns it unchanged.
Seq.countBy id "Histograms are magic because they can also be sorted data in compressed form"
No-operation
Use () as a no-operation if there is no need to return a value. When returning a value use None.
match opcode with | ADD -> eval (+) | NOP -> ()
Apostrophe
In mathematics, the prime symbol ( ′ ) is used to generate more variable names for things which are similar, without resorting to subscripts – x′ generally means something related to or derived from x.
In F# the apostrophe ( ' ) is used in a similar way by postfixing identifiers with ( ' ).
let square x = let x' = x * x x'
Prefixing the type identifier of a parameter with an apostrophe forces the parameter to be generic.
let ident (x : 'a) = x
Random numbers
open System.Diagnostics let rnd = let rnd = System.Random() fun n -> rnd.Next(n)
Returning a function makes sure that Random is kept in the closure and not being created each time the function is called.
Parallel computing
Functions run in parallel may start and complete in any order.
Array.Parallel Parallel.For
Printing text
Format strings are checked by the compiler and is type safe. The formats include the common range of specifiers for padding and alignment used by languages such as C, as well as some other specifiers for computed widths and precisions.
printf Print formatted text to the console printfn Print formatted text + newline to the console sprintf Print formatted text to a string
Format specifiers %d, %i Integer %x, %o Integer in Hex or Octal %s String %f Floating-point %c Character %b boolean %O Object %A Anything, this is useful when you are writing a quick script or you are making changes to types. Using the exact format specifier instead of %A will help with type inference in complex programs. let n = 5 Result printfn "The number is %i " n The number is 5
A function that prints any type in a simple way. let print t = printfn "%A" t
Prints any type and passes it on so you can insert the function in a pipeline to show what passes through. let show t = print t t