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.

Preface

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

Racket is very similar with Guile. But there is more in Racket than Guile. Racket is more like a language to make a programming language. So we can code in entirely different syntax, such as honu, typed racket and rash.

However, I’m still a racket beginner. And all I have is this basic scheme like syntax. I will pour this pure language in this article. And maybe examine later in other time.

I wish that I find a community that I can discuss more racket. And leverage my knowledge.

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

#lang racket
(provide songs)

(define songs (list
 #hash( ("title" . "Cantaloupe Island")
        ("tags" . ("60s" "jazz")) )
 #hash( ("title" . "Let It Be")
        ("tags" . ("60s" "rock")) )
 #hash( ("title" . "Knockin' on Heaven's Door")
        ("tags" . ("70s" "rock")) )
 #hash( ("title" . "Emotion")
        ("tags" . ("70s" "pop")) )
 #hash( ("title" . "The River") )
))

Racket Solution

The Answer

I use conventional building blocks, loop and conditional.

#lang racket
(require "my-songs.rkt")

(define (extract-songs songs)
  (filter-map (λ (song)
    (and (hash-has-key? song "tags")
    (hash-ref song "tags"))
  ) songs))

(displayln (string-join
  (remove-duplicates
  (flatten
  (extract-songs songs))) ":"))

We are going to use λ, just like any cool scheme kids.

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

Environment

There is a cool IDE called DrRacket. I’m using DrRacket, while learning racket. but for blogging purpose I’m using Vim, so I can have smaller figure.

For racket itself. No need any special setup. Just run and voila..!

Indentation

Forgive my indentation. I’m coming from curly braces world.


1: Simple List

Although we can use list or quote or just braces. I decide tu use list.

Dump Output

Consider begin with simple list.

#lang racket

(define tags (list
  "rock" "jazz" "rock" "pop" "pop"))

(define tags-str
  (string-join tags ":"))
(display tags-str)
(newline)

(format "Head: ~s" (car tags))
(format "Tail: ~a" (cdr tags))

(for-each display tags)
(newline)

You can print in guile using either display or format. With the result similar as below list:

❯ racket 01-tags-a.rkt
rock:jazz:rock:pop:pop
"Head: \"rock\""
"Tail: (jazz rock pop pop)"
rockjazzrockpoppop

Racket: A very simple list

We are going to utilize string-join through out this article.

Iterate List

You can also display inside for-each, which is useful for debugging.

#lang racket

(define tags '(
  "rock" "jazz" "rock" "pop" "pop"))

(for-each (lambda tag
  (display (car tag))
  (display (cdr tag))
) tags)
(newline)

(for-each (lambda (tag)
  (display tag)
) tags)
(newline)

The result is similar as below list:

❯ racket 01-tags-b.rkt
rock()jazz()rock()pop()pop()
rockjazzrockpoppop

Racket: For Each

Iterate List

Instead of car and cdr, racket has other build-in subroutine as well.

#lang racket

(define tags (list
  "rock" "jazz" "rock" "pop" "pop"))

(format "Head: ~s" (list-ref  tags 0))
(format "Tail: ~a" (list-tail tags 1))

(format "Head: ~s" (first tags))
(format "Tail: ~a" (rest  tags))

(define tags-str
   (foldr
     (lambda (acc tag)
       (string-append acc ":" tag))
     (first tags)
     (rest  tags)))

(displayln tags-str)

The result is similar as below list:

❯ racket 01-tags-c.rkt
"Head: \"rock\""
"Tail: (jazz rock pop pop)"
"Head: \"rock\""
"Tail: (jazz rock pop pop)"
jazz:rock:pop:pop:rock

Racket: For Each


2: Building Data Structure

Extract structure and display as string.

Before we dive deep into any algorithm, we need examine data structure first.

Simple List

Consider back to our simple list, as our foundation.

(define tags (list
  "rock" "jazz" "rock" "pop" "pop"))

(displayln (string-join tags ":"))

The result is similar as below list:

❯ racket 02-str-a.rkt
rock:jazz:rock:pop:pop

Racket Structure: Simple List

List of Quote

Consider enhance our example as a kind of array of array structure. There many alternative in Racket, the choice is up to you. This time, we can pick list of quote as an example. We can flatten either using srfi or built in subroutine.

Using (concatenate tags), which is available in (srfi srfi-1) module.

(require srfi/1)

(define tags (list
  '("60s" "jazz")
  '("60s" "rock")
  '("70s" "rock")
  '("70s" "pop")
))

(define tagss (concatenate tags))
(displayln tagss)

(define tags-str
  (string-join tagss ":"))
(displayln tags-str)

The result is similar as below list:

❯ racket 02-str-b1.rkt
(60s jazz 60s rock 70s rock 70s pop)
60s:jazz:60s:rock:70s:rock:70s:pop

Racket Structure: List of Quote

Alternatively (flatten tags).

