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
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
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
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
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
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.
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
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
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
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") )
))
Module in Relative Path
We can call module in relative path.
(require "my-songs.rkt")
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)")
What is Next 🤔?
We have alternative way to extract the record structure.
Consider continue reading [ Racket - Playing with Records - Part Two ].