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?
Top comments (0)