Purescript: Playing with Records
Tags: purescript, haskell, functional
Published: 2020-12-22 06:28:26

This post was originally posted on my personal blog 😀. The source code example also provided in github, given in that blog.

🕷 [Purescript - Playing with Records](https://epsi.bitbucket.io/lambda/2020/11/05/playing-with-records-purescript/)

🕷 [Haskell - Playing with Records](https://epsi.bitbucket.io/lambda/2020/11/04/playing-with-records-haskell-01/)

## Goal

> Goal: A step by step practical case to collect unique record fields. Using `Purescript` from `Haskell` mockup.

The task is simply to collect unique tags from records. I have done it using `Javascript`, and `Typescript`. And I want to know how it looks like with `Purescript`.

The issue is, I never code in `Purescript` before. Luckily I have learnt `Haskell` a few years ago. So I decide to make a mockup from `Haskell`. Rewrite from `javascript` to `haskell` takes an adaptation, but after a while, it is not that hard to do.

It turned out, that it is easy to rewrite, from `Haskell` to `Purescript`. But beware of the differences.

While it is comfortable to work with `list` in `Haskell`, `Purescript` utilize `array`, that is more compatible with `Javascript`.

This might cause an issue, for custom algorithm. For example purpose, we are going to use algorithm, for collecting `unique array/list`. Of course we can do it with `nub` standard library. I just want to show the reader something more fundamental, just in case anyone need to make custom function in `Purescript`.

Let's get it on.

-- -- --

## Data Structure: Original in Javascript

Consider a case example of some popular songs from the old decades.

With `javascript`, I can just write away the records in below form:

```javascript
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" }
];

export default songs;
```

-- -- --

## Data Structure: Ported to Haskell

With `Haskell`, I can strictly append additional type signature.

```haskell
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 }
  ]
```

![Data Structure: Ported to Haskell](https://dev-to-uploads.s3.amazonaws.com/i/f0hiqqoizayzgdrhf06u.png)

I also attach `Maybe` as a `nullable option`, so we can adapt   while no tags data is available.

-- -- --

## Data Structure: Ported to Purescript

The same applied with `purescript`. Except we use `array` instead of `list`.

```haskell
module MySongs (Tags(..), Song, songs) where

import Data.Maybe

type Tags = Array String
type Song = { title :: String, tags :: Maybe Tags }

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

```

![Data Structure: Ported to Purescript](https://dev-to-uploads.s3.amazonaws.com/i/7e8m7wm7atrs4azfoz80.png)

-- -- --

## Functional: in Javascript

These days with `ecmascript 2019` we can use `flatMap`, so the code can be shorter.

```javascript
import songs from "./songs-data.js";

const unique = array => [... new Set(array)];

const allTags = unique(songs
  .filter(song => song.tags)
  .flatMap(song => song.tags)
);
console.log(allTags );
```

![Functional: in Javascript](https://dev-to-uploads.s3.amazonaws.com/i/8uduputxl8o72i44ypxd.png)

-- -- --

## Oneliner Solution: in Haskell

We can also make a oneliner statement in `Haskell`. Using `catMaybes` to extract values from list of Maybes. Using `concat` to `flatten` the `list`. And finally using `nub` standar library to filter just the `unique` values. 

```haskell
import MySongs
import Data.List
import Data.Maybe

unwrapTags :: Tags -> [String]
unwrapTags (Tags tags) = tags

main = print $ nub $ concat 
             $ (map unwrapTags)
             $ catMaybes
             $ (map tags songs)
```

With the result similar to below:

```bash
$ runghc 14-songs-tags-unique.hs
["60s","jazz","rock","70s","pop"]
```

![Oneliner Solution: in Haskell](https://dev-to-uploads.s3.amazonaws.com/i/ns0uuascnqklsxrzmo7u.png)

Short enough to makes me happy. If you ever write a `Haskell` codes, you should know how it works.

## Oneliner Solution: in Purescript

It is a little bit longer in `Purescript`. Because we need more `import` statement, compared to `haskell` counterpart.

```haskell
module Step14 where
import Prelude
import Effect.Console (log)
import Data.Array (concat, filter, catMaybes, nub)
import Data.Maybe
import MySongs

main = log $ show $ nub
        $ concat $ catMaybes $ (map _.tags songs)
```

With the result similar to below:

```bash
$ spago run --main Step14
[info] Build succeeded.
["60s","jazz","rock","70s","pop"]
```

You can spot, that in `Purescript` we are using `(map _.tags songs)` to access field of the each `songs` record, instead of `(map tags songs)` in `Haskell`.

![Oneliner Solution: in Purescript](https://dev-to-uploads.s3.amazonaws.com/i/gu7qclnbgicmbzk5j8xz.png)

There are details to achieve this in provided link above. I just want this notes to be short. So I strip out the details here.

-- -- --

## Custom Unique Function: in Haskell

We are done with introduction. Now let me write a custom unique function, based on some stackoverflow and coderosetta.

```haskell
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 to below:

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

The thing is, the code is using `x:xs` pattern matching. This could be a troublesome while working with `array` in `purescript`, since `x:xs` pattern matching is made for `list`. And we are working with `array` in `purescript`.

For you who never heard `x:xs` pattern, it is easier to understand, if we write the pattern as, `head:tail` instead of `x:xs`. Where the `head` is the first element, and the `tail` are the rest elements in `list` or `array`.

-- -- --

## Custom Unique Function: in Purescript

Now, how is it going to be in `Purescript` 🤔?

Well, we can utilize `uncons`. Then recursively `cons` the `tails`.

```haskell
module Step07 where
import Prelude
import Data.Array (filter, (:), cons, uncons)
import Data.Maybe
import Effect.Console (log)

exclude :: String -> Array String -> Array String
exclude tag xs = filter((/=) tag) xs

unique :: Array String -> Array String
unique tags = case uncons tags of
  Nothing -> []
  Just {head: tag, tail: tags} -> cons tag (unique (exclude tag tags))

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

main = log $ show (unique tags)
```

With the result similar to below:

```bash
$ spago run --main Step07
[info] Build succeeded.
["rock","jazz","pop"]
```

No need to take hours to find replacement for this `x:xs` pattern in `array`.

-- -- --

## Conclusion

That is all. You can use the pattern for your own custom algorithm.

What do you think?