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

Having fun with Haskell

I was once want to leverage my ecmascript knowledge. So I learn typescript and then purescript. I was too scared to jump directly too purescript. then I remember a few years a go I learn Haskell, so I make a mockup in Haskell. I rewrite my ecmascript in Haskell, and then I port the Haskell code to purescript.

Writing to Haskell is easy for me because, I have already had experience before. But still, I feels like the journey enriched my knowledge, so I decide to share the adventure.

From Haskell To Purescript

Consider this article as Purescript Mockup.

Writing to Purescript is also easy, since of purescript is very similar to Haskell. But there is a catch, there are differences, and there is very limited guidance. In fact I spent hours find out, onhow to solve these little differences. I think I have an obligation to share, so can port Haskell to Purescript easier.

Prelude Library

Find The Vorpal Sword!

We can make our own example case to leverage Haskell experience. Haskell has so many library, not all library is good. But there is one basic libraries called prelude, that we can use most of it.

There are already so many goodies in prelude, that we can exploit to work out common programming a situation. We can combine different techniques, create more possibility, and finally enjoy prelude as much as possible. In short, prelude is enough someone who start step into Haskell.

It is time to stop this hocus pocus, and get straight to coding.

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.

Songs and Poetry

const songs  = [
  { title: "Cantaloupe Island",          tags: ["60s", "jazz"] },
  { title: "Let It Be",                  tags: ["60s", "rock"] },
  { title: "Knockin' on Heaven's Door",  tags: ["70s", "rock"] },
  { title: "Emotion",                    tags: ["70s", "pop"] },
  { title: "The River" }
];

For simplicity reason, I start with a loosely typed language. The famous ecmascript, as shown in above code.

Specification

  1. This simple records has two fields. The optional tags field is not mandatory.

  2. The tags, can be implemented differently.

    • Ecmascript/Typescript: an array of string.
    • Haskell: a list of string.
    • Purescript: an array of string.

Ecmascript Solution

The Answer

There are many ways to do things in ecmascript. One of them is this cryptic oneliner as below:

console.log([... new Set(songs.flatMap(song => song.tags || []))]);

Prepopulated Data

Haskell is more verbose, with this solution below:

import Data.List

data Tags = Tags [String] deriving (Eq, Show)
data Song = Song { title :: String, tags :: Tags }
        deriving (Show)

songs :: [Song]
songs = [
    Song { title = "Cantaloupe Island",
           tags = Tags ["60s", "jazz"] },
    Song { title = "Let It Be",
           tags = Tags ["60s", "rock"] },
    Song { title = "Knockin' on Heaven's Door",
           tags = Tags ["70s", "rock"] },
    Song { title = "Emotion",
           tags = Tags ["70s", "pop"] },
    Song { title = "The River",
           tags = Tags [] }
  ]

main = print $ nub $ concat
        $ (map (\(Tags tags) -> tags))
        $ (map tags songs)

Haskell: Simpler Songs Record

Actually the solution above is also oneliner.

main = print $ nub $ concat $ (map (\(Tags tags) -> tags)) $ (map tags songs)

Now you can see the power of functional approach. But we need to adapt so we can read, what is really happening with this oneliner above. We need to know how exactly each function do. And further more what alternative applied for each situation.

The Unreadable

After a while, I began to understand how to read functional code. Functional code was so hieroglyph for me, until I wrote it myself. Slowly but sure, I can read others people codes. Even spot some mistakes, and improved with my custom code.

Functional code is easy to be read. If you dare to leave imperative thinking in your mind.


1: Data Type

Sing One Song at A Time

I enjoy playing song, one at a time, memorized the lyrics, play it over and over again, and after that moving it into my playlist.

Before building a complex records, I began with simple datatype.

List of String

Haskell use List instead of Array. The List is using recursive, to construct itself. Thus a linked list it is, in contrast of array. Although there is also Array in haskell, it is usually more comfortable to work with List.

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

main = do
  print tags

With the result of

$ runghc 01-type-tags.hs
["rock","jazz","rock","pop","pop"]

Oh yes, you can runghc instead of compile and run as below:

$ ghc 01-type-tags.hs
[1 of 1] Compiling Main             ( 01-type-tags.hs, 01-type-tags.o )
Linking 01-type-tags ...

$ ./01-type-tags
["rock","jazz","rock","pop","pop"]

Typeclass

Type class in Haskell does look like interface, although actually it serve a slight different purpose.

data Tags = Tags [String] deriving (Show)
data Song = Song { title :: String, tags :: Tags }
        deriving (Show)

song :: Song
song = Song { title = "Cantaloupe Island",
              tags = Tags ["60s", "jazz"] }

main = print (tags song)

With the result of

$ runghc 02-data-song.hs
Tags ["60s","jazz"]

You can observe the difference. Now the list wrapped in Tags constructor.

Data Decision

Alternatively we can use type synonyms. So instead, of using data constructor as below:

data Tags = Tags [String]

I can use type synonym as below

type Tags = [String]

I found that using typeclass is, more suitable for this Haskell article. On the other hand, using simple type synonym is more suitable for Purescript article.

Deriving

We also need to add deriving (show) in typeclass declaration, so we can print it later. There is also other addition later such as Eq, so we can compare with other stuff.

data Tags = Tags [String] deriving (Show)

This is also common stuff.

Unwrap Data

Since we want to the final result be precisely an list, we need to make custom unwrap function:

data Tags = Tags [String] deriving (Show)
data Song = Song { title :: String, tags :: Tags }
        deriving (Show)

song :: Song
song = Song { title = "Cantaloupe Island",
              tags = Tags ["60s", "jazz"] }

unwrap :: Tags -> [String]
unwrap (Tags tags) = [ tag | tag <- tags ]

