Preface
Goal: A brief explanation about learning functional, with a ready to use example records.
As part of my learning journey, I pick a simple algorithm, finding unique array/list from a few records. Then I implement in different language.
I believe this is going to be easier to be understood, from the code/script author, and the reader
Article Series
For each languages I make a dossier of source code, in step by step fashioned extensively. And then I pour the explanation for each steps, resulting in dozens of articles.
Empower Coder Community 🤔?
My self-portrait, pretend that I’m thinking.
Leverage Hello World
Back to square one.
Starting from zero as beginner. I started all over again as an empty cup. Wider my experience as a ployglot. Never underestimate any major language. Then suddenly, I realize that I know nothing.
This is what I must do decades ago. Frankly speaking, if you are a beginner, you should take similar path, with your own case examples.
Feynman Method
I’m using feynman
method.
-
Take notes
-
Explain to others.
My implementation is
-
Write down my code to a repository as personal notes.
-
Explain as an article series in a blog.
Material
One problem, Many programming Languages
I limit the material to javascript related language. Because I know, my time is limited. I have works to do.
The ecmascript and typescript itself is, not a functional programming language. But we can improve those two by bringing functional approach, for these two languages.
The article has been made so far is just a few.
- Ecmacript (Towards Functional)
- Typescript (Small Step into Typed)
- Haskell as Purescript Mockup
- Purescript
- ReasonML
- OCaml
- F#
- Erlang
- Elixir
- Clojure
- Scala
- Lua
- Julia
- GNU R
- Nim
- Crystal
- Go
- Rust
- C#
- Python
- Ruby
- PHP
- BASH, Fish Shell, Ion Shell
- JQ, AWK, Sed
- Perl 5
- Perl 6 (Raku)
- TCL/TK
- Guile
- Racket
- Racket
I know there is still other languages,
such as elm
, groovy
, and racket
.
Also minor language such as idris
, agda
, coq
, unison
,
odin
, jai
, poni
, squirrel
, vlang
and brainf++k
.
But now there are only these, that each are ready to be read.
An Algorithm Case
Why making things complex?
I cannot unseen what I have seen in functional programming,
such as x:xs
pattern matching algorithm.
there are so many algorithm using this pattern,
with wide application.
In fact I have made my custom real life problem solving, using this pattern matching. I will pour this specific topic in other article series later. For now we need a simple example, a base for further application.
Luckily this article has a good case to explore with.
But some languages has already had built in unique
function,
that is easy to use with.
Why reinvent the wheel?
I would like to write my own unique function, with just loop and recursive. My intention is writing a custom pattern matching example, so later I can rewrite other algorithm based on this example.
Reading The Code
Although I begin with functional approach, not all problem solved with functional approach. Some language are better written in imperative one.
Sometimes it is hard for beginner to read code, especially functional code. Because we are commonly read imperative code.
You will have better understanding, if you dare to create your own record case, and start manipulating fields, using your own custom code.
The more you write your own custom code, the more you can read other people code.
What do you think?
Common Use Case
The task is simply to get the unique string.
In my experience after years of techical blogging.
Most blog has capability, to gather tags
from each articles.
All gathered tags
should finally be gathered as unique tag.
This distinct values, will be shown in the special archive pag,
called archive by tags
, or maybe in sidebar for each articles.
Instead of article in a blog. I have very similar case. Here is my list of Beautiful, and imaginative artwork. Some popular songs from the old decades.
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" }
];
Task
Get the unique tag string
The task is simply to get the unique tag string. Of course with functional approach.
After you finished this task with functional approach, I believe you can apply knowledge that you gain for many different case, related with record field, even with the deep nested one.
Imperative Ecmascript (Procedural)
This used to be written as:
let tagSet = new Set();
// using set feature to collect tag names
songs.forEach(song => {
if( "tags" in song ) {
let tags = song.tags;
console.log(tags);
for (const tag of tags) {
tagSet.add(tag);
}
}
});
console.log(tagSet);
// normalize to array
let allTags = [...tagSet];
console.log(allTags);
1: Functional Ecmascript
With ES 2019 flatMap
.
This can be rewritten as:
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 );
2: Typescript
Or if you wish for detail typed structure,
you can add type
from above script to below script:
import { Song, songs } from "./songs-data";
type Unique = (array: Array<string>) => Array<string>;
const unique : Unique = (array) => [... new Set(array)];
type PickTags = (song: Song) => Array<string>;
const pickTags : PickTags = (song) => song.tags!
const allTags: Array<string> = unique(songs
.filter( pickTags )
.flatMap( pickTags )
);
console.log(allTags );
3: Haskell
I need a mockup before writing to purescript
:
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)
4: Purescript
And then the purescript
itself:
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)
5: ReasonML
After learning haskell
, and purescript1, writing the
ReasonML` is become an easy thing to do:
module L = Belt.List
module O = Belt.Option
module S = StdLabels.List
open MySongs
open MyTags
L.map (Songs.lsongs, (lsong) =>
O.getWithDefault(lsong.Songs.ltags, [])
) |> S.flatten
|> Tags.unique
|> Array.of_list
|> Js.log
6: OCaml
After using reasonml
for a while,
it make sense to step into ocaml
.
#use "mysongs.ml";;
#use "mytags.ml";;
let unwrap my_option : tl_tags =
match my_option with Some x -> x | None -> [];;
let flatten my_list : string list = List.fold_left (
fun acc element -> acc @ element
) [] my_list;;
let all_tags my_songs: tl_tags list = List.map (
fun my_song -> (unwrap my_song.tags)
) my_songs;;
let () = print_endline (
join_str (unique (flatten (all_tags songs)))
)
7: F#
According to wikipedia,
F#
originates from Microsoft Research, Cambridge, UK.
open System
#load "MySongs.fsx"
let unwrap myOption: MySongs.MyTags =
match myOption with
| Some x -> x
| None -> []
let mapTagss songs: List<MySongs.MyTags> =
songs |> List.map (
fun (song: MySongs.MySong) ->
(song.Tags |> unwrap )
)
[<EntryPoint>]
MySongs.songs
|> mapTagss
|> List.concat
|> List.distinct
|> fun (tags) ->
"[" + (tags |> String.concat ", ") + "]"
|> Console.WriteLine
8: Erlang
Go further with Erlang
.
-module(t10_unique).
-import(my_songs, [songs/0]).
-import(lists, [append/1]).
-import(set, [from_list/1, to_list/1]).
-export([show/0]).
-include("my_header.hrl").
unique(List) -> sets:to_list(sets:from_list(List)).
show() ->
Songs = songs(),
Tags = [ Head#song.tags || Head <- Songs ],
io:fwrite("~60p~n", [unique(append(Tags))]).
9: Elixir
After using erlang
for a while, it make sense to step into elixir
.
import Songs
songs()
|> Enum.map(& &1.tags)
|> :lists.append
|> Enum.uniq
|> IO.inspect
10: Clojure
The language is fine.
But it takes leiningen
preparation.
(ns t09-pipe (:gen-class) (:use my-songs))
(defn extract
[songs]
(remove nil? (map #(get % :tags) songs))
)
(defn -main [& args]
(->> songs extract flatten distinct println))
I’m still not sure about clojurescript
.
11: Scala
I skip the OOP part.
import mysongs._
object T08 extends App {
def tagsList(songs: List[Song])
: List[Option[List[String]]]
= songs map (song => song.tags)
println(
tagsList(MySongs.songs)
.flatten.flatten.distinct
)
}
I’m still not sure about scala.js
.
12: Lua
Lua capability is somehow limited. This is a good challenge.
local inspect = require('inspect')
require 'fun' ()
require 'my-songs'
require 'my-flatten'
require 'my-tools'
function exclude(val, tags)
return normalize(filter(
function (tag) return tag ~= val end, tags))
end
function unique(tags)
if type(tags) == 'table' then
if #tags == 0 then return {} end
if #tags == 1 then return { head(tags) } end
return join_tables(
{ head(tags) },
unique( exclude(head(tags), tail(tags)) )
)
end
end
local tags = flatten( clean(extract(songs)) )
print_inspect( unique(tags) )
13: Julia
I think I’m in love with this Julia
language.
using Base.Iterators
include("MySongs.jl")
using .MySongs
[ song.tags for song in getSongs()
if song.tags!=nothing
] |> flatten |> collect |> unique |> println
14: GNU R
GNU R
has different beauty compared with Julia.
We can simply utilize dataframe
to data handle records.
library(tibble)
library(purrr)
load("songs.RData")
(dataframe %>%
dplyr::filter(.data$tags != "NULL")
)$tags %>% flatten %>% unique %>% paste
15: Nim
Nim
is surprisingly very easy,
and also fun to work with.
import sequtils, sets, MySongs
songs
.mapIt(it.tags)
.foldl(a & b)
.toOrderedSet
.toSeq
.echo
16: Crystal
With Crystal
, I just feels like copy paste from Ruby
code.
require "./my-songs"
puts songs
.map { |song| song.tags }
.select { |tags| tags != [] of String }
.flatten
.uniq
17: Go
With Go
, we have simple code.
Simplicity is a necessity.
package main
import (
"example.com/mysongs"
"example.com/myutils"
"fmt"
)
func main() {
var tags []string
tags = mysongs.GetSongs().FlattenTags()
tags = myutils.Unique(tags)
fmt.Println(tags)
}
18: Rust
To code in Rust
, we need to understand,
the concept of ownership
and borrowing.
mod my_songs;
mod my_utils;
fn main() {
let songs: Vec<my_songs::Song> =
my_songs::get_songs();
let tags: Vec<&str> = songs
.into_iter()
.filter_map(|song| song.tags)
.flat_map(|tag_s| tag_s)
.collect();
println!("{:?}", my_utils::unique(tags))
}
19: C#
With C# 9.0
, we have OOP in high level, and FP everywhere else.
using System;
using System.Linq;
class Step09Unique
{
public void Show() {
MySongs mySongs = new MySongs();
var tags = (mySongs.songs)
.Select(song => song.Tags)
.Where(tags => tags != null)
.SelectMany(tag => tag)
.Distinct();
Console.WriteLine("[{0}]",
string.Join(", ", tags));
}
}
20: Python
List comprehension everywhere.
from MySongs import songs
tags = [
tag for song in songs
if 'tags' in song
for tag in song['tags']
]
print(list(set(tags)))
21: Ruby
Different approach, compared to python.
require_relative 'MyStructSongs'
include StructSongs
tagss = SONGS
.map { |song| song.tags }
.compact
.flatten
.uniq
puts "#{tagss}"
22: PHP
Something commonly see in web development.
<?php
require_once(__DIR__.'/MyClassSongs.php');
$tags =
array_values(
array_unique(
array_merge(
... array_map(
function ($song) {
return $song->tags; },
$songs)
)));
printf("%s\n", json_encode($tags));
23: BASH, Fish Shell, Ion Shell
These three shells is belong to one section.
BASH IFS
source ./MySongs.sh
source ./MyHelperFlatten.sh
# Extract flatten output to array
tags_fl=$(flatten ${songs[@]})
IFS=' '; read -a tags_flatten <<< "${tags_fl[@]}"
declare -A tags_unique
for tag_item in "${tags_flatten[@]}"; do
let tags_unique[$tag_item]++
done
tags=(${!tags_unique[@]})
IFS=':'
echo "${tags[*]}"
Fish Shell
#!/usr/bin/env fish
source ./MySongs.sh
source ./MyHelperFlatten.sh
# Extract flatten output to array
set tags (flatten $songs | sort -u)
string join ':' $tags
Ion Shell
#!/usr/bin/env ion
source ./MySongs.sh
source ./MyHelperFlatten.sh
# Extract flatten output to array
let tags_fl = $(flatten [@songs])
let tags_un = $(echo $tags_fl | sort -u)
echo $join(@split($tags_un \n) :)
24. JQ, AWK, Sed
These three shells is belong to one section.
JQ
jq -r "(.songs[].tags | \
select( . != null ))[]" \
songs.json \
| sort \
| uniq \
| tr '\n' ' '
AWK
#!/bin/awk -f
BEGIN {
FS="[;,]"
i = 0
}
{
gsub(/;[ ]+/,";")
gsub(/,[ ]+/,",")
$1=""
OFS=":"
$1=$1;
split($0, tags_temp, ":")
for (j in tags_temp)
if (j > 1)
tags[++i] = tags_temp[j]
}
END {
i=0
for (t in tags) {
if ( !exist[tags[t]]++ ) {
unique[++i] = tags[t]
}
}
ORS=":"
for (u in unique) {
if (u == i) ORS="\n"
print unique[u]
}
}
Sed
#!/bin/sh
exec sed -E '
/^(.*);(.*)$/!d; s/^(.*);(.*)$/\2/
' "$@" |
exec sed -E '
:label;N;$!b label
s/(,| *|\n ?)/ /g;s/^ //g;s/ /\n/g
' | sort -u |
exec sed -E '
:label;N;$!b label
s/\n/:/g
'
25: Perl 5
Long Live CPAN.
use MySongs;
my %seen;
my @songs_tags = grep {
!$seen{$_}++
} map {
@{ $_->{'tags'} }
} grep {
exists($_->{'tags'})
} @MySongs::songs;
say join(":", @songs_tags);
26: Perl 6 (Raku)
The evolved Perl, with list comprehensions.
use MySongs;
my @songs_tags = (
$_<tags>
if 'tags' ∈ $_.keys
for @songs
);
my @tags = @songs_tags
.List.flat.unique;
join(":", @tags).say;
- Not scary, with the help of official documentation.
27: TCL/TK
The evolved Perl, with list comprehensions.
lappend auto_path "./"
package require MySongs 1.0
set tags [list]
foreach song $MySongs::Songs {
if [dict exist $song tags] {
set tagss [dict get $song tags]
foreach tag $tagss {
if {[lsearch $tags $tag] < 0} {
lappend tags $tag
}}}}
set tagsstr [join $tags ":"];
puts "$tagsstr";
28: Guile
Scheme implementation. Obscure language with exotic syntax.
(define-public (unique tags)
(if (<= (length tags) 1)
tags
(let
( (head (car tags)) (tail (cdr tags)) )
(append (list head)
(unique (delq head tail)))
)))
29: Racket
Very similar to Guile cousin.
#lang racket
(provide unique-tags)
(define (unique-tags tags)
(if (<= (length tags) 1)
tags
(let (
(head (first tags))
(tail (rest tags)) )
(append (list head)
(unique-tags (remove head tail)))
)))
30: Pascal
No functional stuff.
You can have choices whether pure free pascal, or the object pascal one. Pascal is lower level than other language, so the code is longer than usual. Thus, instead of complee code, I can only give partial stuff as below.
function TSongList3.GetUniqueTags(): TTags;
var
SL : TStringList;
ASong : PSong;
Tag : ANSIString;
begin
SL := TStringList.Create;
SL.Duplicates := dupIgnore;
SL.Sorted := true;
for ASong in Self do
for Tag in ASong^.Tags do
SL.Add(Tag);
Result := SL.ToStringArray(0, Count-1);
SL.Free;
end;
Meet The Jabberwocky
Let’s get it on
Functional is not the beast. Nor math. Functional is the strength to solve an issue, that do not exist in imperative realm.
Why would I overcomplicated task, when in the imperative approach works? Because I need to grow up. I do not use Haskell in any of my real project. But I bring the approach in anywhere else.
Enough with talk. Get your vorpal sword. Time to code.
What is Next 🤔?
Our very first example, the most common used programming language, the ecmascript aka javascript.
Consider continue reading [ Ecmascript - Towards Functional - Part One ].