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 practical case to collect unique record fields using Elixir.

After using erlang for a while, it make sense to step into elixir. It turn out that elixir is easy and also fun to work with. It only take about eight hours to finish my task, starting from blank knowledge. This is compared with purescript that took me for almost two days. I’m so surprised, and asking to myself, why don’t I learned this language years ago?

Reference Reading

Source Examples

You can obtain source examples here:


Common Use Case

Task: Get the unique tag string

Please read overview for more detail.

Prepopulated Data

Songs and Poetry

defmodule Song do
  defstruct title: "", tags: []
end

defmodule Songs do
  def songs() do
    [
      %Song{ title: "Cantaloupe Island",
             tags:  ["60s", "jazz"] },
      %Song{ title: "Let It Be",
             tags:  ["60s", "rock"] },
      %Song{ title: "Knockin' on Heaven's Door",
             tags:  ["70s", "rock"] },
      %Song{ title: "Emotion",
             tags:  ["70s", "pop"] },
      %Song{ title: "The River"}
    ]
  end
end

Elixir Solution

The Answer

There might be many ways to do things in Elixir. One of them is this oneliner as below:

import Songs

IO.inspect songs()
  |> Enum.map(& &1.tags)
  |> :lists.append
  |> Enum.uniq

The syntax, makes me cry. This is simply concise, an easy to understand.

Of course there are hidden details beneath each functions. I will discuss about this step by step.

Enough with introduction, at this point we should go straight to coding.

Environment

No need any special setup. Just run and voila..!


1: Data Structure

Simple List, Simple Struct, and Songs

Simple List

Before building a struct, I begin with simple list.

tags = ["rock", "jazz", "rock", "pop", "pop"]

IO.inspect tags

With the result similar as below list:

$ elixir 01-list.exs
["rock", "jazz", "rock", "pop", "pop"]

Elixir: Simple List of Tags

It is easy to dump variable in elixir using inspect.

The Song Struct

We can continue our journey to records. Everything in elixir is module. Even the struct, defined in a module as shown below:

defmodule Song do
  defstruct title: "", tags: []
end

defmodule Run do
  song = %Song{ 
    title: "Cantaloupe Island",
    tags:  ["60s", "jazz"]
  }

  IO.inspect song
end

With the result similar as below list:

$ elixir 02-record.exs
%Song{tags: ["60s", "jazz"], title: "Cantaloupe Island"}

List of Song Structs

Meet The Songs List

From just karaoke, we can go pro with recording studio.

From simple data, we can build a structure to solve our task.

defmodule Song do
  defstruct title: "", tags: []
end

defmodule Run do
  songs = [
    %Song{ title: "Cantaloupe Island",
           tags:  ["60s", "jazz"] },
    %Song{ title: "Let It Be",
           tags:  ["60s", "rock"] },
    %Song{ title: "Knockin' on Heaven's Door",
           tags:  ["70s", "rock"] },
    %Song{ title: "Emotion",
           tags:  ["70s", "pop"] },
    %Song{ title: "The River"}
  ]

  IO.inspect songs
end

With the result similar as below inspect dump:

$ elixir 03-songs.exs
[
  %Song{tags: ["60s", "jazz"], title: "Cantaloupe Island"},
  %Song{tags: ["60s", "rock"], title: "Let It Be"},
  %Song{tags: ["70s", "rock"], title: "Knockin' on Heaven's Door"},
  %Song{tags: ["70s", "pop"], title: "Emotion"},
  %Song{tags: [], title: "The River"}
]

2: Separating Module

Since we need to reuse the songs record multiple times, it is a good idea to separate the data from logic.

Songs Module

The module file has .ex extension, different from elixir code with .exs extension.

The code can be shown as below:

defmodule Song do
  defstruct title: "", tags: []
end

