# Crystal - Playing with Records

Article Series

Playing with Records Related Articles.

Table of Content
Where to Discuss?

Local Group

### Preface

Goal: A practical case to collect unique record fields using Crystal.

With `Crystal`, I just feels like copy paste from `Ruby` code.

#### Source Examples

You can obtain source examples here:

### Common Use Case

Task: Get the unique tag string

#### Prepopulated Data

Songs and Poetry

``````record Song,
title : String = "",
tags : Array(String) = [] of String

def songs
[
(Song.new "Cantaloupe Island",
["60s", "jazz"]),
(Song.new "Let It Be",
["60s", "rock"]),
(Song.new "Knockin' on Heaven's Door",
["70s", "rock"]),
(Song.new "Emotion",
["70s", "pop"]),
(Song.new "The River"),
]
end``````

#### Crystal Solution

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

``````puts songs
.map { |song| song.tags }
.select { |tags| tags != [] of String }
.flatten.uniq``````

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

We are going to use `array` throught out this article.

#### Simple Array

Before building a `struct`, I begin with simple `array`.

``````tags: Array(String) = ["rock", "jazz", "rock", "pop", "pop"]

puts tags``````

It is easy to dump variable in `crystal` using `puts`. With the result similar as below `array`:

``````\$ crystal 01-tags.cr
["rock", "jazz", "rock", "pop", "pop"]``````

#### The Song Record

We can continue our journey to records. This record is actually just a macro.

``````record Song,
title : String = "",
tags : Array(String) = [] of String

song = Song.new "Cantaloupe Island",
["60s", "jazz"]

puts song``````

With the result similar as below record:

``````\$ crystal 02-song.cr
Song(@title="Cantaloupe Island", @tags=["60s", "jazz"])``````

#### Array of Song Record

Meet The Songs Array

From just karaoke, we can go pro with recording studio. From simple data, we can build a structure to solve our task.

``````record Song,
title : String = "",
tags : Array(String) = [] of String

songs: Array(Song) = [
(Song.new "Cantaloupe Island",
["60s", "jazz"]),
(Song.new "Let It Be",
["60s", "rock"]),
(Song.new "Knockin' on Heaven's Door",
["70s", "rock"]),
(Song.new "Emotion",
["70s", "pop"]),
(Song.new "The River"),
]

songs.each { |song| puts song }``````

It is more convenience to show the result using `each`, with the result similar as below sequential lines of `Song` type:

``````❯ crystal 03-songs.cr
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", @tags=[])``````

Instead of `{…}` syntax, we can utilize `do … end` block.

#### Maybe Null

To avoid null value, we simply use default value instead.

``````record Song,
title : String = "",
tags : Array(String) = [] of String``````

For empty element, we have to write the complete type such as `[] of String`.

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

``````record Song,
title : String = "",
tags : Array(String) = [] of String

def songs
[
(Song.new "Cantaloupe Island",
["60s", "jazz"]),
(Song.new "Let It Be",
["60s", "rock"]),
(Song.new "Knockin' on Heaven's Door",
["70s", "rock"]),
(Song.new "Emotion",
["70s", "pop"]),
(Song.new "The River"),
]
end``````

#### Using Songs Module

Now we can have a very short code.

``````require "./my-songs"
songs.each { |song| puts song }``````

With the result exactly the same as above array.

Map, Select, Compact, Flatten, Uniq

#### Extracting Fields

We can use `map` to extract `tags` field. And then `select` to filter out record with empty `tags`.

``````require "./my-songs"

tagss = songs
.map { |song| song.tags }
.select { |tags| tags != [] of String }

puts tagss``````

With the result similar as below `array` of `array`:

``````\$ crystal 05-filter.cr
[["60s", "jazz"], ["60s", "rock"], ["70s", "rock"], ["70s", "pop"]]``````

We can combine the `map` and `filter` with this `compact_map` method:

``````tagss = songs.compact_map do |song|
song.tags  if song.tags != [] of String
end ``````

With exactly the same result.

#### Flatten

There is already a standard library for flatten, so we can write as below:

``````puts songs
.map { |song| song.tags }
.select { |tags| tags != [] of String }
.flatten``````

#### Unique

In order to get to get `distinct` values, we can utilize `uniq` from standard library.

``````require "./my-songs"

puts songs
.map { |song| song.tags }
.select { |tags| tags != [] of String }
.flatten
.uniq``````

With the final result similar as below `array`:

``````\$ crystal 06-unique.cr
["60s", "jazz", "rock", "70s", "pop"]``````

Now the code is very clear. We can understand exactly what it does, by just reading the code.

### 4: Concurrency with Channel using Fiber

Concurrency in `crystal`, can be handled through `channel` using `fiber`. We are going to build show case to pass data between `thread`, using a custom flatten function.

#### Custom Flatten Function

In order to make a `Sender` demo, I need to make custom handmade flatten function. This is simply flatten using all `tag`s from all songs records, using nested `each` iterator.

``````require "./my-songs"

def flatten()
tags = [] of String

songs.each { |song|
if song.tags != [] of String
song.tags.each { |tag| tags << tag }
end
}

tags
end

puts flatten()``````

With the result similar as below `vector`:

``````\$ crystal 07-flatten.cr
["60s", "jazz", "60s", "rock", "70s", "rock", "70s", "pop"]``````

We are going to utilize this imperative fashioned as a show case, to pass data between `spawn` using `channel`. This way we can send the result through a `channel` using `fiber`, instead of directly pushing the result to `array`.

We should prepare two functions, one as a `sender`, and the other one as a `receiver`.

The first one sending each results through channel.

``````def sender(channel)
songs.each do |song|
if song.tags != [] of String
song.tags.each { |tag| channel.send(tag) }
end
end
channel.send(nil)
end``````

And then `receiver` from channel, all the result would be gathered as `array`.

``````def receiver(channel)
tags = [] of String

while true
if message != nil
tags << message.to_s
else
puts tags
break
end
end
end``````

#### Spawn Both Fiber

Pretty short right!

We require a channel with union type, so we can send a `nil` a stop signal.

``````require "./my-songs"
channel = Channel(String | Nil).new
spawn sender(channel)
With the result similar as below `array`:
``````\$ crystal 08-channel.cr