Preface
Goal: A simple case example of concurrency using OCaml.
Concurrency using Actor model with OCaml
is possible,
but you need to understand a few things:
- Imperative in
OCaml
. - Mutable Variable in
OCaml
.
Before you continue,
do not forget the opam
environment configuration.
$ eval `opam config env`
7: Imperative
Most parts of real world programming,
done using side effects in imperative ways.
OCaml
also embrace this imperative paradigm.
Length of the Loop
Back to Basic
To make a loop, first we need to know the length of the list.
#use "mysongs.ml";;
open Format;;
let len = List.length songs;;
let () = printf "Record Length: %d \n%!" len;;
With the result as below line
:
$ ocaml 14-printf.ml
Record Length: 5
Loop
Consider a loop containing this simple side effect.
This print_endline
is the side effect.
#use "mysongs.ml";;
(* imperative *)
let len = List.length songs;;
let () = for i = 0 to len-1 do
print_endline (List.nth songs i).title
done;;
- .
With the result as below lines
of song titles:
$ ocaml 15-loop.ml
Cantaloupe Island
Let it Be
Knockin' on Heaven's Door
Emotion
The River
Iteration
We can also have the same result with iteration.
#use "mysongs.ml";;
let () = List.iter (
fun song -> print_endline song.title
) songs;;
With the result as below lines
of song titles:
$ ocaml 15-iter.ml
Cantaloupe Island
Let it Be
Knockin' on Heaven's Door
Emotion
The River
Filter and Map
With the same logic we can process any record structure, and get any desire result.
#use "mysongs.ml";;
let join_str my_tags : string =
"[" ^ (String.concat ", " my_tags) ^ "]";;
let all_tags my_songs: tl_tags list
= List.filter_map (
fun my_song -> my_song.tags
) my_songs;;
let () = List.iter (
fun tags -> print_endline (join_str tags)
) (all_tags songs);;
With the result as below list
:
$ ocaml 16-filter-map.ml
[60s, jazz]
[60s, rock]
[70s, rock]
[70s, pop]
Nested Iterator
The thing is, this List.iter
is also an imperative code.
#use "mysongs.ml";;
#use "topfind";;
open Option;;
let unwrap = function
| Some x -> x
| None -> [];;
(* imperative *)
let () = List.iter (
fun song ->
if is_some(song.tags)
then List.iter (
fun tag -> print_endline tag
) (unwrap song.tags)
) songs;;
With the result as below lines
of song tags:
$ ocaml 17-nested-iter.ml
60s
jazz
60s
rock
70s
rock
70s
pop
Although it is written with functional library, it does not mean it is not imperative.
We are going to need this for next concurrency example.
8: Mutable Variable
Now we have an issue, on how to gather all the tags into a list.
Luckily OCaml
also embrace mutable variable, using ref
.
So we do need to bother with recursion and stuff.
Header
#use "mysongs.ml";;
#use "topfind";;
open Option;;
(* miscellanous utilities *)
let join_str my_tags : string =
"[" ^ (String.concat ", " my_tags) ^ "]";;
let unwrap = function
| Some x -> x
| None -> [];;
- .
Custom Flatten Function
(* imperative *)
let tags : string array ref = ref [||];;
let flatten () = List.iter (
fun song ->
if is_some(song.tags)
then List.iter (
fun tag -> tags := (Array.append !tags [|tag|])
) (unwrap song.tags)
) songs;;
- .
This simply be done with ref
keyword.
Main Entry Point
(* main *)
let () =
flatten ();
print_endline (
join_str (Array.to_list !tags));;
- .
With the result as below list
:
$ ocaml 18-mutable.ml
[60s, jazz, 60s, rock, 70s, rock, 70s, pop]
We are also going to need this mutable for next concurrency example.
9: Concurrency with Channel
Concurrency in OCaml
, is surprisingly easy.
We have a show case to pass data between thread
.
Library Reference
The Skeleton
Now we should be ready for the real demo. This is only consist of one short file.
First we have a few modules dependencies.
#use "mysongs.ml";;
#use "topfind";;
#thread;;
open Option;;
open Format;;
open Event;;
Then we have these some functions below:
let join_str my_tags : string = …
let unwrap = …
let receiver () = …
let sender () = …
let () = …
- Miscellanous Helper Utility
- Sender
- Receiver
- Main (program entry point)
Miscellanous Utility
Helper Function
(* miscellanous utilities *)
let join_str my_tags : string =
"[" ^ (String.concat ", " my_tags) ^ "]";;
let unwrap = function
| Some x -> x
| None -> [];;
- .
Channel
Sender and Receiver (Producer and Consumer)
With Channel, we can have send and receive.
let chan = Event.new_channel();;
Then we can use later with receiver:
let rec_message () = (Event.sync (Event.receive chan ));;
Or with sender
fun tag -> Event.sync (
Event.send chan (Some(tag))
)
Or with sender as closing mark
Event.sync (Event.send chan None);;
Sender (Producer)
The sender
detail is as below code:
let sender () = List.iter (
fun song ->
if is_some(song.tags)
then List.iter (
fun tag -> Event.sync (
Event.send chan (Some(tag))
)
) (unwrap song.tags)
) songs;
Event.sync (Event.send chan None);;
Receiver (Consumer)
And the receiver
detail is as below code:
let rec_message () = (Event.sync (Event.receive chan ));;
let receiver () =
let message = ref (rec_message ()) in
while is_some(!message) do
printf "%s\n" (get !message);
message := (rec_message ());
done;;
Main (Entry Point)
Finally we can run the receive
thread separately.
let () =
printf "";;
Thread.create receiver ();;
sender ();;
With the result as below lines
of song tags:
$ ocaml 19-channel.ml
60s
jazz
60s
rock
70s
rock
70s
pop
10: Put All The String Together
But we want to gather all tags as a list right?
Consider use mutable
variable.
Receiver (Consumer)
And the receiver
detail is as below code:
let rec_message () = (Event.sync (
Event.receive chan
));;
let receiver () =
let tags : string array ref = ref [||] in
let message = ref (rec_message ()) in (
while is_some(!message) do
tags := (Array.append !tags [|(get !message)|]);
message := (rec_message ());
done;
print_endline (
join_str (Array.to_list !tags)
);
);;
If the message encounter none
,
this will print all tags instead.
Sender (Producer)
The sender
detail is as below code:
let sender () = List.iter (
fun song ->
if is_some(song.tags)
then List.iter (
fun tag -> Event.sync (
Event.send chan (Some(tag))
)
) (unwrap song.tags)
) songs;;
We can move out the closing mark to main entry point.
Main (Entry Point)
Finally we can run the receive
thread separately.
(* main *)
let () =
printf "";;
Thread.create receiver ();;
sender ();;
Event.sync (Event.send chan None);;
- .
With the result as below list
:
$ ocaml 20-channel.ml
[60s, jazz, 60s, rock, 70s, rock, 70s, pop]
Caveat
Beware of the OCaml
behaviour.
As shown in receiver
below:
let receiver () =
let tags : string array ref = ref [||] in
let message = ref (rec_message ()) in (
while is_some(!message) do
printf "%s\n" (get !message); (* print later *)
tags := (Array.append !tags [|(get !message)|]);
message := (rec_message ());
done;
print_endline (
join_str (Array.to_list !tags)
);
);;
- .
This will have undesired result as below.
The all tags list will be printed first
,
while each messages printed later.
$ ocaml 20-channel.ml
[60s, jazz, 60s, rock, 70s, rock, 70s, pop]
60s
jazz
60s
rock
70s
rock
70s
pop
This is the end of our Ocaml
journey in this article.
We shall meet again in other article.
What is Next 🤔?
Consider continue reading [ F# - Playing with Records - Part One ].