Preface
Goal: A practical case to collect unique record fields using Lua.
Lua capability is somehow limited and less attractive. This is a good challenge. And I finally find out that Lua can do more than I thought.
Since Lua
does not come natively with map
, filter
, and reduce
,
we should write code with imperative approach.
Lua
has a free functional libary, called fun
.
This library is good, but implement the code comes at price,
when it comes to convert <generator>
back to array.
But don’t worry, this case example is enough for you,
to code in functional fashioned.
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
songs = {
{ title = "Cantaloupe Island",
tags = {"60s", "jazz"} },
{ title = "Let It Be",
tags = {"60s", "rock"} },
{ title = "Knockin' on Heaven's Door",
tags = {"70s", "rock"} },
{ title = "Emotion",
tags = {"70s", "pop"} },
{ title = "The River" }
}
Lua Solution
The Answer
There might be many ways to do things in Lua
.
One of them is this oneliner as below:
import mysongs._
…
print(inspect(unique(flatten(songs))))
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
The array
data type is common in lua
.
Simple Array
Before building a records,
I begin with simple array
.
local inspect = require('inspect')
-- https://github.com/kikito/inspect.lua
tags = {"rock", "jazz", "rock", "pop", "pop"}
print(tags)
With the result similar as below:
$ lua 01-tags.lua
table: 0x5574a7a78650
Or better, using luajit:
$ luajit 01-tags.lua
{ "rock", "jazz", "rock", "pop", "pop" }
Inspect Library
To dump variable in lua
,
we can utilize inspect
library that is available at:
First we have to include the module
local inspect = require('inspect')
Then use the inspect
function in the code.
local inspect = require('inspect')
tags = {"rock", "jazz", "rock", "pop", "pop"}
print(inspect(tags))
With the result similar as below array
:
$ lua 01-tags.lua
{ "rock", "jazz", "rock", "pop", "pop" }
The Song Record
We can continue our journey to records.
Lua
has associative array
called table
,
as shown below:
local inspect = require('inspect')
song = { title = "Cantaloupe Island",
tags = {"60s", "jazz"} }
print(inspect(song))
With the result similar as below record:
$ lua 02-record.lua
{
tags = { "60s", "jazz" },
title = "Cantaloupe Island"
}
List of Song Record
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.
Consider make this data stucture more complex, as shown in below code:
local inspect = require('inspect')
songs = {
{ title = "Cantaloupe Island",
tags = {"60s", "jazz"} },
{ title = "Let It Be",
tags = {"60s", "rock"} },
{ title = "Knockin' on Heaven's Door",
tags = {"70s", "rock"} },
{ title = "Emotion",
tags = {"70s", "pop"} },
{ title = "The River" }
}
for _, song in pairs(songs) do
print(inspect(song))
end
_.
With the result similar as below array
:
$ lua 03-songs.lua
{
tags = { "60s", "jazz" },
title = "Cantaloupe Island"
}
{
tags = { "60s", "rock" },
title = "Let It Be"
}
{
tags = { "70s", "rock" },
title = "Knockin' on Heaven's Door"
}
{
tags = { "70s", "pop" },
title = "Emotion"
}
{
title = "The River"
}
The Fun Library
There is a functional library available at:
First we have to include the module
local fun = require('fun')
Now we can write the code in functional fashioned, as shown below:
local inspect = require('inspect')
local fun = require('fun')
…
-- compose
function print_inspect(x)
print(inspect(x))
end
fun.each(print_inspect, songs)
With exactly the same result as above code.
2: Separating Module
Since we need to reuse the songs record multiple times, it is a good idea to separate the record from logic.
Songs Module
The code can be shown as below:
songs = {
{ title = "Cantaloupe Island",
tags = {"60s", "jazz"} },
{ title = "Let It Be",
tags = {"60s", "rock"} },
{ title = "Knockin' on Heaven's Door",
tags = {"70s", "rock"} },
{ title = "Emotion",
tags = {"70s", "pop"} },
{ title = "The River" }
}
Using Songs Module
Now we can have a very short code.
ilocal inspect = require('inspect')
local fun = require('fun')
require "my-songs"
-- compose
function print_inspect(x)
print(inspect(x))
end
fun.each(print_inspect, songs)
With the result exactly the same as previous code.
3: Using Fun Library
Towards Functional
Imperative/Procedural
Consider this loop:
local inspect = require('inspect')
require "my-songs"
for _, song in ipairs(songs)
do
print(inspect(song.tags))
end
_.
With the result similar as below array
:
$ lua 05-map.lua
{ "60s", "jazz" }
{ "60s", "rock" }
{ "70s", "rock" }
{ "70s", "pop" }
nil
Importing Fun Library
There are other way to refer function using require
.
local inspect = require('inspect')
require 'fun' ()
require 'my-songs'
This way, we can just call each
, instead of fun.each
.
Functional Approach
We can rewrite using fun
library,
so we have functional fashioned code.
local inspect = require('inspect')
require 'fun' ()
require 'my-songs'
function print_inspect(x)
print(inspect(x))
end
local tags = map(
function (song) return song.tags end,
songs
)
each(print_inspect, tags)
With the result exactly the same as previous code.
Filter
We can go further stripping nil
value.
local inspect = require('inspect')
require 'fun' ()
require 'my-songs'
function print_inspect(x)
print(inspect(x))
end
function extract(songs)
return map(
function (song) return song.tags end, songs)
end
function clean(tags)
return filter(
function (tag) return tag end, tags)
end
each(print_inspect, clean(extract(songs)))
With the result similar as below array
:
$ lua 06-filter.lua
{ "60s", "jazz" }
{ "60s", "rock" }
{ "70s", "rock" }
{ "70s", "pop" }
So far so good. We are going to face issue, sooner or later.
4: Imperative Approach
Flatten and Unique
We can actually, discard the whole functional library above, and making our own custom script.
Flatten using Loop
All at Once
Just like anty other functional programming,
we can have a lot of fun with map
.
Then get the default option value with pattern matching
.
local inspect = require('inspect')
require 'my-songs'
function flatten(songs)
local tags = {}
for _, song in pairs(songs)
do
local song_tags = song.tags
if type(song_tags) == 'table' then
for _, tag in pairs(song_tags)
do
tags[#tags + 1] = tag
end
end
end
return tags
end
print(inspect(flatten(songs)))
With the result similar as below array
:
$ lua 07-flatten.lua
{ "60s", "jazz", "60s", "rock", "70s", "rock", "70s", "pop" }
Unique Elements
I found the right script from the internet:
Now I can apply to my code.
local inspect = require('inspect')
require 'my-songs'
function flatten(songs)
…
end
function unique(tags)
local hash, res = {}, {}
for _,tag in pairs(tags) do
if (not hash[tag]) then
res[#res+1] = tag
hash[tag] = true
end
end
return res
end
print(inspect(unique(flatten(songs))))
_.
With the result similar as below array
:
$ lua 08-unique.lua
{ "60s", "jazz", "rock", "70s", "pop" }
Succeed.
It is done. Task finished. Mission accomplished.
What is Next 🤔?
We should be ready for our next topic, concurrency case example.
Consider continue reading [ Lua - Playing with Records - Part Two ].