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
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]
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 ].