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: Continue Part One.

I would like to continue with concurrency, but the thing is the previously example code, is not enough to cover most part of clojure. We cannot just jump to this intermediate topic, with lack of basic code introduction, So I dedicate this article to cover basic clojure trick.


5: Alternate Approach

Since our introduction to clojure above is too short, consider to makes it more complete alternate flatten written in imperative fashioned.

Then we are going to continue with recursive example, and also, with head and tail x:xs pattern, until finally conclude with our songs task with alternate solution.

Clojure Project File

We need to a more setup, so that all script in this artcile could be handled.

  :profiles {…
             :t10-flatten      {:main t10-flatten}
             :t11-tags-exclude {:main t11-tags-exclude}
             :t12-tags-unique  {:main t12-tags-unique}
             :t13-recursion    {:main t13-recursion}
             :t13-songs-unique {:main t13-songs-unique}
          }

Custom Flatten Using doseq

Not every real life code is functional. Some case such as concurrency are naturally comes from side effect. Thus we need to prepare to handle this, without map, filter, or reduce.

We can write our custom flatten using doseq. This doseq is very similar with for loop.

(ns t10-flatten (:gen-class) (:use my-songs))

(defn -main [& args]
  (doseq [song songs]
    (if (not (nil? (:tags song)))
      (doseq [tag (:tags song)]
        (println tag)
      ))))

With the result similar as below string:

$ lein with-profile t10-flatten run
60s
jazz
60s
rock
70s
rock
70s
pop

Imperative Flatten Using Atom

Or better, we can save the value using atom. This flatten function is just an imperative style, of pushing new tag to array.

(ns t10-flatten (:gen-class) (:use my-songs))

(defn get-tags [songs]
  (def tags (atom []))
  (doseq [song songs]
    (if (not (nil? (:tags song)))
      (doseq [tag (:tags song)]
        (swap! tags conj tag))))
  (deref tags))

(defn -main [& args]
  (println (get-tags songs)))

With the result similar as below vector:

$ lein with-profile t10-flatten run
[60s jazz 60s rock 70s rock 70s pop]

Clojure: Imperative Flatten Using Atom

We would use this function as an example concurrency code later.

Simple Recursion Example

Atom is not the only solution, we can write this in functional fashioned using recursive. Consider this simple recursion example before we apply in concurency.

(ns t13-recursion (:gen-class))

(defn collect-random
  ([]
    (collect-random []))
  ([numbers]
    (if (<= (count numbers) 10)
      (collect-random (conj numbers (rand-int 100)))
      (println numbers))
  ))

(defn -main [& args] (collect-random))

With the result similar as below vector:

$ lein with-profile t13-recursion run
[91 72 26 58 95 94 49 16 97 11 93]

As you see in above recursion, we have multi-arity pattern. This is a very common pattern.

Head and Tail Pattern Matching

Why making things complex?

My intention is writing a custom pattern matching example, so later I can rewrite other algorithm based on this example. You can read the detail on the first article. The record overview.

Consider going back to simple data. We need to discuss about solving unique list.

The original x:xs pattern matching from haskell shown as below:

exclude :: String -> ([String] -> [String])
exclude value = filter((/=) value)

unique :: [String] -> [String]
unique [] = []
unique (head:tail) = head:unique(exclude head tail)

tags :: [String]
tags = ["rock", "jazz", "rock", "pop", "pop"]

main = print (unique tags)

With the result of

["rock","jazz","pop"]

This x:xs pattern is made of two functions:

  1. Exclude
  2. Unique

Loop for Exclude

Based on

exclude :: String -> ([String] -> [String])
exclude value = filter((/=) value

We can rewrite twoliners above to this below:

(ns t11-tags-exclude (:gen-class))
(def tags ["rock" "jazz" "rock" "pop" "pop"])

(defn exclude [value items]
  (filter (fn [item] (not= value item)) items)
)

(defn -main [& args] (
  println (exclude "rock" tags)
))

Or even shorter with

(defn exclude [value items]
  (filter #(not= value %) items))

With the result similar as below vector:

$ lein with-profile t11-tags-exclude run
(jazz pop pop)

Loop for Unique

Based on

unique :: [String] -> [String]
unique [] = []
unique (head:tail) = head:unique(exclude head tail)

We can rewrite threeliners above with recursive function as below code:

(ns t12-tags-unique (:gen-class))
(def tags ["rock" "jazz" "rock" "pop" "pop"])

(defn exclude [value items]
  (filter #(not= value %) items))

(defn unique [items]
  (if (<= (count items) 1)
    items
    (let [[head & tail] items]
      (concat [head] (unique
        (exclude head tail))))
  ))

(defn -main [& args] (
  println (unique tags)
))

With the result similar as below vector:

$ lein with-profile t12-tags-unique run
(rock jazz pop)

The Recursive Head and Tail

Based on

… = head:unique(exclude head tail)

Transformed into three possibilities:

  1. [], return [] (empty vector).

  2. [head], return [head] (vector with single value). We can also wrapped both [] and [head], by just returning items.

  3. [head:tail], returning recursive function [head, new-tail].

    (let [[head & tail] items]
      (concat [head] (unique
        (exclude head tail))))

I guess the code itself is self explanatory.

Put All The String Together

We can apply all to finish our previous songs task, with entirely different approach.

(ns t13-songs-unique (:gen-class) (:use my-songs))

(defn get-tags [songs]
  (def tags (atom []))
  (doseq [song songs]
    (if (not (nil? (:tags song)))
      (doseq [tag (:tags song)]
        (swap! tags conj tag))))
  (deref tags))

(defn exclude [value items]
  (filter #(not= value %) items))

(defn unique [items]
  (if (<= (count items) 1)
    items
    (let [[head & tail] items]
      (concat [head] (unique
        (exclude head tail))))
  ))

(defn -main [& args]
  (->> songs
       (get-tags)
       (unique)
       (println)
  ))

With the result similar as below vector:

$ lein with-profile t13-songs-unique run
(60s jazz rock 70s pop)

Clojure: The Recursive of Head and Tail Pattern


What is Next 🤔?

We should be ready for our next topic, concurrency case example.

Consider continue reading [ Clojure - Playing with Records - Concurrency ].