Skip to content

Instantly share code, notes, and snippets.

@james-world
Last active December 6, 2019 17:04
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save james-world/d03b90a478f730b343b915c6306fa123 to your computer and use it in GitHub Desktop.
Save james-world/d03b90a478f730b343b915c6306fa123 to your computer and use it in GitHub Desktop.
Notes from a talk on FSharp

F# for C# Developers - Show Notes

Worthwhile Links

Recommended Books

Code Samples from the Talk

Card Payment Example (from Scott Wlaschin)

type CheckNumber = int
type CardNumber = string
type CardType = Visa | Mastercard
type CreditCardInfo = CardType * CardNumber

type PaymentMethod =
    | Cash
    | Check of CheckNumber
    | Card of CreditCardInfo

type PaymentAmount = decimal
type Currency = EUR | USD

type Payment = {
    Amount : PaymentAmount
    Currency: Currency
    Method: PaymentMethod

// Card Game Example

type Suit = Club | Diamond | Spade | Heart
type Rank = Two | Three | Four | Five | Six | Seven | Eight
            | Nine | Ten | Jack | Queen | King | Ace
type Card = Suit * Rank

type Hand = Card list
type Deck = Card list

type Player = { Name: string; Hand: Hand }
type Game = { Deck: Deck; Players: Player list }

type Deal = Deck -> Deck * Card
type PickUp = (Hand * Card) -> Hand

Composition and Piping

let add1 x = x + 1
let double x = x * 2
let square x = x * x

// 1. Show composition

let add1_double_square = add1 >> double >> square

// 2. Show piping

5 add1 |> double |> square

// 3. Show abstraction (if time)

let foo x = String.replicate x "X"
let bar s = String.length s

// abstracting away the string

let foo_bar = foo >> bar

Implementing Composition and Piping in C#

void Main()
{
	Func<int, int> add1 = x => x + 1;
	Func<int, int> doubl = x => x * 2;
	Func<int, int> square = x => x * x;
	
	var add1_doubl_square = add1.Compose(doubl).Compose(square);
	
	add1_doubl_square(5).Pipe(Console.WriteLine);
	
	5.Pipe(add1).Pipe(doubl).Pipe(square).Pipe(Console.WriteLine);
}

public static class FuncExtensions
{
	public static Func<T, V> Compose<T,U,V>(
		this Func<T,U> left,
		Func<U, V> right) => x => right(left(x));
		
	public static U Pipe<T,U>(this T x, Func<T, U> f) => f(x);
	
	public static void Pipe<T>(this T x, Action<T> f) => f(x);
}

Screen-Scraping Species

You will need to install nuget packages FSharp.Data and Suave and reference the correct paths to their dlls.

#r "../packages/FSharp.Data/lib/net45/FSharp.Data.dll"

open FSharp.Data

type Species = HtmlProvider<"http://en.wikipedia.org/wiki/The_world's_100_most_threatened_species">

let species =
    [ for x in Species.GetSample().Tables.``Species list``.Rows ->
        x.Type, x.``Common name`` ]

let speciesSorted =
    species
    |> List.countBy fst
    |> List.sortByDescending snd

#r "../packages/Suave/lib/net461/Suave.dll"

open Suave

let html =
    [ yield "<html><body><ul>"
    for (category, count) in speciesSorted do
        yield sprintf "<li>Category <b>%s</b>: <b>%d</b></li>" category count
    yield "</ul></body></html>" ]
    |> String.concat "\n"

startWebServer defaultConfig (Successful.OK html)

The Parser Example

open System

type ParserResult<'T> =
    | Success of 'T * char list
    | Failure

type Parser<'T> = char list -> ParserResult<'T>

let run parser (input: string) =
    let stream = input |> Seq.toList
    match parser stream with
    | Success (v, rest) -> printfn "Parsed '%s': '%A' leaving '%s'" input v (String(List.toArray rest))
    | Failure -> printfn "Failed to parse '%s'" input

let CharParser c =
    fun stream ->
        match stream with
        | x :: xs when x = c -> Success (c, xs)
        | _ -> Failure

let (<|>) p1 p2 =
    fun stream ->
        match p1 stream with
        | Failure -> p2 stream
        | x -> x

let DigitParser =
    ['0'..'9']
    |> List.map CharParser
    |> List.reduce (<|>)

let (|>>) p f =
    fun stream ->
        match p stream with
        | Success (v, rest) -> Success (f v, rest)
        | Failure -> Failure

let DigitParserInt =
    DigitParser |>> (fun v -> int v - int '0')

let rec Many p =
    fun stream ->
        match p stream with
        | Failure -> Success ([], stream)
        | Success (v, rest) -> (Many p) |>> (fun w -> v :: w) <| rest

let Many1 p =
    fun stream ->
        match p stream with
        | Failure -> Failure
        | Success (v, rest) -> (Many p) |>> (fun w -> v :: w) <| rest

let IntegerParser =
    (Many1 DigitParserInt)
    |>> List.reduce (fun x y -> 10 * x + y)

let (>>.) p1 p2 =
    fun stream ->
        match p1 stream with
        | Failure -> Failure
        | Success (_, rest) -> p2 rest

let StringParser (str: string) =
    str.ToCharArray()
    |> Array.map CharParser
    |> Array.reduce (>>.)
    |>> (fun _ -> str)

let (.>>) p1 p2 =
    fun stream ->
        match p1 stream with
        | Success (v, rest) -> p2 |>> (fun _ -> v) <| rest
        | Failure -> Failure

let eof =
    fun stream ->
        match stream with
        | [] -> Success ((), stream)
        | _ -> Failure


let StudentIdParser =
    StringParser "SID" >>. IntegerParser .>> eof

run StudentIdParser "SID1234"

BONUS - Converting an integer to Roman Numerals

let numeralsMap =
    [ 1000, "M"
      900, "CM"
      500, "D"
      400, "CD"
      100, "C"
      90, "XC"
      50, "L"
      40, "XL"
      10, "X"
      9, "IX"
      5, "V"
      4, "IV"
      1, "I" ]

let rec arabicToRoman num =
    match num with
    | 0 -> ""
    | _ ->
	let picker (value, roman) =
	    if value <= num
	    then Some (value, roman)
	    else None
	let (value, roman) = List.pick picker numeralsMap
	roman + (arabicToRoman (num - value))

printfn "%s" (arabicToRoman 8)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment