Article Series

This article series discuss more than 30 different programming languages. Please read overview before you read any of the details.

Playing with Records Related Articles.

Where to Discuss?

Local Group

Preface

Goal: A simple case example of concurrency using F#.

I was having a hard time finding concurrency, with actor model example in F#, until I find this article:

I really like this article in that link above, and I try to apply each step to suit my need.

In fact this article below is just a slight modification, of above link.


7: Concurrency with Mailbox

Reference Reading

Custom Flatten Function

In order to make a Sender demo, I need to make custom handmade flatten function. This nested loop below, intentionally written in imperative fashioned.

#load "MySongs.fsx"

open System
open MySongs

let unwrap myOption: MyTags = 
  match myOption with | Some x -> x | None -> []

let flatten songs: unit = do
  for (song: MySong) in songs do
    if (song.Tags: Option<MyTags>).IsSome then
      for (tag: string) in (song.Tags |> unwrap) do
        tag |> Console.WriteLine

[<EntryPoint>]
songs |> flatten

With the result similar as below lines:

$ dotnet fsi 18-flatten.fsx
60s
jazz
60s
rock
70s
rock
70s
pop

Simple Mailbox

We should be ready for the real demo. Following the original article, this is only consist of one short file.

First we need to create the mailbox agent:

let myAgent =
  MailboxProcessor.Start(fun inbox ->
    async {
      while true do
        let! message = inbox.Receive()
        match message with
        | Some tag -> printfn "Tag: '%s'" tag
        | None     -> printfn "None"
    })

Then use agent in previous flatten function:

let unwrap myOption: MyTags = 
   match myOption with | Some x -> x | None -> []

let flatten songs: unit = do
  for (song: MySong) in songs do
    if (song.Tags: Option<MyTags>).IsSome then
      for (tag: string) in (song.Tags |> unwrap) do
        Some(tag) |> myAgent.Post

This way, we can call in program entry point

#load "MySongs.fsx"

open System
open MySongs

let myAgent = …

let unwrap myOption: MyTags = …

let flatten songs: unit = …

[<EntryPoint>]
songs |> flatten
None  |> myAgent.Post

With the result similar as below lines:

$ dotnet fsi 19-mailbox.fsx
Tag: '60s'
Tag: 'jazz'
Tag: '60s'
Tag: 'rock'
Tag: '70s'
Tag: 'rock'
Tag: '70s'
Tag: 'pop'
None

.NET F#: Concurrency with Mailbox

Reply Mailbox, with Loop

Following the original article, we are going to use loop.

There is a lot of enhancement from previous code. This is almost exactly the same from the original article.

type Message =  string * AsyncReplyChannel<string>

let joinStr tags: string =
  "[" + (tags |> String.concat ", ") + "]"

let replyAgent =
  MailboxProcessor<Message>.Start(fun inbox ->
    let rec loop tags =
      async {
        let! (tag, replyChannel) = inbox.Receive()
        let newTags = tags @ [tag]
        replyChannel.Reply
          (String.Format
            ("Received tags: {0}", newTags |> joinStr))
        do! loop(newTags)
      }
    loop([])
  )

Then use agent in previous flatten function, and call in program entry point

let unwrap myOption: MyTags = 
   match myOption with | Some x -> x | None -> []

let flatten songs: unit = do
  for (song: MySong) in songs do
    if (song.Tags: Option<MyTags>).IsSome then
      for (tag: string) in (song.Tags |> unwrap) do
        replyAgent.PostAndReply(fun rc -> tag, rc)
          |> Console.WriteLine

[<EntryPoint>]
songs |> flatten

With the result similar as below lines:

$ dotnet fsi 20-reply.fsx
Received tags: [60s]
Received tags: [60s, jazz]
Received tags: [60s, jazz, 60s]
Received tags: [60s, jazz, 60s, rock]
Received tags: [60s, jazz, 60s, rock, 70s]
Received tags: [60s, jazz, 60s, rock, 70s, rock]
Received tags: [60s, jazz, 60s, rock, 70s, rock, 70s]
Received tags: [60s, jazz, 60s, rock, 70s, rock, 70s, pop]

Just remember, that this script is based on the article below:

If there is anything that you don’t like. Maybe it is just my script. And my lack of F# script.

Simple Mailbox, with Loop

Combined from the two example above, we can create our custom script to utilize mailbox, within a recursive loop.

Again we need to create the mailbox agent. Since match is an expression rather than computation expression, we can’t put do! loop(tags @ [tag]) within match. We will find a workaround using if (message.IsSome).

let myAgent =
  MailboxProcessor.Start(fun inbox ->
    let rec loop tags =
      async {
        let! message = inbox.Receive()
        let result =
          match message with
          | Some tag -> tags @ [tag]
          | None     -> []

        if (message.IsSome)
        then do! loop(result)
        else tags |> joinStr |> printfn "%s"
      }
    loop [] 
  )

Again, use agent in previous flatten function, and call in program entry point

let flatten songs: unit = do
  for (song: MySong) in songs do
    if (song.Tags: Option<MyTags>).IsSome then
      for (tag: string) in (song.Tags |> unwrap) do
        Some(tag) |> myAgent.Post

[<EntryPoint>]
songs |> flatten
None  |> myAgent.Post

With the result similar as below lines:

$ dotnet fsi 21-mailbox.fsx
[60s, jazz, 60s, rock, 70s, rock, 70s, pop]

.NET F#: Concurrency with Mailbox

This is the end of our F# journey in this article. We shall meet again in other article.


What is Next 🤔?

Consider continue reading [ Erlang - Playing with Records ].