Upload
kit-eason
View
378
Download
2
Embed Size (px)
DESCRIPTION
An intro to F# done originally for SmartDevs, Hereford
Citation preview
@kitlovesfsharp www.github.com/misterspeedy
F# For an Easy Life!
Kit Eason
F# in a nutshell
Microsoft first class supported language for .NET
Supports OO and functional paradigms
Compiles to CLI like C#, VB.Net
First class citizen in VS2010 and VS2012 (free!). (Also in 2013 preview)
Open source, runs on Mono (use Xamarin)
Strongly typed
F# functions
Use ‘let’ to declare functions and values
let add x y =
x + y
The return value of the function is the last value calculated (no ‘return’
statement
let add x y =
x + y
Types are inferred (at design time)
Argument lists don’t have brackets
F# Interactive (FSI)
> let add x y =
x + y;;
val add : x:int -> y:int -> int
> add 3 4;;
val it : int = 7
>
• Use F# interactive to define and try out functions
Calculating present value
> let presentValue fv r t =
fv * (1.+r) ** (-t);;
val presentValue : fv:float -> r:float -> t:float -> float
> presentValue 2000.0 0.07 10.;;
val it : float = 1016.698584
>
• Formulae can be represented very directly
The ‘map’ function
Take some collection (array, list, IEnumerable)
For every value calculate and return some other value
Result is another array/list/IEnumerable containing the results
let someSquares min max =
// This generates an array from min to max:
let numbers = [|min..max|]
Array.map (fun x -> x * x) numbers
val someSquares : min:int -> max:int -> int []
> someSquares 100 110;;
val it : int [] =
[|10000; 10201; 10404; 10609; 10816; 11025; 11236; 11449; 11664;
11881;
12100|]
>
The forward pipe operator |>
Takes the output from the preceding function
Places it into the (last) parameter of the following function
let add x y =
x + y
let multiply x y =
x * y
val add : x:int -> y:int -> int
val multiply : x:int -> y:int -> int
> add 2 3 |> multiply 5;;
val it : int = 25
>
The forward pipe operator |> (2)
Comes into its own when dealing with collections
let rootMeanSquare min max =
[|min..max|]
|> Array.map (fun x -> x * x)
|> Array.average
|> sqrt
val rootMeanSquare : min:float -> max:float -> float
> rootMeanSquare -10.0 10.0;;
val it : float = 6.055300708
>
The ‘mapi’ function
Like ‘map’ but provides you with an index value 0, 1, 2 etc.
let someAdditions min max =
[|min..max|]
|> Array.mapi (fun i x -> i + x)
val someAdditions : min:int -> max:int -> int []
> someAdditions 100 110;;
val it : int [] = [|100; 102; 104; 106; 108; 110; 112;
114; 116; 118; 120|]
>
Calculating present value of a cashflow
Calculate the present value of a cashflow starting at time 0
let presentValue fv r t = fv * (1.+r) ** (-t)
let cfPresentValue cf r =cf|> Array.mapi (fun t amt -> presentValue amt r (float(t)))|> Array.sum
val presentValue : fv:float -> r:float -> t:float -> float
val cfPresentValue : cf:float [] -> r:float -> float
> cfPresentValue [|1000.0; 2000.0; 2500.0|] 0.07;;
val it : float = 5052.755699
>
Great Circle Distance
Unit Testing and TDD
Create a new F# library project
Use Nuget to bring in FSUnit
Add GreatCircleTests.fs
module GreatCircleTests
open NUnit.Framework
open FsUnit
Write a trivial test!
module GreatCircleTests
open NUnit.Frameworkopen FsUnit
[<TestFixture>]type ``Given the GreatCircleDistance function``() =
[<Test>]member x.``The function returns 0 for a journey between the same
points``() =let expected = 0.let actual = GreatCircle.Distance 45. 50. 45. 50.actual |> should equal expected
Pass the test!
module GreatCircle
let Distance lat1 long1 lat2 long2 =0.
What’s the next simplest test?
[<Test>]member x.``The function returns 20014 km for a journey between the poles``() =
let expected = 20014.let actual = GreatCircle.Distance -90. 0. 90. 0.actual |> should equal expected
We’re gonna need an algorithm!
var R = 6371; // km
var dLat = (lat2-lat1).toRad();
var dLon = (lon2-lon1).toRad();
var lat1 = lat1.toRad();
var lat2 = lat2.toRad();
var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
var d = R * c;
How’s this?
open System
let Distance lat1 lon1 lat2 lon2 =
let EarthRadius = 6378.1let Deg2Rad deg =
deg * Math.PI / 180.
let lat1r = lat1 |> Deg2Radlet lat2r = lat2 |> Deg2Rad
let dLat = lat2 - lat1 |> Deg2Radlet dLon = lon2 - lon1 |> Deg2Rad
let a = (sin(dLat/2.) ** 2.) + (sin(dLon/2.) ** 2.) * cos(lat1r) * cos(lat2r)
let c = 2. * atan2 (sqrt(a)) (sqrt(1.-a))
c * EarthRadius
Whuuuuuuuuuuuuuuuuuu?
------ Test started: Assembly: FSUnitTestingExample.dll ------
Test 'GreatCircleTests+Given the GreatCircleDistance function.The
function returns 20014 km for a journey between the poles' failed:
Expected: 20014.0d
But was: 20037.392103861061d
at FsUnit.TopLevelOperators.should[a,a](FSharpFunc`2 f, a x,
Object y)
C:\Code\VS2012\FSUnitTestingExample\FSUnitTestingExample\Gre
atCircleTests.fs(28,0): at GreatCircleTests.Given the
GreatCircleDistance function.The function returns 20014 km for a
journey between the poles()
1 passed, 1 failed, 0 skipped, took 0.37 seconds (NUnit 2.6.1).
They all laughed at Christopher Columbus…
[<TestFixture>]type ``Given the GreatCircleDistance function``() =
let margin = 0.003
[<Test>]member x.``The function returns 0 for a journey between the same points``() =
let expected = 0.let actual = GreatCircle.Distance 45. 50. 45. 50.actual |> should (equalWithin margin) expected
[<Test>]member x.``The function returns 20014 km for a journey between the poles``() =
let expected = 20014.let actual = GreatCircle.Distance -90. 0. 90. 0.let error = expected * marginactual |> should (equalWithin error) expected
Further tests better added as cases
// Travel no distance:[<TestCase(0., 0., 0., 0., 0.)>]// Travel along the equator eastwards for 90 degrees:[<TestCase(0., 0., 0., 90., 10018.79)>]// Travel along the equator westwards for 90 degrees:[<TestCase(0., 0., 0., -90., 10018.79)>]// Travel along the equator eastwards for 180 degrees:[<TestCase(0., 0., 0., 180., 20037.58)>]// Travel along the equator westwards for 180 degrees:[<TestCase(0., 0., 0., -180., 20037.58)>]// Travel along the meridian northwards 90 degrees:[<TestCase(0., 0., 90., 0., 10018.79)>]// Travel along the meridian soutwards 90 degrees:[<TestCase(0., 0., -90., 0., 10018.79)>]// Travel from Farnham to Reigate:[<TestCase(51.214, -0.799, 51.230, -0.188, 42.6)>]// Travel from London to Sidney Australia:[<TestCase(51.51, -0.13, -33.86, 151.21, 16998.)>]
member t.``the function returns the right result``(lat1, long1, lat2, long2, expected) =let actual = GreatCircle.Distance lat1 long1 lat2 long2let error = expected * marginactual |> should (equalWithin error) expected
Information-Rich Programming
Bring large, structured data sources into the code in a type-safe way…
with Intellisense!
Implemented by ‘Type Providers’
Introduced with F#3.0 (VS2012)
Code-gen free!
Information Rich Programming
22
Freebase
Nuget and Fsharp.Data
Get airports and locations/// Gets airports between a specified start and end index (to facilitate paged access).
let GetAirportsPaged startIndex pageSize =
query {
for airport in dc.Transportation.Aviation.Airports do
where ( airport.Geolocation <> null
&& airport.Geolocation.Latitude.HasValue
&& airport.Geolocation.Longitude.HasValue
)
skip startIndex
take pageSize
select (airport.Name,
airport.Geolocation.Latitude.Value,
airport.Geolocation.Longitude.Value)
}
|> Array.ofSeq
|> Array.map (fun (name, lat, long) -> { Name = name; Lat = lat; Long = long })
Get airports and locations (2)
/// Gets all airports from Freebase which have a defined location.
let GetAllAirports pageSize =
let rec getPage startIndex acc =
let page = GetAirportsPaged startIndex pageSize
if page.Length > 0 then
Array.append acc (getPage (startIndex+pageSize) page)
else
acc
getPage 0 [||]
Get closest airports
/// Gets the closest n airports to the given airport.
let GetClosest target count airportList =
airportList
|> Array.map (fun airport -> airport, (DistanceBetween(airport.Lat) (airport.Long) (target.Lat) (target.Long)))
|> Array.sortBy (fun (airport, distance) -> distance)
|> Seq.truncate count
|> Array.ofSeq
Get closest airports to a named airport
/// Gets airports near an airport specified by name.
let GetAirportsNear name airportList =
let target = airportList
|> Array.tryFind (fun airport -> airport.Name.Contains(name))
if target.IsSome then
airportList
|> GetClosest target.Value 20
|> Array.iter (fun (airport, distance) -> printfn "%s - %f km" airport.Name distance)
else
printfn "Could not find %s" name