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

Scala: Simple List of Tags

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 )
  )
}

Scala: The Songs Package Containing List of Record

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()

Scala: Extracting Field Using Map and Match

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)

Scala: Flatten Twice: Option and Nested List

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)

Scala: Flatten and Unique (Distinct)

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