Mavnn's blog

Stuff from my brain

Introducing F# to Experienced Developers

So, as a follow up to this post I'm in the final stages of preparing a presentation for this Friday introducing an audience of (mostly) fairly experienced developers to F# and F# syntax. The main reason for this is to get a number of people up to speed enough on reading F# that they can have a better experience at the Progressive F# Tutorials at the end of the month. So the aim here isn't to get people fully autonomous and writing code right now, but to allow them to read the bulk of the example code in the tutorials and follow what's going on.

The general approach I've gone for is to set up a Git repository that has a series of tagged snap shots I can check out as I work through the concepts I'm planning to cover. This will enable me to actually demonstrate and run pieces of code, answer questions and make live modifications and then always jump back to a known starting point for the next section of the talk. Given the people involved have all done some .net development and I don't need to cover things like Visual Studio usage and projects, all of the code is contained in a single Program.fs file in a console app. I've included the snapshot of the file from each tagged commit below, with a brief overview of what I'm planning to introduce before skipping to the next snapshot.

With a full screen Visual Studio editing session, I should be able to make the code large enough to be visible and reasonably rapidly guide people to the areas where the code has changed.

A combination of the excellent PaperCut project and a local 'http request to email' service pretending to be an SMS sender, we should be able to see messages being generated by the code as we go along.

After the session, I'm planning to mention Chris Marinos' koans and Try F# (especially given that Rachel Reese is running a session at the tutorials for those who are going).

Please note that these are up here for comment and suggestions at this point - I'll be pushing up the actual Git repository and a screencast (I hope) after the event. The code is designed to be a prop for the talk rather than an independent resource - for that I'd always point people to the koans/Try F# first.

So, show me the codez:

Tag '1'

Nothing too exciting here :). This is just a place holder while giving the introduction, although I will also point out the lack of required boiler plate compared to C#.

1
// Let's send an email!

Tag '2'

Introduce the open keyword, let for value assignment, and give people a feel that they're not completely leaving their nice safe .net world behind.

1
2
3
4
5
6
7
8
9
// Let's send an email!

open System.Net.Mail

// We only use the 'new' keyword here because SmtpClient is disposable
let smtpClient = new SmtpClient("smtp.local")

// And given it's disposable, we should really dispose of it...
smtpClient.Dispose()

Tag '3'

Talk about functions, show parameter application, introduce the pipe operator.

Some discussion about type inference will probably happen here.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
open System.Net.Mail

let smtpClient = new SmtpClient("smtp.local")

// This is a function
let sendMessage client message =
    // We'd better do something here to actually
    // send a message...
    printfn "I haven't sent a mail message!"
    ()

sendMessage smtpClient "My message"

smtpClient.Dispose()

// And we'll add this so we can see the output
// before it disappears
System.Console.ReadLine() |> ignore
// This line would be the same as writing:
//
// ignore (System.Console.Readline())
//
// but you have to admit that this is a bit 
// more readable

Tag '4'

Introduce the use keyword, show property assignment with <-.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
open System.Net.Mail

let smtpClient = new SmtpClient("smtp.local")

let sendMessage client message =
    (* Because we used 'use' this will get
    disposed at the end of the declaring
    scope *)
    use mailMessage = new MailMessage("[email protected]", "[email protected]")
    (* This is have you assign a parameter *)
    mailMessage.Subject <- "Message subject"
    mailMessage.Body <- message
    smtpClient.Send mailMessage
    printfn "I've sent a mail message!"

sendMessage smtpClient "My message"

smtpClient.Dispose()
System.Console.ReadLine() |> ignore

Tag '5'

List syntax and introduce the Seq module. Example of currying.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
open System.Net.Mail

let smtpClient = new SmtpClient("smtp.local")

let sendMessage client message =
    use mailMessage = new MailMessage("[email protected]", "[email protected]")
    mailMessage.Subject <- "Message subject"
    mailMessage.Body <- message
    smtpClient.Send mailMessage
    printfn "I've sent a mail message!"

(* But really, what you want computers for
is doing the same thing lots of times... *)
let myMessages =
    [
        "My first message"
        "My second message"
        "My third and final message"
    ]

