Preface
Goal: A practical case to collect unique record fields using Scala.
Scala combine to paradigm, OOP
and FP
.
For quick and dirty solution, I skip the OOP
parts.
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
package mysongs
case class Song(
title: String,
tags: Option[List[String]]
)
object MySongs {
val songs: List[Song] = List(
Song( "Cantaloupe Island",
Some(List("60s", "jazz")) ),
Song( "Let It Be",
Some(List("60s", "rock")) ),
Song( "Knockin' on Heaven's Door",
Some(List("70s", "rock")) ),
Song( "Emotion",
Some(List("70s", "pop")) ),
Song( "The River", None )
)
}
Scala Solution
The Answer
There might be many ways to do things in Scala
.
One of them is this oneliner as below:
import mysongs._
object T08 extends App {
def tagsList(songs: List[Song])
: List[Option[List[String]]]
= songs map (song => song.tags)
println(
tagsList(MySongs.songs)
.flatten.flatten.distinct
)
}
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
I do not know what data type is common in scala
,
so I use list
.
Simple List
Before building a records,
I begin with simple list
.
object T01 extends App {
val tags: List[String] =
List("rock", "jazz", "rock", "pop", "pop")
println(tags)
}
It is easy to dump variable in scala
using println
.
With the result similar as below list
:
$ scala T01Tags.scala
List(rock, jazz, rock, pop, pop)
You might notice the difference between the object name T01
.
And the filename T01Tags.scala
.
The Song Record
We can continue our journey to records.
Instead of class
, we can utilize case class
,
as shown below:
case class Song(
title: String,
tags: List[String]
)
object T02 extends App {
def song(): Song = Song(
"Cantaloupe Island",
List("60s", "jazz")
)
println(song())
}
With the result similar as below record:
$ scala T02Record.scala
Song(Cantaloupe Island,List(60s, jazz))
Instead of val song
,
we can also utilize function with def song()
.
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,
with adding nullability option
as shown in below code:
case class Song(
title: String,
tags: Option[List[String]]
)
object T03 extends App {
val songs: List[Song] = List(
Song( "Cantaloupe Island",
Some(List("60s", "jazz")) ),
Song( "Let It Be",
Some(List("60s", "rock")) ),
Song( "Knockin' on Heaven's Door",
Some(List("70s", "rock")) ),
Song( "Emotion",
Some(List("70s", "pop")) ),
Song( "The River", None )
)
songs map (song => println(song))
}
With the result similar as below sequential lines of Song
type:
$ scala T03Songs.scala
Song(Cantaloupe Island,Some(List(60s, jazz)))
Song(Let It Be,Some(List(60s, rock)))
Song(Knockin' on Heaven's Door,Some(List(70s, rock)))
Song(Emotion,Some(List(70s, pop)))
Song(The River,None)
Instead of just println
,
I iterate the list
using songs map
.
Wrapping in Options for Nullability
I also add option
so tags
can accept None
value,
instead of just empty list.
tags: Option[List[String]]
This is why we have this Some […]
for each tags
.
We need, a not too simple, case example.
2: Separating Package
Since we need to reuse the songs record multiple times, it is a good idea to separate the record from logic.
Songs Package
The code can be shown as below:
package mysongs
case class Song(
title: String,
tags: Option[List[String]]
)
object MySongs {
val songs: List[Song] = List(
Song( "Cantaloupe Island",
Some(List("60s", "jazz")) ),
Song( "Let It Be",
Some(List("60s", "rock")) ),
Song( "Knockin' on Heaven's Door",
Some(List("70s", "rock")) ),
Song( "Emotion",
Some(List("70s", "pop")) ),
Song( "The River", None )
)
}
You can spot the ide on how scala
handle null value,
for the last record.
Compile Package
Before using package, we need to compile first.
$ scalac MySongs.scala
Using Songs Package
Now we can have a very short code.
import mysongs._
object T04 extends App {
MySongs.songs map (song => println(song))
}
With the result exactly the same as previous code.
3: Finishing The Task
Map, Match, Flatten and Unique
Extracting Field Using Map
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
.
import mysongs._
object T05 extends App {
val tagsList = MySongs.songs map (
song => song.tags match {
case Some(tags) => tags
case None => List()
}
)
tagsList map (tags => println(tags))
}
With the result similar as below vector:
$ scala T05Extract.scala
List(60s, jazz)
List(60s, rock)
List(70s, rock)
List(70s, pop)
List()
The last nil
exist,
because we do not have any tags
for the last song.
Regular Function
Instead of using val
to create variable,
we can utilize def
with simple songs
parameter argument.
This means we can chain the result directly to map
,
without storing first.
import mysongs._
object T06 extends App {
def tagsList(songs: List[Song])
: List[List[String]] = songs map (
song => song.tags match {
case Some(tags) => tags
case None => List()
}
)
tagsList(MySongs.songs) map (
tags => println(tags)
)
}
With the result exactly the same as previous code.
Flatten
Flatten in scala
can unwrap
the nullability option
,
and also flatten list
.
Since we have list
of option list
,
we need to flatten this nested list
into just a simple list
.
We can simply apply standard flatten
twice to the nested list
.
import mysongs._
object T07 extends App {
def tagsList(songs: List[Song])
: List[Option[List[String]]]
= songs map (song => song.tags)
val myTagsList = tagsList(MySongs.songs)
println(myTagsList.flatten)
println(myTagsList.flatten.flatten)
}
With the result similar as below vector:
$ scala T07Flatten.scala
List(List(60s, jazz), List(60s, rock), List(70s, rock), List(70s, pop))
List(60s, jazz, 60s, rock, 70s, rock, 70s, pop)
You can spot, how the code significantly simplified.
Unique (Distinct)
Scala has enough library so we can avoid making custom algorithm.
We can just complete the task with applying,
standard distinct
to the list
.
import mysongs._
object T08 extends App {
def tagsList(songs: List[Song])
: List[Option[List[String]]]
= songs map (song => song.tags)
println(
tagsList(MySongs.songs)
.flatten.flatten.distinct
)
}
With the result similar as below:
$ scala T08Unique.scala
List(60s, jazz, rock, 70s, pop)
That is all.
What is Next 🤔?
Scala
is powerful,
but I guess my case example is not complex enough.
I need to explore scala
more, and get more interesting experience.
Consider continue reading [ Scala - Playing with Records - Part Two ].