main = print $ unwrap $ tags song

With the result of

$ runghc 03-data-unwrap.hs
["60s","jazz"]

Haskell: Unwrapping Data Constructor

Map and List Comprehensive

In the example above we are using list comprehensive.

unwrap (Tags tags) = [ tag | tag <- tags ]

We can instead using map to achieve this:

unwrap (Tags tags) = map (\tag -> tag) tags

The choice is yours, whatever suit the situation. Or whatever you are comfortable with.

🤔

With map we can simplified using id.

unwrap (Tags tags) = map id tags

Since map id tags is the as tags, then we can rewrite as:

unwrap (Tags tags) = tags

Thank you to Jihad D. Waspada, for this map id tags to tags improvement.


2: Data Structure

Meet The Songs Record

From just karaoke, we can go pro with recording studio.

From simple data, we can build a structure to solve our task.

Record in Haskell

Consider to make a new module, named MySongs.hs.

module MySongs (Tags(..), Song, songs, title, tags) where

data Tags = Tags [String]
        deriving (Eq, Show)

data Song = Song { title :: String, tags :: Maybe Tags }
        deriving (Show)

songs :: [Song]
songs = [
    Song { title = "Cantaloupe Island",
           tags = Just (Tags ["60s", "jazz"]) },
    Song { title = "Let It Be",
           tags = Just (Tags ["60s", "rock"]) },
    Song { title = "Knockin' on Heaven's Door",  
           tags = Just (Tags ["70s", "rock"]) },
    Song { title = "Emotion",
           tags = Just (Tags ["70s", "pop"]) },
    Song { title = "The River",
           tags = Nothing }
  ]

Haskell: Song Data Structure

Exporting Constructor

To be able to use Tags constructor in other module, we need to write as below:

module MySongs (Tags(..), Song) where

Instead of as below

module MySongs (Tags, Song) where

Now we can use the constructor on the other modules:

unwrap :: Tags -> [String]
unwrap (Tags tags) = [ tag | tag <- tags ]

Wrapping in Maybe

Why do we have to wrapped tags in Maybe.

Song { title :: String, tags :: Maybe Tags }

It looks redundant, because we can define tags, as an empty list anyway.

    Song { title = "The River", tags = [] }

The anwer is, yes of course, it is redundant. In fact it is a good decision to write as below. And reducing so many complexity affected but the design.

Song { title :: String, tags :: Tags }

While the answer is yes, why this article still stuck with wrapping inside Maybe 🤔? Am I stubborn?

This article is intended for tutorial purpose. Mimic the original javascript that can have undefined, empty []. Thus we need the record, not to be too simple.

Using Songs Module

At this point we can utilize the module, as shown in this very simple script below:

import MySongs

main = print songs

With the result similar as below nested list:

Haskell: Using The Module


3: Approach in Solving Unique

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

nub

Haskell has this nub, as a comfortable method to bring off unique list. While Purescript has this nub to pull of unique array.

import Data.List

tags = ["rock", "jazz", "rock", "pop", "pop"]
main = print (nub tags)

With the result similar as below list:

$ runghc 08-tags-nub.hs
["rock","jazz","pop"]

As simple as that.

Avoid nub

Why do I need different approach anyway 🤔? Because there is a coding pattern that I need to show, especially in Purescript. I need to know how things works, so we can apply to other case.

And I simply cannot explain this pattern for tutorial purpose, if I linger with magic provided by, a ready to use method such as this nub thing.

In real world, you can safely use nub or ordnub, and leaving the complexity behind.

Exclude: List Comprehension

Before we go further, I need to show this cool exclude trick.

exclude :: String -> [String] -> [String]
exclude tag xs = [ x | x <- xs, (/=) tag x ]

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

main = print (exclude "rock" tags)

With the result similar as below list:

$ runghc 05-tags-filter.hs
["jazz","pop","pop"]

The list comprehesion can be shown as below notation:

exclude tag xs = [ x | x <- xs, (/=) tag x ]

I like to mockup things with this list comprehensive. It is simply math notation, and easier to be understood.

Exclude: Filter

Then I, simplify list comprehension above with filter. With about the same result.

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

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

main = print (exclude "rock" tags)

Now the question is. why would I want to make it hard for myself 🤔? Wasn’t list comprehension is good enough 😢?

The reason is, in Purescript, it is more comfortable in purescript, to work with Array than List.

And the catch is, there is no list comprehension, in Array 😅.

Infix

We can also write above function using infix fashioned.

exclude tag = filter(tag /=)

It is more clear to just show the code as below:

main = print $ filter ("rock" /=) tags

Using parantheses would make inequality operator as prefix. while pre could be interpreted as before. Infix here, could mean in the middle. It is easier to think infix as this way.

Unique

Afterward, we can continue to construct, a recursive list of unique string.

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

unique :: [String] -> [String]
unique [] = []
unique (tag:tags) = tag:unique(exclude tag tags)

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

main = print (unique tags)

With the result similar as below list:

$ runghc 07-tags-unique.hs
["rock","jazz","pop"]

This unique is using x:xs as pattern matching. The semicolon : is a cons operator to create a List. The basic coding pattern is a recursive function as below:

func (x:xs) = x : func(xs)

There used to be a constructor named Cons before my age. It is now replaced by : operator.

Simplified Unique

We can go further by simplify the code as oneliner below:

unique [] = []
unique (x:xs) = x : unique (filter ((/=) x) xs)

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

main = print (unique tags)

This way you can flexbily apply this code pattern, to a more complex record structure.

For simple case, use nub instead.


What is Next 🤔?

We have all the preparation, we need to conclude this article.

Consider continue reading [ Haskell - Playing with Records ].