Preface
Goal: Continue Part One
5: Approach in Solving Unique
A custom example, that you can rewrite.
An Algorithm Case
Why reinvent the wheel?
My intention is writing a custom pattern matching example, so later I can rewrite other algorithm based on this example. You can read the detail on the first article. The record overview.
Consider going back to simple data. We need to discuss about solving unique list.
x:xs Pattern Matching
The original x:xs
pattern matching is made of two functions:
- Exclude
- Unique
Pattern matching in Julia
required macro,
and is not as fancy as haskell
, or even OCaml
.
But what we have in Julia
is enough to solve our little problem.
Exclude Function
The exclude
function is just a filter
with below details:
tags = ["rock", "jazz", "rock", "pop", "pop"]
function exclude(value)
tags -> filter(tag -> tag!=value, tags)
end
tags |> exclude("rock") |> println
With the result as below array
:
$ julia 21-exclude.jl
["jazz", "pop", "pop"]
Recursive Unique Function
With exclude
function above,
we can build unique
function recursively, as below code:
tags = ["rock", "jazz", "rock", "pop", "pop"]
exclude = function(value, tags)
return [ tag for tag in tags if tag != value ]
end
unique = function(tags)
if length(tags) <= 1
return tags
else
head = popfirst!(tags)
tail = unique(exclude(head, tags))
return pushfirst!(tail, head)
end
end
tags |> unique |> println
With the result as below array
:
$ julia 22-unique.jl
["rock", "jazz", "pop"]
Although the syntax looks is, not exactly the same as functional programming in haskell, the algorithm is the same.
Install Match Macro
Instead of just conditional,
we can utilize third party macro named match
.
First we have to install using julia
command line.
julia> using Pkg
julia> Pkg.add("Match")
Installing known registries into `~/.julia`
######################################################################## 100.0%
Added registry `General` to `~/.julia/registries/General`
Resolving package versions...
Installed Match ─ v1.1.0
Updating `~/.julia/environments/v1.5/Project.toml`
[7eb4fadd] + Match v1.1.0
Updating `~/.julia/environments/v1.5/Manifest.toml`
[7eb4fadd] + Match v1.1.0
Using Match Macro
Then we can use it as below code.
using Match
tags = ["rock", "jazz", "rock", "pop", "pop"]
exclude(value, tags) = begin
filter(tag -> tag!=value, tags)
end
unique(tags)::Array{String} = begin
@match tags begin
[] => []
[head] => [head]
_ => begin
head = popfirst!(tags)
tail = unique(exclude(head, tags))
pushfirst!(tail, head)
end
end
end
tags |> unique |> println
With the result as below array
:
$ julia 23-match.jl
["rock", "jazz", "pop"]
The reason that I put Array{String}
string is,
without the type signature we have this result below:
$ julia 23-match.jl
Any["rock", "jazz", "pop"]
One more thing, by the time of this article written,
I still don’t know how to match with idiomatic [tail:head]
.
Generic Type
Both functions can should capable applied to any type, not just string. It is a good time to apply generic.
tags = ["rock", "jazz", "rock", "pop", "pop"]
function exclude(
value::T, tags::Array{T}
)::Array{T} where {T<:Any}
return [ tag for tag in tags if tag != value ]
end
function unique(
tags::Array{T}
)::Array{T} where {T<:Any}
if length(tags) <= 1
return tags
else
head = popfirst!(tags)
tail = unique(exclude(head, tags))
return pushfirst!(tail, head)
end
end
tags |> unique |> println
With the result as below array
:
$ julia 24-generics.jl
["rock", "jazz", "pop"]
Custom Utility Module
This would be great if we can reuse, the generic unique function above into its own module.
Instead of two functions, we can merge both into one, to reduce complexity.
module MyUtils
export unique
function unique(tags::Array{T})::Array{T} where {T<:Any}
if length(tags) <= 1
return tags
else
head = popfirst!(tags)
excluded = [ tag for tag in tags if tag != head ]
return pushfirst!(unique(excluded), head)
end
end
end
Apply to Songs
The complete code is as below:
using Base.Iterators
include("MyUtils.jl")
include("MySongs.jl")
using .MySongs
[ song.tags for song in getSongs()
if song.tags!=nothing
] |> flatten |> collect |> MyUtils.unique |> println
With the result as below array
:
$ julia 25-songs.jl
["60s", "jazz", "rock", "70s", "pop"]
6: More About Function
Dive Deeper with Functional Programming
Function Composition
We can rewrite the previous code with function composition.
This article below show a very nice trick,
that you can use of in Julia
For practical reason, I flip the function order.
function ∘(f, g)
x -> g(f(x))
end
Function composition is pretty common in functional world. Now we can compose the function as below:
[ song.tags for song in getSongs()
if song.tags!=nothing
] |> (flatten ∘ collect ∘ unique ∘ println)
The complete code is as below:
using Base.Iterators
include("MySongs.jl")
using .MySongs
function ∘(f, g)
x -> g(f(x))
end
[ song.tags for song in getSongs()
if song.tags!=nothing
] |> (flatten ∘ collect ∘ unique ∘ println)
With the result as below array
:
$ julia 14-composition.jl
["60s", "jazz", "rock", "70s", "pop"]
7: Concurrency with Channel
Julia
support Actor Model
pattern.
We can rewrite those process above through channel.
Reference
The Skeleton
We should be ready for the real demo. This is only consist of one short file.
include("MyUtils.jl")
include("MySongs.jl")
using .MySongs
function flatten(messages::Channel) … end
function walk(messages::Channel) … end
function run() … end
run()
- Producer: flatten()
- Consumer: walk()
- Program entry point: run()
Producer and Consumer
We should prepare two functions:
- One for
Producer
thatput!
to channel, - And the other one for
Consumer
thattake!
from channel.
function flatten(messages::Channel)
songs::Array{Song} = getSongs()
for song in songs
if song.tags!=nothing
for tag in song.tags
put!(messages, tag)
end
end
end
end
function walk(messages::Channel)
newtags::Array{String} = []
for tag in messages
push!(newtags, tag)
end
return newtags
end
Running Both Routine
Pretty short right!
Consider gather both function in run
entry point.
function run()
messages = Channel(flatten)
walk(messages) |>
MyUtils.unique |>
println
end
run()
With the result as below array
:
$ julia 27-channel.jl
["60s", "jazz", "rock", "70s", "pop"]
This is the end of our Julia
journey in this article.
We shall meet again in other article.
What is Next 🤔?
Consider continue reading [ GNU R - Playing with Records ].