- Background
F# is a new functional language from Microsoft based on OCaml.
- A few interesting points about the language:
- F# is multi-paradigm, meaning it can be used in a functional, imperative, or object oriented manner.
- It is statically typed giving some protection against mixing object types
- F# runs on the CLR and can access all the wonderful libraries in .Net and your custom-built .Net objects.
- You can write F# modules accessible to your .Net applications.
- F# shines in compute intensive applications where multi-processors and multi-threading can speed results.
- Type inference makes your code smaller and more flexible.
You can download F# from here although its installed automatically with Visual Studio 2010.
- Getting Started
- Our first program.
Open your favorite text editor and enter this into a file named "HelloWorld.fs".
printfn "Hello World"
Since I like to compile the first program by hand, let's compile it in a console window, then execute it.
C:\home\mfincher\fsharp>"C:\Program Files\Microsoft F#\v4.0\fsc.exe" HelloWorld.fs Microsoft (R) F# 2.0 Compiler build 4.0.30319.1 Copyright (c) Microsoft Corporation. All Rights Reserved. C:\home\mfincher\fsharp>HelloWorld.exe Hello World
You've just written your first F# program. OK, no big deal really.
- F# program in Visual Studio
In VS select "File/New Project/Visual F#/F# Application" and enter this program:
open System [<EntryPoint>] let main (args : string[]) = if args. Length <> 3 then failwith "Usage: HiThere.exe firstName lastName city" let firstName, lastName, city = args.[0], args.[1], args.[2] printfn "Hi There %s %s from %s" firstName lastName city 0
Now set the parameters be selecting "Project/<YourApplicationName> Properties/Debug/Start Options Command line parameters:" and enter "Percy Jackson NewYork"
Enter "Ctl-F5" to run your application.
Hi There Percy Jackson from NewYork Press any key to continue . . .
The "0" at the end is the status value returned to the operating system.
- Let
"let" binds a symbol to an immutable (cannot be changed) value.
In languages like java and c# when you assign a primitive type like a double to a value, you are copying a value into the "box" that contains the value of the variable.
Consider the C# code below:
double total; total = 100.0; total = 200.0;
"double total" allocates 8 bytes of memory to hold a value and sets the variable "total" to point to that chunk of memory. "total = 100.0" copies the constant "100.0" into that bit of memory. "total = 200.0" overwrites the memory with a new value, "200.0". "100.0" is now gone. "total" still points to the same bit of memory, but the contents of that memory have changed.
In contrast, let's look at this F# code:
> let total = 100.0;; > let total = 200.0;;
It looks similar, but behind the scenes life is different in Functional Programming Land. The first line allocates a bit of memory and copies "100.0" into that memory. Then it sets the memory associated with the symbol "total" to point to that memory containing "100.0".
When the second line is executed, F# allocates a new bit of memory and copies "200.0" into it and then makes "total" point to the new memory location. "100.0" still exists in memory, but "total" now points to the memory with "200.0" in it.
Since a value is not overwritten, it makes parallel programming easier since you can't overwrite a value another thread is depending on.
Psst, by the way, you can assign multiple values with one "let"
> let dog, cat = "bark", "meow";; val dog : string = "bark" val cat : string = "meow"
- Mutable assignments
Although in functional programming we generally want immutable objects, F# allows mutable values,
> let mutable total = "100.0";; val mutable total : string = "100.0" > total <- "200.0";; //destructive assignment operator val it : unit = ()
The contents of the memory that "total" points to is now overwritten using the "destructive assignment operator", "<-".
- Variable Names
F# variable names are composed of letters, numbers, underbar "_", and apostrophe "'", but starts with a letter or underscore. Variable and function names are case sensitive.
- Whitespace
In F# whitespace matters. Instead of using curly braces blocks of code are indented. To avoid fights about tabs being 4 or 5 spaces, tabs are banished from the language.
The block of code under a key work like "if" must start to the right of "if" so F# knows where the block starts.
- Comments
F# has three types of comments:
(* I am a multi-line comment *) //I am a single line comment ///I am a documentation comment
- F# Interactive (FSI) Window
To play with snippets of code you can enter the F# Interactive Window inside VS2010 by entering "Ctl-Alt-F" (or if you have resharper installed taking that key combination, you can use "View/Other Windows/F# Interactive" until you remap it). Enter code then ";;" followed by a blank line.
Microsoft (R) F# 2.0 Interactive build 4.0.30319.1 Copyright (c) Microsoft Corporation. All Rights Reserved. For help type #help;; > let a = "asdf";; val a : string = "asdf" >
To remove the clutter from a session, right-click in the window and select, "Reset Session".
This type of interaction is called a read-eval-print loop (REPL.) popularized in LISP.
You can also use the command line version of fsi by invoking it directly in a console. My fsi.exe is at "C:\Program Files\Microsoft F#\v4.0\fsi.exe".
Use the "#help;;" directive to list other directives inside fsi.
> #help;; F# Interactive directives: #r "file.dll";; Reference (dynamically load) the given DLL #I "path";; Add the given search path for referenced DLLs #load "file.fs" ...;; Load the given file(s) as if compiled and referenced #time ["on"|"off"];; Toggle timing on/off #help;; Display help #quit;; Exit F# Interactive command line options: See 'fsi --help' for options
- Accessing the .Net libraries
One of the great things about F# is the ability to access the ginormous library of .Net objects. Use the "open" keyword to access elements in a library without having to use a fully qualified name. Inside the FSI:
> open System let ran = new Random() let x = ran.Next();; val ran : Random val x : int = 1762269880
Another example of accessing .Net libraries, but using a fully qualified name and not the "open" keyword.
printfn "dir=%s" System.Environment.CurrentDirectory
If you "open" two namespaces that have identically named elements F# picks the last one opened as the winner.
- Our first program.
- Types
- F# supports all our old friends in the .Net framework,
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 -2^31 to 2^31-1 uint, uint32 u System.UInt32 0 to 2^31-1 int64 L System.Int64 -2^63 to 2^63-1 uint64 UL System.UInt64 0 to 2^64-1 native int n System.IntPtr system dependent unsigned native int un System.IntPtr system dependent float or double (optional) e System.Double 15 significant digits float32 f System.Float 7 significant digits decimal M System.Decimal 28 digits of precision bignum I System.FSharp.Math.BigInt arbitrary precision - Specifying type
You can specify a type for a constant by adding its suffix to a number
> let NationalDebt = 14000000000000M;; val NationalDebt : decimal = 14000000000000M > let DebtPerPerson = 44080.57f;; val DebtPerPerson : float32 = 44080.5703f
You can also specify the type of a value explicitly
> let x = 100.0 let y:double = 200.0;; val x : float = 100.0 val y : double = 200.0
- Quiet overflow
When a numeric type gets to big, it quietly overflows and rolls over like an odometer (well at least like the old analog odometers). Below we see an unsigned byte (whose largest value is 255) get overflowed. To get around this use bigger types or use checked arithmetic.
> let IAmAByte = 250uy;; val IAmAByte : byte = 250uy > let IAmTooBig = IAmAByte + 30uy;; val IAmTooBig : byte = 24uy
- Specifying numbers in other bases
F# uses the common syntax of prefacing hex numbers with "0x", Octal with "0o" (zero followed by "o"), and binary with "0b" or "0B".
- BigInt
For really, really big integers use BigInt. To calculate the number of protons in the universe, the Eddington Number, just put "I" at the end of the number.
> let Eddington = 1000000000000000000000000000000000000000000000000000000000000000000000000000000000I ;; val Eddington : Numerics.BigInteger = 1000000000000000000000000000000000000000000000000000000000000000000000000000000000
- Math operators
F# uses the standard math operators, "+","-","*","/","%" modulus,"**" power (e.g., 2.0 ** 3.0, only for floating point.)
- Math functions
The common math functions are supported: abs, ceil, exp, floor, log, sqrt, cos, sin, tan, pown (power for an integer).
- Boolean operators
The usual suspects: "&&" and, "||" or, "not".
- Strings
All strings are UTF-16 double-byte characters. Strings can be defined across lines, if the line ends with a backslash. On the second line, all leading whitespace is removed. To access individual characters, use the ".[n]" syntax.
> let vowels = "ae\ iou\ y";; val vowels : string = "aeiouy" > vowels.[2] ;; val it : char = 'i'
You can embed end-of-line characters directly in the editor
> let haiku = "Ceaselessly we code, yet requirements fall like a gentle rain";;
Use the "@" sign to denote verbatim strings, string you don't want to include escaped characters.
> let cdir = @"C:\home\mfincher";; val cdir : string = "C:\home\mfincher"
You can access .Net's StringBuilder class for efficient use of appending text.
> let henry = new System.Text.StringBuilder();; val henry : System.Text.StringBuilder = > henry.Append("And gentlemen in England now-a-bed") henry.Append("Shall think themselves accurs'd they were not here,") henry.Append("And hold their manhoods cheap whiles any speaks") henry.Append("That fought with us upon Saint Crispin's day.") ;;
- Type Conversions
F# does not do implicit type conversions for you. You, as Master of Your Fate and Captain of Your Destiny, must explicitly tell F# when you want to convert types. This hopefully leads to fewer errors. Fortunately F# provides an easy way to do that. Just preface whatever you want to convert with the desired type and viola, it's done.
> let abyte = 200uy + 10;; > let abyte = 200uy + 10;; //F# refuses to do the tiny conversion of "10" to a byte. --------------------^^ stdin(67,21): error FS0001: The type 'int' does not match the type 'byte' let abyte = 200uy + byte 10;; val abyte : byte = 210uy > let pi = float "3.14159";; //convert a string to a float val pi : float = 3.14159 >
- Statically Typed
F# is a "statically typed" language, meaning that at compile time it knows all the types of the variables. F# uses "type inference" to determine the type of a variable. It looks at how a variable is used and then determines the type.
- A final note about nothing
Java and C# have the concept of "null", meaning "no object". This concept is called "unit" in F#. and written as "()". For example:
> let x = ();; val x : unit = ()
"void" exists in F# as well, but only for interaction with the .Net libraries.
- Units of Measure
One of the great things about F# it that it allows you to annotate values with Units of Measure. (Remember the Mars Climate Orbiter burning up because all subcontractors used metric except Lockheed Martin?) F# will do limited testing to make sure your units are consistent. Units of Measure doesn't really understand the concepts of length, volume, force, or currency, but used wisely can be a great help. A little example:
[<Measure>] type cm [<Measure>] type inch val cmPerInch : float<cm/inch> = 2.54 > let length1 = 1.0<cm> let length2 = 1.0<inch>;; val length1 : float<cm> = 1.0 val length2 : float<inch> = 1.0 > let length3 = length1 + length2;; let length3 = length1 + length2;; ------------------------^^^^^^^ stdin(41,25): error FS0001: The unit of measure 'inch' does not match the unit of measure 'cm' > let length3 = length1 + length2 * cmPerInch;; val length3 : float<cm> = 3.54
- F# supports all our old friends in the .Net framework,
- Functions
- Our first function
Let's define a function to square a number
> let squareMe x = x * x;; val squareMe : int -> int > squareMe 4;; val it : int = 16
Note that F# does not have a "return" key word; the last expression evaluated is returned.
- Recursive functions
When defining a recursive function we use the keyword "rec". ( You knew we'd have to use fibonacci at least once - we'll skip Towers of Hanoi)
let rec fib x = if x <=1 then x else fib (x-1) + fib (x-2) printfn "fibonacci(%d)=%d" 10 (fib 10) //55
- Two parameters
> let addTwo a b = a + b ;; val addTwo : int -> int -> int > addTwo 1 3;; val it : int = 4
- Argument Types
In the above example, F# defaults to assuming that the argument types are ints. If we now invoke the function with floats, well, F# doesn't like that:
> addTwo 1.0 3.0;; addTwo 1.0 3.0 -------^^^ stdin(94,8): error FS0001: This expression was expected to have type int but here has type float
- Type inference of function
In a fascinating case of inference, if we invoke a function using a particular type before entering the ";;" F# will look at the function and then determine the types of the function. I think this is cool.
> let subtract a b = a - b //would default to ints unless we give it a hint subtract 2.0 1.0 ;; val subtract : float -> float -> float
- Type annotation
We can tell F# implicitly what the type of the parameters are.
> let multiply (x: float) (y: float) = x * y ;; val multiply : float -> float -> float
Without these annotations F# would think the function took ints.
- Functions within functions
You can define functions inside other functions. These inner functions are not visible outside the containing function. (Raise your hand if you understand this.)
> let cubeMe x = let squareMe x = x * x squareMe x * x;; val cubeMe : int -> int > cubeMe 3;; val it : int = 27 > squareMe 2;; //since squareMe is defined inside cubeMe, we can't see it squareMe 2 ^^^^^^^^ stdin(6,1): error FS0039: The value or constructor 'squareMe' is not defined >
The same is true of variables defined inside functions. They are not visible to the outside world.
- Our first function
- Statements vs. Expressions
A "statement" does something, has no return value, and cannot be passed into a function as an argument. An "expression" is a block of code that evaluates to a value. F# has no statements.
- Control Flow
- the "if" expression
"if" sorta works like you would expect
> let evenOrOdd x = if x % 2 = 0 then printfn "even" else printfn "odd";; val evenOrOdd : int -> unit > evenOrOdd 3;; odd val it : unit = () > evenOrOdd 4;; even val it : unit = () >
- "if" returns a value
Oddly enough, (no pun intended), "if" expressions return a value.
> let evenOrOdd x = let result = if x % 2 = 0 then "even" else "odd" printfn "%s" result;; val evenOrOdd : int -> unit > evenOrOdd 5;; odd val it : unit = () >
- elif
"elif" is a shortcut for "else if"
> let getPrice size = let price = if size = "small" then 1.0f elif size = "medium" then 1.5f else 1.75f printfn "price=%f" price;; val getPrice : string -> unit > getPrice "medium";; price=1.500000 val it : unit = () >
- "if"'s dark secret - it's a communist
All paths from an "if" expression must have the same type - no commoners and elites - everyone must be equal. This stands to reason. The dirty secret about "if" is that if there is no "else" at the end of the "if", F# generates an implied "else" which has the type of ... you guessed it ... "unit". Let's see it choke on an "if" without an "else" that returns something other than "unit"
> let getPrice size = let price = if size = "small" then 1.0f elif size = "medium" then 1.5f printfn "price=%f" price;; let price = -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ stdin(200,6): error FS0001: This expression was expected to have type float32 but here has type unit >
The error above is F#'s elliptical way of telling us the implied else is returning "unit" and the rest of the "if" is returning "float32".
Generally, unless the return type of the "if" statement is "unit", you always need an "else".
- Looping
F# has "for" and "while" looping constructs, although recursive looping is the preferred iteration method.
module Loops open System [<EntryPoint>] let main (args : string[]) = for i = 1 to 10 do //prints 1..10 printfn "i=%d" i for j = 10 downto 5 do //prints 10, 9, 8, 7, 6, 5 printfn "j=%d" j let mutable x = 0 while x < 10 do //prints 1..9 printfn "x=%d" x x <- x + 1 0
- the "if" expression
- Tuples
Tuples are containers for two or more unnamed values of possibly different types.
Here we see a tuple, pronounced "two-pull" containing four values.
> (1,2,3.0,"four");; val it : int * int * float * string = (1, 2, 3.0, "four")
F# shows the type of the tuple by seperating the individual types with "*", this has nothing to do with multiplication.
- What can be in a tuple?
Tuples can contain simple values, expressions, and even snippets of code. Tuples are used to pass parameters into a funtion as a group and return multiple values from a function.
> (1, 1+2,("a","b","c"), printfn "Hello");; Hello val it : int * int * (string * string * string) * unit = (1, 3, ("a", "b", "c"), null)
- fst, and snd
If you have a two-element tuple you can retrieve the first value with "fst" and the second with "snd".
> let n = (1,2);; val n : int * int = (1, 2) > fst n;; val it : int = 1 > snd n;; val it : int = 2
- The parenthesis are optional.
> let n = 1,2;; val n : int * int = (1, 2)
- Arrays
Arrays are mutable, fixed-sized and zero-based sequences of the same data type.
- Simple Arrays
Simple arrays are denoted by "[|", followed by the elements separated by ";" or line-feeds, ending with "|]
> let AOlympians = [| "Aphrodite"; "Apollo"; "Ares"; "Artemis"; "Athena" |];; val AOlympians : string [] = [|"Aphrodite"; "Apollo"; "Ares"; "Artemis"; "Athena"|]
You can access the elements by using the ".[n]" notation
> let godOfWar = AOlympians.[2];; val godOfWar : string = "Ares"
You can use ranges like in Ruby
let OneToTen = [|1..10|];; val OneToTen : int [] = [|1; 2; 3; 4; 5; 6; 7; 8; 9; 10|] > let OneToTenByTwos = [|1..2..10|];; val OneToTenByTwos : int [] = [|1; 3; 5; 7; 9|] > let FiveToOne = [|5..-1..1|];; //count down val FiveToOne : int [] = [|5; 4; 3; 2; 1|]
- Array.zeroCreate
this creates an array of the given size and fills it with zero for numeric types and nulls for all others.
If you just create the array without giving it any hints about the type, you get an error:
In the FSI you must assign a member before entering ";;" so it knows what type it will be.> let letter = Array.zeroCreate 26;; let letter = Array.zeroCreate 26;; ----^^^^^^ stdin(51,5): error FS0030: Value restriction. The value 'letter' has been inferred to have generic type val letter : '_a [] Either define 'letter' as a simple data term, make it a function with explicit parameters or, if you do not intend for it to be generic, add a type annotation.
let letters = Array.zeroCreate 26 letters.[0] <- 'a';; //you must use the destructive assignment operator since arrays are mutable val letters : char [] = [|'a'; '\000'; '\000'; '\000'; '\000'; '\000'; '\000'; '\000'; '\000'; '\000'; '\000'; '\000'; '\000'; '\000'; '\000'; '\000'; '\000'; '\000'; '\000'; '\000'; '\000'; '\000'; '\000'; '\000'; '\000'; '\000'|] > letters.[0] = 'a';; //if you use "=" outside a 'let' assignment F# thinks you're asking an equality question val it : bool = false
- A few examples of creating arrays:
module arrays [<EntryPoint>] let main (args : string[]) = //Array.create numberOfElements defaultValue let sixOnes = Array.create 6 1 printfn "%A" sixOnes let tenToThirtyByFives = [|10.0; 15.0; 20.0; 25.0; 30.0|] printfn "%A" tenToThirtyByFives let tenToThirtyByFives = [| 10.0 .. 5.0 .. 30.0 |] printfn "%A" tenToThirtyByFives let tenToThirtyByFives = Array.init 5 (fun x -> float(x) * 5.0+ 10.0) printfn "%A" tenToThirtyByFives 0
You can specify the type of the array by any of the following:
> let states1 : string[] = Array.zeroCreate 50;; > let states2 : string array = Array.zeroCreate 50;; > let states3 = Array.zeroCreate<string> 50;;
- Slices
You can create new arrays from slices of an exisitng array
> let nums = [|0..9|];; val nums : int [] = [|0; 1; 2; 3; 4; 5; 6; 7; 8; 9|] > let OneToThree = nums.[1..3];; // [|1; 2; 3|] > let ZeroToFive = nums.[..5];; // [|0; 1; 2; 3; 4; 5|] > let TwoToNine = nums.[2..];; // [|2; 3; 4; 5; 6; 7; 8; 9|] > let duplicate = nums.[0..];; // [|0; 1; 2; 3; 4; 5; 6; 7; 8; 9|]
-
> let matrix = Array2D.zeroCreate<float> 3 3;; val matrix : float [,] = [[0.0; 0.0; 0.0] [0.0; 0.0; 0.0] [0.0; 0.0; 0.0]] > matrix.[1,2] = 2.5;; //don't do this val it : bool = false > matrix.[1,2] <- 2.5;; //use the destructive assignment operator instead val it : unit = ()
Array3D and Array4D are available for your pleasure.
- Simple Arrays
- Deeper into Functions
- Everything is a function
Everything in F# evaluates to a value of a specific type. Even a simple value can be thought of as an expression that takes no parameters and evaluates to it's value.
let temperature = 98.6;; //is a function
- Partial Application of Functions - things start to get weird
Up til now everything we've learned has been pretty straight forward, a little odd at times, but not too radical, but now Alice is about to enter the rabbit hole.
> let addTwo a b = a + b;; val addTwo : int -> int -> int > (addTwo 3) 4;; val it : int = 7 > let plus3 = addTwo 3; //partial application of a function val plus3 : (int -> int) > plus3 4;; val it : int = 7
Note that "plus3" is a function that "swallows" the "addTwo" function and its first argument, and "plus3" takes an argument that is really the second argument of the "addTwo" function.
- Higher Order Functions
In functional programming functions are real objects. Higher-order functions take a function as an argument or return a function as an argument. Let's look at an example of "doTwice" a method that takes an int and a function as arguments
module HigerOrderFunctions [<EntryPoint>] let main (args : string[]) = //two tiny functions to play with later let addOne n = n + 1 let divideByTwo n = n / 2 // doTwice takes an integer and then // a function that takes an int and returns an int // doTwice applies this function to the argument // and then to the result of that function let doTwice n (f:int->int) = f(f(n)) let x = doTwice 10 addOne printfn "x=%d" x //prints "x=12" let y = doTwice 12 divideByTwo printfn "y=%d" y //prints "y=3" 0
- Lambda functions
Lambdas are syntactic sugar allowing us to define and use unnamed functions. To create a lambda use the keyword "fun" followed by arguments, then "->", then the body of the function. The following is identical to the code above except we get rid of "doubleMe" and replace with a lambda function.
module lambda [<EntryPoint>] let main (args : string[]) = let doTwice n (f:int->int) = f(f(n)) let x = doTwice 10 (fun n -> n * 2) //lambda function as second argument printfn "x=%d" x //prints "x=40" 0
- Everything is a function
- Mapping
One of the basic concepts of functional programming in the concept of a map. A map takes one list, applies a function to each of the original list elements and creates a new list. In C# this is called "Select" and in Lisp it is "mapcar".
F# has a built-in method on Array called "map". "map" takes two arguments: a function to apply to elements and an array of elements to operate on. The original list is untouched.
For example, below we take an array of the numbers 1 through 10, and apply a function to each number to create a second Array.
In addition to "map", Array has three other similiar methods:module Mapcar [<EntryPoint>] let main (args : string[]) = let oneThruTen = [| 1 .. 10 |] let powersOfTwo = Array.map(fun n -> pown 2 n ) oneThruTen printfn "%A" powersOfTwo //[|2; 4; 8; 16; 32; 64; 128; 256; 512; 1024|] 0
- map2 - this takes two input arrays and creates an output array
- mapi - takes one input array and passes the element and its order number to the mapping function to create the output array
- mapi2 - takes two input arrays and passes the two elements and their order number to the mapping function to create the output array
- Folding
While mapping takes a collection and creates another collection, folding takes a collection and distills it down to a single object. Array.fold takes three arguments: a function, an initial value for the accumulator, and an Array.
module Folder [<EntryPoint>] let main (args : string[]) = let oneThruTen = Array.init 10 (fun n -> n+1) let sum = Array.fold(fun accumulator n -> accumulator + n ) 0 oneThruTen printfn "%d" sum //55 0
- Filtering
A filter selectively populates a new collection with some of the elements of an initial collection. Array.filter takes two arguments: a function that takes and element and returns a bool value, and an Array.
module Filter [<EntryPoint>] let main (args : string[]) = let names = [| "Katniss"; "Peeta"; "Haymitch"; "Gale"; "Primrose"; "Coriolanus"; |] let longNames = Array.filter (fun (name: string) -> name.Length > 6) names printfn "%A" longNames //[|"Katniss"; "Haymitch"; "Primrose"; "Coriolanus"|] 0
- Zipping
A zipping function takes to collections and combines them.
module Zipper [<EntryPoint>] let main (args : string[]) = let firstNames = [| "Katniss"; "Peeta"; "Haymitch"; |] let lastNames = [| "Everdeen"; "Mellark"; "Abernathy"; |] let fullNames = Array.zip(firstNames) lastNames printfn "%A" fullNames //[|("Katniss", "Everdeen"); ("Peeta", "Mellark"); ("Haymitch", "Abernathy")|] 0
- Pipelining
The pipelining operator, "|>" allows us to push arguments onto functions. The technical definition is: let inline (|>) x f = f x
module Pipelining [<EntryPoint>] let main (args : string[]) = let square a = a * a let four = square 2 let four = 2 |> square printfn "%d" four let twoSquaredFourTimes = 2 |> square |> square |> square |> square printfn "%d" twoSquaredFourTimes 0
- Functional Composition
When you want to pipe the output of one function into another you can use the pipelining operation shown above or you can use the composition operation, ">>". f(g(x)) is the same as h = f >> g
module Composition [<EntryPoint>] let main (args : string[]) = let square a = a * a let double b = b * 2 let squareThenDouble = square >> double printfn "%d" (squareThenDouble 3) //18; i.e., 3 |> square |> double 0
- Closures
Closures are functions that have some pre-bound variables, i.e., some arguments of the function are predefined or "closed". Closures allow us to make complicated functions from many simpler ones. This encourages reuse and shorter programs.
-
module Closure [<EntryPoint>] let main (args : string[]) = let multiply x y = x * y let triple = multiply 3 //partial application of function. // "triple" is a closure that takes one argument and multiples it by three printfn "%d" (triple 5) //15 0
- Lazy Evaluation
You can delay evaluation by using Lazy evaluation. The expression will only evaluate when "Force" is invoked and the value is cached, (or memoized) and returned for future invocations of "Force". In the code below note that "multiplying 7 by 6" is printed only once, even through we invoke it twice.
module Lazy [<EntryPoint>] let main (args : string[]) = let multiply x y = printfn "multiplying %d by %d" x y x * y let answer = lazy(multiply 7 6) printfn "after assignment." printfn "%d" (answer.Force()) //prints 'multiplying 7 by 6' '42' printfn "%d" (answer.Force()) //prints only '42' since calculation was memoized. 0
Produces:
after assignment. multiplying 7 by 6 42 42
- Operator Overloading
The following symbols can be used for operators: !, $, %, &, *, +, -, ., /, <, =, >, ?, @, ^, | and ~. You define the operator like this:
let (operator-symbols) parameter-list = function-body
module Overload [<EntryPoint>] let main (args : string[]) = let ($) (x: float) (y: float) = (x + y)/2.0 let avg = 10.0 $ 20.0 printfn "%f" avg //15.000000 0
You are not limited to a single symbol, but can use a seqence of the symbols to make new operators
module Overload [<EntryPoint>] let main (args : string[]) = //define geometric mean as the awkward sequence "%!" let (%!) (x: float) (y: float) = System.Math.Sqrt(x * y) let geoMean = 2.0 %! 10.0 printfn "%f" geoMean //4.472136 0
So far all of our operators have been binary, taking two arguments with our operator in the middle. You can define unary operators by prefixing the definition with "~".
module Overload [<EntryPoint>] let main (args : string[]) = let (~%) (x: float) = System.Math.Round(x) let num = % 10.2 printfn "%f" num //10.0 0