(define tags (list
  '("60s" "jazz")
  '("60s" "rock")
  '("70s" "rock")
  '("70s" "pop")
))

(define tagss (flatten tags))
(displayln tagss)

(displayln (string-join tagss ":"))

With exactly the same result.

Racket Structure: List of Quote

Mimic Hash (or Dictionary)

Using Constructor

However, we can construct hash like structure, all with tags key. We can extract using map, and get new structure.

(define tags (list
  (cons "tags" (list "60s" "jazz"))
  (cons "tags" (list "60s" "rock"))
  (cons "tags" (list "70s" "rock"))
  (cons "tags" (list "70s" "pop"))
))

(define tagss
  (map (λ (tagss)
      (list-tail tagss 1))
    tags)
)

(display tagss)(newline)

(displayln (string-join
  (flatten tagss) ":"))

The result is similar as below list:

❯ racket 02-str-c.rkt
((60s jazz) (60s rock) (70s rock) (70s pop))
60s:jazz:60s:rock:70s:rock:70s:pop

Racket Structure: Mimic Hash or Dictionary using Constructor

Real Hash (or Dictionary)

The Real One

Racket support hash. So we can make complex structure.

(define tags (list
  #hash( ("tags" . ("60s" "jazz")) )
  #hash( ("tags" . ("60s" "rock")) )
  #hash( ("tags" . ("70s" "rock")) )
  #hash( ("tags" . ("70s" "pop"))  )
))

(define tagss
  (map (λ (tagss)
      (hash-ref tagss "tags"))
    tags)
)

(display tagss)(newline)

(displayln (string-join
  (flatten tagss) ":"))

And examine how to access the array inside the hash. Now we can apply (hash-ref tagss "tags").

(define tagss
  (map (λ (tagss)
      (hash-ref tagss "tags"))
    tags)
)

The result is similar as below list:

❯ racket 02-str-d.rkt
((60s jazz) (60s rock) (70s rock) (70s pop))
60s:jazz:60s:rock:70s:rock:70s:pop

Racket Structure: Real Hash or Dictionary Structure

Notes on Pairs

Below are equivalent

(cons "a" "b")

and

(cons "a" . ("b"))

3: Separating Module

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

Data Structure Using Dictionary

Simple Record

Consider try another structure, a hash to store just one record.

(define song #hash(
  ("title" . "Cantaloupe Island")
  ("tags" . ("60s" "jazz"))
))

(displayln song)
(displayln (hash-ref song "tags"))
(displayln (hash-has-key? song "tags"))

Now, examine the result:

❯ racket 03-record.rkt
#hash((tags . (60s jazz)) (title . Cantaloupe Island))
(60s jazz)
#t

Racket Structure: One Record

The Songs Structure

Multiple Records

We can continue our journey to records using list of hash. No need any complex structure.

(define songs (list
 #hash( ("title" . "Cantaloupe Island")
        ("tags" . ("60s" "jazz")) )
 #hash( ("title" . "Let It Be")
        ("tags" . ("60s" "rock")) )
 #hash( ("title" . "Knockin' on Heaven's Door")
        ("tags" . ("70s" "rock")) )
 #hash( ("title" . "Emotion")
        ("tags" . ("70s" "pop")) )
 #hash( ("title" . "The River") )
))

(filter-map (λ (song)
  (and (hash-has-key? song "tags")
  (format "~a" (hash-ref song "tags")))
) songs)

The result of the script will be similar as below record:

❯ racket 03-songs.rkt
'("(60s jazz)" "(60s rock)" "(70s rock)" "(70s pop)")

The last one do not have tags key, thus the result of hash-has-key? is false #f.

The Songs Module

Notice how easy the transition. All we need to add just the (provide ...) keyword.

#lang racket
(provide songs)

(define songs (list
 #hash( ("title" . "Cantaloupe Island")
        ("tags" . ("60s" "jazz")) )
 #hash( ("title" . "Let It Be")
        ("tags" . ("60s" "rock")) )
 #hash( ("title" . "Knockin' on Heaven's Door")
        ("tags" . ("70s" "rock")) )
 #hash( ("title" . "Emotion")
        ("tags" . ("70s" "pop")) )
 #hash( ("title" . "The River") )
))

Racket: The Songs Module Containing Array of Records

Module in Relative Path

We can call module in relative path.

(require "my-songs.rkt")

Racket: Using Songs Module

Using Songs Module

Now we can have a shorter code as shown below.

#lang racket
(require "my-songs.rkt")

(filter-map (λ (song)
  (and (hash-has-key? song "tags")
  (format "~a" (hash-ref song "tags")))
) songs)

With the result exactly the same as previous code.

❯ racket 04-module.rkt
'("(60s jazz)" "(60s rock)" "(70s rock)" "(70s pop)")

Racket: Using Songs Module


What is Next 🤔?

We have alternative way to extract the record structure.

Consider continue reading [ Racket - Playing with Records - Part Two ].