defmodule Songs do
  def songs() do
    [
      %Song{ title: "Cantaloupe Island",
             tags:  ["60s", "jazz"] },
      %Song{ title: "Let It Be",
             tags:  ["60s", "rock"] },
      %Song{ title: "Knockin' on Heaven's Door",
             tags:  ["70s", "rock"] },
      %Song{ title: "Emotion",
             tags:  ["70s", "pop"] },
      %Song{ title: "The River"}
    ]
  end
end

In order to use the songs module, we need to compile the module first.

$ elixirc songs.ex

You should see two different generated files.

$ ls *.beam
Elixir.Song.beam  Elixir.Songs.beam
  • .

Elixir: The Songs Module Containing List of Struct

Using Songs Module

Now we can have a very short code.

defmodule Run do
  import Songs
  IO.inspect songs()
end

With the result exactly the same as previous code.

Elixir: Using Songs Module


3: Finishing The Task

Map, Flatten and Unique

Extracting Field from Record

All at Once

Elixir has enough library so we can avoid making custom algorithm. We can just complete the task with this simple code:

defmodule Run do
  import Songs
  tags = for song <- songs(), do: song.tags
  IO.inspect Enum.uniq(:lists.append(tags))
end

With the result similar as below list:

$ elixir 05-unique.exs
["60s", "jazz", "rock", "70s", "pop"]

We have this:

  1. Map using for do.
  2. :lists.Append from Erlang's standard library.
  3. Enum.uniq from Elixir's library.

Using Enum.Map

Elixir also comes with Enum.Map. This is very convenience for functional programmer. Now we can rewrite above code to below.

defmodule Run do
  import Songs
  tags =  Enum.map(songs(), fn(song) -> song.tags end)
  IO.inspect tags |> :lists.append |> Enum.uniq
end

It is also cooler to use pipe |> to avoid too many parenthesis.

A kind guy from community named Adi Pur hint me, about pipe and capture operator. This is a very useful information.

Using Capture Operator

We can rewrite the code above into this below

defmodule Run do
  import Songs
  tags =  Enum.map(& &1.tags)
  IO.inspect tags |> :lists.append |> Enum.uniq
end

Thank you to Adi Pur. Finally we have this cool oneliner fashioned as shown below:

import Songs

songs()
  |> Enum.map(& &1.tags)
  |> :lists.append
  |> Enum.uniq
  |> IO.inspect

Elixir: Flatten and Unique (Distinct)


4: Concurrency with Process

Instead of using coroutine, Elixir is using Erlang Process, using Actor Model pattern.

Reference

The Skeleton

We should be ready for the real demo. This is only consist of one short file.

defmodule MyProcess do
  def walk, do 
  def flatten(pid), do 
end

defmodule Run, do 
  1. flatten()

    • a Sender that send pid to spawned pid,
  2. walk()

    • a Receiver that receive from spawned pid.
  3. Run()

    • Program entry point

Sender

The flatten function is an imperative style, of pushing new tag to process.

  def flatten(pid) do
    import Songs

    for song <- songs() do
      for tag <- song.tags do
        send pid, {:tag, tag}
      end
    end
  end

We would gather all process later to form a new tags list.

Receiver

The walk is a recursive function, consist of two forms with different arity:

  • walk/0, and
  • walk/1.
  def walk, do: walk([])

  def walk(tags) do
    receive do
      {:tag, tag} ->
        walk(tags++[tag])
      {:quit} ->
        tags |> Enum.uniq |> IO.inspect
    end
  end

Elixir: Concurreny with Process: Sender and Receiver

Running Both Process

Pretty short right!

Consider gather both function in run entry point.

defmodule Run do
  pid = spawn(MyProcess, :walk, [])

  MyProcess.flatten(pid)
  send pid, {:quit}
end

With the result as below array:

❯ elixir 08-process.exs
["60s", "jazz", "rock", "70s", "pop"]

Elixir: Concurreny with Process: Sender and Receiver

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


What is Next 🤔?

Consider continue reading [ Clojure - Playing with Records - Part One ].