Purescript: Playing with Records
E.R. Nurwijayadi
Posted on December 22, 2020
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
š· Haskell - Playing with Records
Goal
Goal: A step by step practical case to collect unique record fields. Using
Purescript
fromHaskell
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:
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.
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 }
]
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
.
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 }
]
Functional: in Javascript
These days with ecmascript 2019
we can use flatMap
, so the code can be shorter.
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 );
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.
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:
$ runghc 14-songs-tags-unique.hs
["60s","jazz","rock","70s","pop"]
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.
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:
$ 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
.
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.
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:
$ 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
.
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:
$ 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?
Posted on December 22, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.