Preface
Goal: A practical case to collect unique record fields using F#.
According to wikipedia,
F#
originates from Microsoft Research, Cambridge, UK.
Reference Reading
Source Examples
You can obtain source examples here:
Common Use Case
Task: Get the unique tag string
Please read overview for more detail.
Prepopulated Data
Songs and Poetry
module MySongs
type MyTags = List<string>
type MySong = { Title: string; Tags: Option<MyTags> }
let songs : List<MySong> = [
{ Title = "Cantaloupe Island";
Tags = Some ["60s"; "jazz"]};
{ Title = "Let It Be";
Tags = Some ["60s"; "rock"]};
{ Title = "Knockin' on Heaven's Door";
Tags = Some ["70s"; "rock"]};
{ Title = "Emotion";
Tags = Some ["70s"; "pop"]};
{ Title = "The River";
Tags = Some []};
]
F# Solution
The Answer
There might be many ways to do things in F#
.
One of them is this oneliner as below:
MySongs.songs
|> mapTagss
|> List.concat
|> List.distinct
|> fun (tags) ->
"[" + (tags |> String.concat ", ") + "]"
|> Console.WriteLine
Of course there are hidden details beneath each functions. I will discuss about this step by step.
Enough with introduction, at this point we should go straight to coding.
Environment
The dotnet
run well in linux
.
1: Data Type
Before building a complex records, I begin with simple datatype.
Using List.
There are a some useful data type in F#
.
List
is comfortable to work with.
My first attempt is copy from previous OCaml
code,
then paste to F#
let tags: List<string> =
["rock"; "jazz"; "rock"; "pop"; "pop"]
let () = List.iter (printf "%s, ") tags
Or you can utilize mutable
as below:
let mutable tags: List<string> = []
tags <- ["rock"; "jazz"; "rock"; "pop"; "pop"]
With the result as below array (not list):
$ dotnet fsi 01-tags.fsx
rock, jazz, rock, pop, pop, %
This output doesn’t even end with \n
endline.
Custom Pretty Print
Since we need nice output through put this article.
I create a helper join_str
function,
to make a custom pretty print.
let tags: List<string> =
["rock"; "jazz"; "rock"; "pop"; "pop"]
let joinStr : string =
"[" + (tags |> String.concat ", ") + "]"
let joinStr2: string =
"[" + (tags
|> List.reduce (fun acc tag ->
acc + ", " + tag)
) + "]"
printfn "%s" joinStr2
With the result as below list:
$ dotnet fsi 02-assign.fsx
[rock, jazz, rock, pop, pop]
Using Console.WriteLine
Built-in Object Dump
It turned out that F#
has capability,
to dump variable into the console.
open System
let tags: List<string> =
["rock"; "jazz"; "rock"; "pop"; "pop"]
|> List.distinct
Console.WriteLine tags
With the result as below list:
$ dotnet fsi 02-console.fsx
[rock; jazz; pop]
We will use Console.WriteLine
throughout this article.
2: Data Structure
Meet The Songs Record
From just karaoke, we can go pro with recording studio.
From simple data, we can build a structure to solve our task.
Type Alias
We can use user defined type to define a record:
open System
type MyTags = List<string>
type MySong = { Title: string; Tags: MyTags }
let song : MySong = {
Title = "Cantaloupe Island";
Tags = ["60s"; "jazz"]
}
[<EntryPoint>]
Console.WriteLine song.Tags
With the result as below list:
$ dotnet fsi 03-type.fsx
[60s; jazz]
Wrapping in Options for Nullability
I also add option
so tags
can accept None
value,
instead of just empty list.
type MySong = { Title: string; Tags: Option<MyTags> }
The complete code is as below:
open System
type MyTags = List<string>
type MySong = { Title: string; Tags: Option<MyTags> }
let song : MySong = {
Title = "Cantaloupe Island";
Tags = ["60s"; "jazz"] |> Some
}
let unwrap myOption: MyTags =
match myOption with
| Some x -> x
| None -> []
[<EntryPoint>]
song.Tags |> Console.WriteLine
With the result as below list:
$ dotnet fsi 04-type-option.fsx
Some([60s; jazz])
We need, a not too simple, case example.
This is why we have this Some […]
.
For unwrapping
, we can utilize this code below:
let unwrap myOption: MyTags =
match myOption with
| Some x -> x
| None -> []
Almost the same with OCaml
counterpart.
Record in F#
It is easy to arrange a bunch of record in a list.
Compared to other programming language it is a matter of syntax.
The only adaptation we need is, F#
utilize ;
instead of ,
.
Since we are going to reuse the songs
records multiple times,
consider to make a new file, named mysongs.ml
,
with complete code as below:
module MySongs
type MyTags = List<string>
type MySong = { Title: string; Tags: Option<MyTags> }
let songs : List<MySong> = [
{ Title = "Cantaloupe Island";
Tags = Some ["60s"; "jazz"]};
{ Title = "Let It Be";
Tags = Some ["60s"; "rock"]};
{ Title = "Knockin' on Heaven's Door";
Tags = Some ["70s"; "rock"]};
{ Title = "Emotion";
Tags = Some ["70s"; "pop"]};
{ Title = "The River";
Tags = None};
]
Using Songs File
At this point we can utilize the separate file above, as shown in this very simple script below:
open System
#load "MySongs.fsx"
let unwrap myOption: MySongs.MyTags =
match myOption with
| Some x -> x
| None -> []
[<EntryPoint>]
for song in MySongs.songs do
song.Tags
|> unwrap
|> Console.WriteLine
With the result similar as below sequential lines of list
:
$ dotnet fsi 05-module.fsx
[60s; jazz]
[60s; rock]
[70s; rock]
[70s; pop]
[]
3: Finishing The Task
Map, Flatten, Unique
Map
Instead of unwrap
in for-loop
,
we can prepare the data im the first place by using List.map
.
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>]
for tags in (MySongs.songs |> mapTagss) do
tags |> Console.WriteLine
With the result similar as below sequential lines of list
:
$ dotnet fsi 06-map.fsx
[60s; jazz]
[60s; rock]
[70s; rock]
[70s; pop]
[]
Flatten
We can use List.concat
to flatten the list
of list
.
[<EntryPoint>]
MySongs.songs
|> mapTagss
|> List.concat
|> fun (tags) ->
"[" + (tags |> String.concat ", ") + "]"
|> Console.WriteLine
With the result similar as below list
:
$ dotnet fsi 07-flatten.fsx
[60s, jazz, 60s, rock, 70s, rock, 70s, pop]
Unique
Finally Oneliner
And finally produce the unique
value using List.distinct
.
[<EntryPoint>]
MySongs.songs
|> mapTagss
|> List.concat
|> List.distinct
|> fun (tags) ->
"[" + (tags |> String.concat ", ") + "]"
|> Console.WriteLine
With the result similar as below list
:
$ dotnet fsi 08-unique.fsx
[60s, jazz, rock, 70s, pop]
This F#
syntax fashion in writing code is very cool.
We are not finished yet.
What is Next 🤔?
Consider continue reading [ F# - Playing with Records - Part Two ].