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

OCaml: Imperative using Nested Iterator

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.

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

OCaml: Mutable Variable

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 () = …
  1. Miscellanous Helper Utility
  2. Sender
  3. Receiver
  4. 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);;

OCaml: Concurrency Actor: Sender

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;;

OCaml: Concurrency Actor: Receiver

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]

OCaml: Concurrency with Channel

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