Multiple itemsval char : char--------------------type char = System.CharFull name: Microsoft.FSharp.Core.char
val zero : int
namespace System
type Convert = static val DBNull : obj static member ChangeType : value:obj * typeCode:TypeCode -> obj + 3 overloads static member FromBase64CharArray : inArray:char[] * offset:int * length:int -> byte[] static member FromBase64String : s:string -> byte[] static member GetTypeCode : value:obj -> TypeCode static member IsDBNull : value:obj -> bool static member ToBase64CharArray : inArray:byte[] * offsetIn:int * length:int * outArray:char[] * offsetOut:int -> int + 1 overload static member ToBase64String : inArray:byte[] -> string + 3 overloads static member ToBoolean : value:obj -> bool + 17 overloads static member ToByte : value:obj -> byte + 18 overloads ...Full name: System.Convert
System.Convert.ToInt32(value: System.DateTime) : int (+0 other overloads)System.Convert.ToInt32(value: string) : int (+0 other overloads)System.Convert.ToInt32(value: decimal) : int (+0 other overloads)System.Convert.ToInt32(value: float) : int (+0 other overloads)System.Convert.ToInt32(value: float32) : int (+0 other overloads)System.Convert.ToInt32(value: uint64) : int (+0 other overloads)System.Convert.ToInt32(value: int64) : int (+0 other overloads)System.Convert.ToInt32(value: int) : int (+0 other overloads)System.Convert.ToInt32(value: uint32) : int (+0 other overloads)System.Convert.ToInt32(value: uint16) : int (+0 other overloads)
type Char = struct member CompareTo : value:obj -> int + 1 overload member Equals : obj:obj -> bool + 1 overload member GetHashCode : unit -> int member GetTypeCode : unit -> TypeCode member ToString : unit -> string + 1 overload static val MaxValue : char static val MinValue : char static member ConvertFromUtf32 : utf32:int -> string static member ConvertToUtf32 : highSurrogate:char * lowSurrogate:char -> int + 1 overload static member GetNumericValue : c:char -> float + 1 overload ... endFull name: System.Char
System.Char.IsDigit(c: char) : boolSystem.Char.IsDigit(s: string, index: int) : bool
union case Option.Some: Value: 'T -> Option<'T>
union case Option.None: Option<'T>
val parseScore : chars:char list -> Option<int> listFull name: Index.parseScore
val chars : char list
type 'T list = List<'T>Full name: Microsoft.FSharp.Collections.list<_>
Multiple itemsval char : value:'T -> char (requires member op_Explicit)Full name: Microsoft.FSharp.Core.Operators.char--------------------type char = System.CharFull name: Microsoft.FSharp.Core.char
module Optionfrom Microsoft.FSharp.Core
Multiple itemsval int : value:'T -> int (requires member op_Explicit)Full name: Microsoft.FSharp.Core.Operators.int--------------------type int = int32Full name: Microsoft.FSharp.Core.int--------------------type int<'Measure> = intFull name: Microsoft.FSharp.Core.int<_>
val rest : char list
active recognizer Digit: char -> int optionFull name: Index.( |Digit|_| )
val x : int
val parseScoreResult : Option<int> listFull name: Index.parseScoreResult
val countScore : scores:int list -> intFull name: Index.countScore
val scores : int list
val count : (int -> int list -> int)
val frame : int
val b1 : int
val b2 : int
val next : int list
val r1 : int
val r2 : int
val countScoreResult : intFull name: Index.countScoreResult
val sequence : optionals:'a option list -> 'a list optionFull name: Index.sequence
val optionals : 'a option list
type 'T option = Option<'T>Full name: Microsoft.FSharp.Core.option<_>
val sequence' : ('b list option -> 'b option list -> 'b list option)
val acc : 'b list option
val optionals : 'b option list
val map : mapping:('T -> 'U) -> option:'T option -> 'U optionFull name: Microsoft.FSharp.Core.Option.map
Multiple itemsmodule Listfrom Microsoft.FSharp.Collections--------------------type List<'T> = | ( [] ) | ( :: ) of Head: 'T * Tail: 'T list interface IEnumerable interface IEnumerable<'T> member GetSlice : startIndex:int option * endIndex:int option -> 'T list member Head : 'T member IsEmpty : bool member Item : index:int -> 'T with get member Length : int member Tail : 'T list static member Cons : head:'T * tail:'T list -> 'T list static member Empty : 'T listFull name: Microsoft.FSharp.Collections.List<_>
val rev : list:'T list -> 'T listFull name: Microsoft.FSharp.Collections.List.rev
val h : 'b
val t : 'b option list
val acc : 'b list
val oneOption : string list optionFull name: Index.oneOption
val bowlingScore : score:string -> Option<int>Full name: Index.bowlingScore
val score : string
Multiple itemsval string : value:'T -> stringFull name: Microsoft.FSharp.Core.Operators.string--------------------type string = System.StringFull name: Microsoft.FSharp.Core.string
System.String.ToCharArray() : char []System.String.ToCharArray(startIndex: int, length: int) : char []
module Arrayfrom Microsoft.FSharp.Collections
val toList : array:'T [] -> 'T listFull name: Microsoft.FSharp.Collections.Array.toList
val bowlingScoreResult : Option<int>Full name: Index.bowlingScoreResult
Multiple itemstype EntryPointAttribute = inherit Attribute new : unit -> EntryPointAttributeFull name: Microsoft.FSharp.Core.EntryPointAttribute--------------------new : unit -> EntryPointAttribute
val printfn : format:Printf.TextWriterFormat<'T> -> 'TFull name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
type byref<'T> = (# "<Common IL Type Omitted>" #)Full name: Microsoft.FSharp.Core.byref<_>
type bool = System.BooleanFull name: Microsoft.FSharp.Core.bool
type Int32 = struct member CompareTo : value:obj -> int + 1 overload member Equals : obj:obj -> bool + 1 overload member GetHashCode : unit -> int member GetTypeCode : unit -> TypeCode member ToString : unit -> string + 3 overloads static val MaxValue : int static val MinValue : int static member Parse : s:string -> int + 3 overloads static member TryParse : s:string * result:int -> bool + 1 overload endFull name: System.Int32
System.Int32.TryParse(s: string, result: byref<int>) : boolSystem.Int32.TryParse(s: string, style: System.Globalization.NumberStyles, provider: System.IFormatProvider, result: byref<int>) : bool
val dict : keyValuePairs:seq<'Key * 'Value> -> System.Collections.Generic.IDictionary<'Key,'Value> (requires equality)Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.dict
namespace System.Collections
namespace System.Collections.Generic
Multiple itemstype Dictionary<'TKey,'TValue> = new : unit -> Dictionary<'TKey, 'TValue> + 5 overloads member Add : key:'TKey * value:'TValue -> unit member Clear : unit -> unit member Comparer : IEqualityComparer<'TKey> member ContainsKey : key:'TKey -> bool member ContainsValue : value:'TValue -> bool member Count : int member GetEnumerator : unit -> Enumerator<'TKey, 'TValue> member GetObjectData : info:SerializationInfo * context:StreamingContext -> unit member Item : 'TKey -> 'TValue with get, set ... nested type Enumerator nested type KeyCollection nested type ValueCollectionFull name: System.Collections.Generic.Dictionary<_,_>--------------------System.Collections.Generic.Dictionary() : unitSystem.Collections.Generic.Dictionary(capacity: int) : unitSystem.Collections.Generic.Dictionary(comparer: System.Collections.Generic.IEqualityComparer<'TKey>) : unitSystem.Collections.Generic.Dictionary(dictionary: System.Collections.Generic.IDictionary<'TKey,'TValue>) : unitSystem.Collections.Generic.Dictionary(capacity: int, comparer: System.Collections.Generic.IEqualityComparer<'TKey>) : unitSystem.Collections.Generic.Dictionary(dictionary: System.Collections.Generic.IDictionary<'TKey,'TValue>, comparer: System.Collections.Generic.IEqualityComparer<'TKey>) : unit
type IDisposable = member Dispose : unit -> unitFull name: System.IDisposable
F# CAMP
Writing .NET applications in F#
- Open up new instance of Visual Studio 2015
- Make sure you have F# templates (in Other Languages)
- Let's stick to .NET Framework v4.5.1
Agenda
- F# Library (bowling score)
- F# Console app
- F# Build script - FAKE
- F# Test project - xUnit
- C# Window app - WPF (integration with F#)
- F# Web app
F# Library (bowling score)
- Create new, blank solution called "bowling" in directory of your choice
- Create new F# library called "bowling" in the solution
- Delete Script.fsx from the project
- Rename Library1.fs to Bowling.fs
- Open renamed file Bowling.fs in editor
- Remove generated code from the file, and insert module declaration:
1:
module Bowling
! Remember to save all changes when manipulating projects in Visual Studio (Ctrl + Shift + S)
- Copy code for Digit active pattern recognizer into Bowling module
1:
2:
3:
4:
5:
6:
let (|Digit|_|) char =
let zero = System.Convert.ToInt32 '0'
if System.Char.IsDigit char then
Some (System.Convert.ToInt32 char - zero)
else
None
- Copy code for parseScore function after Digit
1:
2:
3:
4:
5:
6:
7:
8:
9:
let rec parseScore (chars: list<char>) : list<Option<int>> =
match chars with
| [] -> []
| 'X' :: rest -> Some 10 :: parseScore rest
| Digit x :: '/' :: rest -> Some x :: Some (10 - x) :: parseScore rest
| Digit x :: rest -> Some x :: parseScore rest
| '-' :: '/' :: rest -> Some 0 :: Some 10 :: parseScore rest
| '-' :: rest -> Some 0 :: parseScore rest
| _ :: rest -> None :: parseScore rest
Test the code in Interactive
- Select all lines excluding module declaration
- Trigger Execute in Interactive
- In interactive window, enter following:
1:
let parseScoreResult = parseScore ['4'; '/'];;
Value of parseScoreResult
[Some 4; Some 6]
Exercise
- Extend Digit active pattern to recognize '-' character as well,
- Rename Digit to Pins to better reflect its intent after the change,
- Refactor parseScore function - make use of the fact that Pins recognizes now '-' and remove redundant pattern matching case(s),
- In interactive, make sure that after refactoring the code still works.
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
let countScore (scores: list<int>) : int =
let rec count frame scores =
match scores with
| [] -> 0
| 10 :: (b1 :: b2 :: _ as next) ->
10 + b1 + b2 + (if frame = 10 then 0 else count (frame+1) next)
| r1 :: r2 :: (b1 :: _ as next) when r1 + r2 = 10 ->
10 + b1 + (if frame = 10 then 0 else count (frame+1) next)
| r1 :: r2 :: next ->
r1 + r2 + count (frame+1) next
count 1 scores
- Test the function in interactive:
1:
let countScoreResult = countScore [10;9;1;5;5;7;2;10;10;10;9;0;8;2;9;1;10];;
Value of countScoreResult
187
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
let sequence (optionals: list<option<'a>>) : option<list<'a>> =
let rec sequence' acc optionals =
match optionals, acc with
| [],_ ->
Option.map List.rev acc
| Some h :: t, Some acc ->
sequence' (Some (h :: acc)) t
| _ ->
None
sequence' (Some []) optionals
- Test the function in interactive:
1:
let oneOption = sequence [Some "abc"; Some "def"; Some "ghi"];;
Value of oneOption
Some ["abc"; "def"; "ghi"]
- Add bowlingScore function
1:
2:
3:
4:
5:
6:
let bowlingScore (score: string) : Option<int> =
score.ToCharArray()
|> Array.toList
|> parseScore
|> sequence
|> Option.map countScore
- Test the function in interactive:
1:
let bowlingScoreResult = bowlingScore "X9/5/72XXX9-8/9/X";;
Value of bowlingScoreResult
Some 187
Summary
- Creating F# Library projects in VS
- Declaring Bowling module
- Testing code in interactive
F# Console app
- Create new F# Console Application "bowling.console"
- Add project reference from "bowling.console" to "bowling"
- Compile "bowling" project
- Invoke Bowling.bowlingScore on example input and print output
1:
2:
3:
4:
5:
6:
7:
// Learn more about F# at http://fsharp.org
// See the 'F# Tutorial' project for more help.
[<EntryPoint>]
let main argv =
printfn "%A" (Bowling.bowlingScore "XXXXXXXXXXXX")
0 // return an integer exit code
Exercise
Invoke Bowling.bowlingScore for each argument from argv (console arguments)
XXXXXXXXXXXXX 9-9-9-9-9-9-9-9-9-9- 5/5/5/5/5/5/5/5/5/5/5 X9/5/72XXX9-8/9/X
Hint: Use Array.iter function to perform an action for each element from an array
Summary
- Creating F# console apps
- Printing to console
F# Build script - FAKE
Paket for managing dependencies
- Create new directory ".paket" next to the ".sln" solution file
- Download paket.bootstrapper.exe from here and save it in ".paket" directory
- In console, change directory to where the solution file and ".paket" folder are located. Do not change directory to ".paket"
- Run paket.bootstrapper.exe from console to download newest Paket, and then invoke paket.exe init:
1:
2:
> .paket\paket.bootrapper.exe
> .paket\paket.exe init
- In solution, add "New Solution Folder" called ".project"
- "Add Existing Item" - add "paket.dependencies" file to the ".project" solution folder
- Open "paket.dependencies" file in the VS editor
Modify the "paket.dependencies" file to add "Build" group and "FAKE" package:
1:
2:
3:
4:
group Build
source https://www.nuget.org/api/v2
nuget FAKE
Run paket install:
1:
> .paket\paket.exe install
Add "New Item", "build.cmd" to the ".project" solution folder:
1:
2:
3:
4:
5:
@echo off
cls
.paket\paket.bootstrapper.exe
.paket\paket.exe restore
packages\Build\FAKE\tools\FAKE.exe build.fsx %*
Add "New Item", "build.fsx" to the ".project" solution folder:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
#r @"packages/Build/FAKE/tools/FakeLib.dll"
open Fake
Target "Build" (fun _ ->
MSBuildRelease "bin" "Build" ["bowling.sln"]
|> Log "Build output"
)
RunTargetOrDefault "Build"
Run the build script:
1:
build.cmd
Summary
- Paket for managing dependencies
- FAKE for build scripts
- Invoking build script from command line
- Create new F# Library "bowling.tests" for .NET 4.5.1,
- Remove "Script.fsx" file,
- Rename "Library1.fs" to "Tests.fs",
- "Add new item", "App.config" application configuration file to "bowling.tests",
- Add Project Reference from "bowling.tests" to "bowling",
- Remove boilerplate code and declare Bowling.Tests module:
1:
module Bowling.Tests
! Save all changes in Visual Studio
- Open "paket.dependencies" in VS editor,
- Add "xunit.runner.console" package to "Build" group,
- Add new group "Tests" with "framework: net451",
- Add "FSharp.Core" 4.0.0.1 with "redirects: force" option, "xUnit" and "FsUnit.xUnit" nugets to "Tests" group
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
group Build
source https://www.nuget.org/api/v2
nuget FAKE
nuget xunit.runner.console
group Tests
framework: net451
source https://www.nuget.org/api/v2
nuget FSharp.Core 4.0.0.1 redirects: force
nuget xUnit
nuget FsUnit.xUnit
- Add "New Item", "paket.references" (General -> Text File) to "bowling.tests" project
- Open "paket.references" file in VS editor and fill it with below:
1:
2:
3:
4:
group Tests
FSharp.Core
xUnit
FsUnit.xUnit
Run "paket install":
1:
> .paket\paket.exe install
- Open "build.fsx" build script in VS editor,
- Add "Tests" build target:
1:
2:
3:
4:
5:
6:
7:
8:
9:
open Fake.Testing // for testing helper functions
Target "Tests" (fun _ ->
["bin/bowling.tests.dll"]
|> xUnit2 (fun xunitParams ->
{ xunitParams with ToolPath = @"packages/Build/xunit.runner.console/"
+ @"tools/xunit.console.exe" }
)
)
At the bottom of "build.fsx", specify Target dependency and change default target to "Tests":
1:
2:
3:
4:
5:
6:
// Targets above
"Build"
==> "Tests"
RunTargetOrDefault "Tests"
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
#r @"packages/Build/FAKE/tools/FakeLib.dll"
open Fake
open Fake.Testing
Target "Build" (fun _ ->
MSBuildRelease "bin" "Build" ["bowling.sln"]
|> Log "Build output"
)
Target "Tests" (fun _ ->
["bin/bowling.tests.dll"]
|> xUnit2 (fun xunitParams ->
{ xunitParams with ToolPath = @"packages/Build/xunit.runner.console/"
+ @"tools/xunit.console.exe" }
)
)
"Build"
==> "Tests"
RunTargetOrDefault "Tests"
- Open "Tests.fs" source file in editor,
- Add unit test for checking score of 12 Strikes:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
module Bowling.Tests
open Xunit
open FsUnit.Xunit
[<Fact>]
let ``12 strikes in row`` () =
let expected = Some 270
let actual = Bowling.bowlingScore "XXXXXXXXXXXX"
actual |> should equal expected
Run the build script (without any additional parameters):
Exercises
- Fix the test
-
Add three more test cases for following scores:
- "9-9-9-9-9-9-9-9-9-9-"
- "5/5/5/5/5/5/5/5/5/5/5"
- "X9/5/72XXX9-8/9/X"
Summary
- Creating test library in F#
- Adding test nuget packages with Paket
- Attaching tests to the build script pipeline
- Writing unit tests in F#
C# Window app - WPF (integration with F#)
- Add C# Windows "WPF Application" project, "bowling.wpf" to the solution,
- Add project reference from "bowling.wpf" to "bowling",
- Design awesome GUI with a TextBox, TextBlock and a Button:
- Open "paket.dependencies" file,
- Add "FSharp.Core" 4.0.0.1 package with "redirects: force" option to main group,
- Use "framework: net451" for main group as well:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
framework: net451
source https://www.nuget.org/api/v2
nuget FSharp.Core 4.0.0.1 redirects: force
group Build
source https://www.nuget.org/api/v2
nuget FAKE
nuget xunit.runner.console
group Tests
framework: net451
source https://www.nuget.org/api/v2
nuget FSharp.Core 4.0.0.1 redirects: force
nuget xUnit
nuget FsUnit.xUnit
Add "New Item", "paket.references" to "bowling.wpf":
1:
FSharp.Core
Run paket install:
1:
> .paket\paket.exe install
Add action on button click:
1:
2:
3:
4:
5:
6:
7:
8:
9:
private void button_Click(object sender, RoutedEventArgs e)
{
var input = textBox.Text;
var score = Bowling.bowlingScore(input);
textBlock.Text =
FSharpOption<int>.get_IsSome(score) ?
"Score: " + score.Value.ToString() :
"Wrong score!";
}
The F# Component Design Guidelines
Below snippet doesn't feel nice in C#:
1:
2:
3:
4:
textBlock.Text =
FSharpOption<int>.get_IsSome(score) ?
"Score: " + score.Value.ToString() :
"Wrong score!";
Exercise
Create new function TryGetBowlingScore in Bowling module for better interop with C#, conforming to the F# Component Design Guidelines.
Use the new function in code behind button click in C#.
Skeleton of the function
1:
2:
3:
let TryGetBowlingScore(score: string, result : byref<int>) : bool =
result <- 0
false
New stuff - assign value operator
1:
2:
let mutable x = 0
x <- 5
Using .NET libraries from F#
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
let (i1success,i1) = System.Int32.TryParse("123");
if i1success then printfn "parsed as %i" i1 else printfn "parse failed"
let dict = new System.Collections.Generic.Dictionary<string,string>()
dict.Add("a","hello")
let (e1success,e1) = dict.TryGetValue("a")
let (e2success,e2) = dict.TryGetValue("b")
let makeResource name =
{ new System.IDisposable
with member this.Dispose() = printfn "%s disposed" name }
https://fsharpforfunandprofit.com/posts/completeness-seamless-dotnet-interop/
Summary
- Referencing F# code from C# (FSharp.Core package)
- Conforming to the F# Component Design Guidelines
- Using .NET libraries from F#
- Add F# Console application project, "bowling.web" to the solution,
- Add project reference from "bowling.web" to "bowling",
- Open "paket.dependencies" file,
- Add "Suave" package to main group:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
framework: net451
source https://www.nuget.org/api/v2
nuget FSharp.Core 4.0.0.1 redirects: force
nuget Suave
group Build
source https://www.nuget.org/api/v2
nuget FAKE
nuget xunit.runner.console
group Tests
framework: net451
source https://www.nuget.org/api/v2
nuget FSharp.Core 4.0.0.1 redirects: force
nuget xUnit
nuget FsUnit.xUnit
Add "New Item", "paket.references" to "bowling.web":
1:
Suave
Run paket install:
1:
> .paket\paket.exe install
- Open "Program.fs" from "bowling.web",
- Remove boilerplater code, and insert following hello world suave:
1:
2:
3:
open Suave
startWebServer defaultConfig (Successful.OK "Hello world")
Exercise
Implement scoreHandler function so that:
- it responds with 200 OK with score for correct input,
- it responds with 400 BAD REQUEST with "Wrong result" message for wrong input:
1:
2:
3:
4:
5:
6:
open Suave
let scoreHandler (input: string) : WebPart =
Successful.OK "Hello world"
startWebServer defaultConfig (Filters.pathScan "/%s" scoreHandler)
Hint: Make use of Successful.OK and RequestErrors.BAD_REQUEST functions. Both are of type string -> WebPart.
Demo: .fs files order in project
(order matters)
At first it looks like a limitation but it really turns out to be one of the most beloved F# features
Summary
- Suave.IO is a very light-weight server library, easy to use with F#
- .fs file order inside project matters
Summary
- F# Library (bowling score)
- F# Console app
- F# Build script - FAKE
- F# Test project - xUnit
- C# Window app - WPF (integration with F#)
- F# Web app
F# CAMP
Writing .NET applications in F#
Open up new instance of Visual Studio 2015
Make sure you have F# templates (in Other Languages)
Let's stick to .NET Framework v4.5.1