myMessages
|> Seq.iter (sendMessage smtpClient)
(* Let's have some vindaloo with that map *)

smtpClient.Dispose()
System.Console.ReadLine() |> ignore

Tag '6'

An async workflow. Turns out that SmtpClient is not as clean for that as you would hope - it's async send methods don't appear to be thread safe (wait, what?) and even the relatively recent looking SendMailAsync method returns a Task rather than a Task<'T>. Having said that, it shows that even in less than ideal circumstances, you can leverage the async stuff even when interfacing with older .net code from other languages.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
open System.Net.Mail


(* But really, what you want computers for
is doing the same thing lots of times... 
at the same time! *)
let sendMessage message =
    async {
        // Move the client inside because...
        // have _you_ checked if it's thread safe?
        use smtpClient = new SmtpClient("smtp.local")
        use mailMessage = new MailMessage("[email protected]", "[email protected]")
        mailMessage.Subject <- "Message subject"
        mailMessage.Body <- message
        do!
            smtpClient.SendMailAsync(mailMessage)
            |> Async.AwaitIAsyncResult
            |> Async.Ignore
        printfn "I've sent a mail message!"
    }

let myMessages =
    [
        "My first message"
        "My second message"
        "My third and final message"
    ]

myMessages
|> Seq.map sendMessage
|> Async.Parallel
|> Async.RunSynchronously
|> fun _ -> printfn "Finished all sends!"

System.Console.ReadLine() |> ignore

Tag '7'

Tuples! Showing both construction and deconstruction syntax. Also discuss that this is how we pass multiple parameters to methods on classes.

In the live coding for this one, I'll make sure to demonstrate adding and removing brackets in different places.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
open System.Net.Mail

let sendMessage messageDetails =
    async {
        let address, body = messageDetails
        use smtpClient = new SmtpClient("smtp.local")
        use mailMessage = new MailMessage("[email protected]", address)
        mailMessage.Subject <- "Message subject"
        mailMessage.Body <- body
        do!
            smtpClient.SendMailAsync(mailMessage)
            |> Async.AwaitIAsyncResult
            |> Async.Ignore
        printfn "I've sent a mail message!"
    }


(* But we probably don't want to send all of these
to the same person. *)
let myMessages =
    [
        "[email protected]", "My first message"
        "[email protected]", "My second message"
        "[email protected]", "My third and final message"
    ]

myMessages
|> Seq.map sendMessage
|> Async.Parallel
|> Async.RunSynchronously
|> fun _ -> printfn "Finished all sends!"

System.Console.ReadLine() |> ignore

Tag '8'

Record syntax.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
open System.Net.Mail

(* How about if I want to pass lots of different bits
of information in? *)
type messageDetails =
    {
        toAddress : string
        fromAddress : string
        body : string
    }

let sendMessage messageDetails =
    async {
        use smtpClient = new SmtpClient("smtp.local")
        use mailMessage =
            new MailMessage(
                messageDetails.fromAddress,
                messageDetails.toAddress)
        mailMessage.Subject <- "Message subject"
        mailMessage.Body <- messageDetails.body
        do!
            smtpClient.SendMailAsync(mailMessage)
            |> Async.AwaitIAsyncResult
            |> Async.Ignore
        printfn "I've sent a mail message!"
    }

let myMessages =
    [
        { toAddress = "[email protected]"; fromAddress = "[email protected]"; body = "My first message" }
        { toAddress = "[email protected]"; fromAddress = "[email protected]"; body = "My second message" }
        { toAddress = "[email protected]"; fromAddress = "[email protected]"; body = "My third message" }
    ]

myMessages
|> Seq.map sendMessage
|> Async.Parallel
|> Async.RunSynchronously
|> fun _ -> printfn "Finished all sends!"

System.Console.ReadLine() |> ignore

Tag '9'

Use discriminated unions for modeling business inputs. In this case, building a MessageDetails class that can contain the details of either an email or an SMS send request.

Also has a 2nd, maybe slightly more idiomatic implementation of an async workflow.

I'm hoping to get at least this far in the session. The rest of it would be nice, but if we get here then I'll be happy I've covered at least the basics.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
open System.IO
open System.Net
open System.Net.Mail

(* But some people have given us mobile
numbers rather than email addresses *)
type EmailDetails =
    {
        toAddress : string
        fromAddress : string
        body : string
    }

type SmsDetails =
    {
        toNumber : string
        fromNumber : string
        message : string
    }

type MessageDetails =
    | Email of EmailDetails
    | Sms of SmsDetails

let sendEmail messageDetails =
    async {
        use smtpClient = new SmtpClient("smtp.local")
        use mailMessage =
            new MailMessage(
                messageDetails.fromAddress,
                messageDetails.toAddress)
        mailMessage.Subject <- "Message subject"
        mailMessage.Body <- messageDetails.body
        do!
            smtpClient.SendMailAsync(mailMessage)
            |> Async.AwaitIAsyncResult
            |> Async.Ignore
        printfn "I've sent a mail message!"
    }

let sendSms messageDetails =
    async {
        let http = HttpWebRequest.Create("http://sms.local") :?> HttpWebRequest
        http.Method <- "POST"
        let messagePayload =
            sprintf
                "To: %s\nFrom: %s\nMessage: %s"
                messageDetails.toNumber
                messageDetails.fromNumber
                messageDetails.message
        using
            (http.GetRequestStream())
            (fun stream ->
                use sw = new StreamWriter(stream)
                sw.Write(messagePayload))
        let! response = http.GetResponseAsync() |> Async.AwaitTask
        if (response :?> HttpWebResponse).StatusCode <> HttpStatusCode.OK then
            failwith "Http request failed!"
        printfn "I've sent an SMS!"
    }

let sendMessage message =
    match message with
    | Email details -> sendEmail details
    | Sms details -> sendSms details

let myMessages =
    [
        Email { toAddress = "[email protected]"; fromAddress = "[email protected]"; body = "My first message" }
        Email { toAddress = "[email protected]"; fromAddress = "[email protected]"; body = "My second message" }
        Email { toAddress = "[email protected]"; fromAddress = "[email protected]"; body = "My third message" }
        Sms { toNumber = "+447777123123"; fromNumber = "+447888321321"; message = "Hello by sms" }
        Sms { toNumber = "+447777123124"; fromNumber = "+447888321321"; message = "Hello by sms x2" }
        Sms { toNumber = "+447777123123"; fromNumber = "+447888321321"; message = "Hello by sms x3" }
    ]

myMessages
|> Seq.map sendMessage
|> Async.Parallel
|> Async.RunSynchronously
|> fun _ -> printfn "Finished all sends!"

System.Console.ReadLine() |> ignore

Tag '10'

Validation with Active patterns.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
open System.IO
open System.Net
open System.Net.Mail

type EmailDetails =
    {
        toAddress : string
        fromAddress : string
        body : string
    }

type SmsDetails =
    {
        toNumber : string
        fromNumber : string
        message : string
    }

type MessageDetails =
    | Email of EmailDetails
    | Sms of SmsDetails

(* But what if some people have given us invalid data?

Our SMS sender requires full numbers with national
codes - let's add some validation! *)
let (|ValidSmsRequest|InvalidSmsRequest|) details =
    // Hmm. Bananas. My favourite.
    let regex = System.Text.RegularExpressions.Regex(@"^\+\d\d")
    if regex.IsMatch(details.toNumber) && regex.IsMatch(details.fromNumber) then
        ValidSmsRequest details
    else
        InvalidSmsRequest "You must include the +xx country prefix on mobile numbers."

let sendEmail messageDetails =
    async {
        use smtpClient = new SmtpClient("smtp.local")
        use mailMessage =
            new MailMessage(
                messageDetails.fromAddress,
                messageDetails.toAddress)
        mailMessage.Subject <- "Message subject"
        mailMessage.Body <- messageDetails.body
        do!
            smtpClient.SendMailAsync(mailMessage)
            |> Async.AwaitIAsyncResult
            |> Async.Ignore
        printfn "I've sent a mail message!"
    }

(* We've moved the SMS post logic into this method
without change - no validation here.

We've marked it private so no one else can call it
by mistake *)
let private postSms messageDetails =
    async {
        let http = HttpWebRequest.Create("http://sms.local") :?> HttpWebRequest
        http.Method <- "POST"
        let messagePayload =
            sprintf
                "To: %s\nFrom: %s\nMessage: %s"
                messageDetails.toNumber
                messageDetails.fromNumber
                messageDetails.message
        using
            (http.GetRequestStream())
            (fun stream ->
                use sw = new StreamWriter(stream)
                sw.Write(messagePayload))
        let! response = http.GetResponseAsync() |> Async.AwaitTask
        if (response :?> HttpWebResponse).StatusCode <> HttpStatusCode.OK then
            failwith "Http request failed!"
        printfn "I've sent an SMS!"
    }

(* And this is where we do our validation *)
let sendSms messageDetails =
    match messageDetails with
    | ValidSmsRequest details -> postSms details
    | InvalidSmsRequest error -> async { printfn "Sms sending error: %s" error }

let sendMessage message =
    match message with
    | Email details -> sendEmail details
    | Sms details -> sendSms details

let myMessages =
    [
        Email { toAddress = "[email protected]"; fromAddress = "[email protected]"; body = "My first message" }
        Email { toAddress = "[email protected]"; fromAddress = "[email protected]"; body = "My second message" }
        Email { toAddress = "[email protected]"; fromAddress = "[email protected]"; body = "My third message" }
        Sms { toNumber = "+447777123123"; fromNumber = "+447888321321"; message = "Hello by sms" }
        Sms { toNumber = "+447777123124"; fromNumber = "+447888321321"; message = "Hello by sms x2" }
        Sms { toNumber = "+447777123123"; fromNumber = "+447888321321"; message = "Hello by sms x3" }
        Sms { toNumber = "447777123123"; fromNumber = "+447888321321"; message = "I won't be sent!" }
        Sms { toNumber = "+447777123123"; fromNumber = "+ab7888321321"; message = "Neither will I!" }
        Sms { toNumber = "Bob"; fromNumber = "+ab7888321321"; message = "..and I definitely won't!" }
    ]

myMessages
|> Seq.map sendMessage
|> Async.Parallel
|> Async.RunSynchronously
|> fun _ -> printfn "Finished all sends!"

System.Console.ReadLine() |> ignore

Tag '11'

The extra credit section! I very much doubt I'll get this far in a one hour session, but if I do this is just some fun playing with type providers. Also covers randomness such as:

  • generating infinite sequences using seq expressions
  • double back tick identifiers
  • pattern matching with guards
  • a bit more of a workout of the Seq module
  • we can have a lot of discussion of lazy evaluation, because this code is just insanely broken without it
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
open System
open System.IO
open System.Net
open System.Net.Mail
open System.Text.RegularExpressions

type EmailDetails =
    {
        toAddress : string
        fromAddress : string
        body : string
    }

type SmsDetails =
    {
        toNumber : string
        fromNumber : string
        message : string
    }

type MessageDetails =
    | Email of EmailDetails
    | Sms of SmsDetails

let (|ValidSmsRequest|InvalidSmsRequest|) details =
    let regex = Regex(@"^\+\d\d")
    if regex.IsMatch(details.toNumber) && regex.IsMatch(details.fromNumber) then
        ValidSmsRequest details
    else
        InvalidSmsRequest "You must include the +xx country prefix on mobile numbers."

let sendEmail messageDetails =
    async {
        use smtpClient = new SmtpClient("smtp.local")
        use mailMessage =
            new MailMessage(
                messageDetails.fromAddress,
                messageDetails.toAddress)
        mailMessage.Subject <- "Message subject"
        mailMessage.Body <- messageDetails.body
        do!
            smtpClient.SendMailAsync(mailMessage)
            |> Async.AwaitIAsyncResult
            |> Async.Ignore
        printfn "I've sent a mail message!"
    }

let private postSms messageDetails =
    async {
        let http = HttpWebRequest.Create("http://sms.local") :?> HttpWebRequest
        http.Method <- "POST"
        let messagePayload =
            sprintf
                "To: %s\nFrom: %s\nMessage: %s"
                messageDetails.toNumber
                messageDetails.fromNumber
                messageDetails.message
        using
            (http.GetRequestStream())
            (fun stream ->
                use sw = new StreamWriter(stream)
                sw.Write(messagePayload))
        let! response = http.GetResponseAsync() |> Async.AwaitTask
        if (response :?> HttpWebResponse).StatusCode <> HttpStatusCode.OK then
            failwith "Http request failed!"
        printfn "I've sent an SMS!"
    }

let sendSms messageDetails =
    match messageDetails with
    | ValidSmsRequest details -> postSms details
    | InvalidSmsRequest error -> async { printfn "Sms sending error: %s" error }

let sendMessage message =
    match message with
    | Email details -> sendEmail details
    | Sms details -> sendSms details

(* And now for something completely different...

Let's send a bunch of actors and celebrities a selection 
of astronomical data. Because, you know. Why not?

If you're running this code at home, you'll need
to install the nuget package from the packages.config
file *)
open FSharp.Data
let FreebaseKey =
    let rec getKey (dir : DirectoryInfo) =
        match dir.EnumerateFiles("freebase.key") with
        | files when Seq.isEmpty files -> getKey (dir.Parent)
        | files -> (Seq.head files).OpenText().ReadToEnd().Trim()
    let dir = DirectoryInfo(Directory.GetCurrentDirectory())
    getKey dir

type FreebaseProvider = FreebaseDataProvider<Key="api key goes here">

let freebase = FreebaseProvider.GetDataContext()

(* If you don't have an api key you can delete lines
88 to the end of this comment, and uncomment the line below.

It will limit how many times you can run the program
before it starts throwing authentication errors,
though - there's a fairly strict rate limit. *)
//let freebase = FreebaseData.GetDataContext()

let actors =
    freebase.``Arts and Entertainment``.Film.``Film actors``
    |> Seq.filter (fun a -> not <| Seq.isEmpty a.``Film performances``)
    (* You get a (virtual) cookie if you can work out
    why I've added the filter below *)
    |> Seq.filter (fun a ->
        (a.``Film performances`` |> Seq.head)
            .Film.Name.[0 .. 0]
        |> Regex("[a-zA-Z]").IsMatch)
    |> Seq.filter (fun a -> not <| Seq.isEmpty a.``Country of nationality``)
    |> Seq.take 20

let encode (str : string) =
    let clean = Regex("\W")
    clean.Replace(str, "-")

let emailAddresses =
    seq { for actor in actors ->
            let name = actor.Name |> encode
            let domain =
                (actor.``Film performances`` |> Seq.head).Film.Name
                |> encode
            let countryCode =
                match (actor.``Country of nationality`` |> Seq.head).``ISO Alpha 2`` with
                | alpha when Seq.isEmpty alpha ->
                    "com"
                | alpha when (Seq.head alpha).ToLower() = "us" ->
                    "com"
                | alpha -> sprintf "co.%s" <| (Seq.head alpha).ToLower()
            sprintf "%s@%s.%s"
                name
                domain
                countryCode }

(* We're going to need 20 planets for our 20 celebrities,
so we'll repeat the planets as many times as we need *)
let planets =
    seq {
        while true do
            yield! freebase.``Science and Technology``.Astronomy.Planets
    }

let messages =
    seq { for planet in planets ->
            sprintf """Hi there!
We thought you might be interested to know that:

The planet %s has:
%d moons!
An average orbital velocity of %Am/s!

And is also known as:
%s

Regards,

Astro
                """
                planet.Name
                (planet.``Orbited by`` |> Seq.length)
                planet.``Average Orbital Speed``
                (planet.``Also known as`` |> String.concat ", ") }

let combineAddressAndMessage (address, message) =
    Email {
        toAddress = address
        fromAddress = "[email protected]"
        body = message
    }

let myMessages =
    Seq.zip emailAddresses messages
    |> Seq.map combineAddressAndMessage

myMessages
|> Seq.map sendMessage
|> Async.Parallel
|> Async.RunSynchronously
|> fun _ -> printfn "Finished all sends!"

System.Console.ReadLine() |> ignore

Comments