FSharp

From ScienceZero
Revision as of 06:46, 11 September 2013 by Bjoern (Talk | contribs) (=Bit array)

Jump to: navigation, search

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.

Values and variables

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.
let mutable b = 2
This is a variable and it can be updated with a new value like in other laguanges
b <- b + 1
now b = 3

Types

The compiler will infer types in most cases.
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      LF  System.Double    64 bit floating-point IEEE 64, approximately 15 significant digits. Suffix is used on hex values.              
float32    lf  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 any value to return, it can then return the option type with value None.

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 spaces 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]

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]

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

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
Encapsulation of mutable state through local definitions:
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()

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"|]

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.

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 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.

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 dictionary of array items and their frequency
let histogram arr =
    Array.fold (fun acc key -> 
        if Map.containsKey key acc then 
            let count = acc.[key]
            Map.add key (count + 1) acc 
        else
            Map.add key 1 acc) Map.empty arr
    |> Map.toArray
    |> Array.sortBy (fun (_,count) -> -count)  // Sort array by count in reverse order 

let a = histogram (("A quick brown fox").ToCharArray())

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 = Array.fold (fun acc item -> Set.add item acc) Set.empty 
     
let a = uniqueItems (("Simplicity is prerequisite for reliability.").ToCharArray())

.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())

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
If it makes no sense for a function to return a value it 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 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 ().

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

// Using a while loop that require a variable to be able to exit
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

// Single line version
for t in [|"One";"Two";"Three"|] 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.
Special case as look-up table
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)

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
// Partial active pattern 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.

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

Things that are important to know

The pipeline operator

let bm 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 bm fileName = fileName |> readData |> parseData |> drawBitmap

or write it in an aligned column like this
let bm fileName = fileName
                  |> readData
                  |> parseData
                  |> drawBitmap


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 many megabytes of memory and take time to create
let b = seq{1..1000000} // This lazy sequence uses almost no memory and only takes time according to how many items are consumed

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

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
[< Struct >]
[< Measure >]
[< Obsolete >] - Mark a construct as deprecated so the compiler will issue a warning when it is referenced
[< ReferenceEquality >]
[< AbstractClass >]
[< >]
[< >]
[< >]

Mutually recursive funcitons

Two functions that depend on each other will cause the type inference to fail without 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

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