Article Series

This article series discuss more than 30 different programming languages. Please read overview before you read any of the details.

Playing with Records Related Articles.

Where to Discuss?

Local Group

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.

.NET F#: Using Console.WriteLine


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};
  ]

.NET F#: Song Data Structure in Separate File

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]
[]

.NET F#: Using Songs File


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.

.NET F#: Finally Oneliner

We are not finished yet.


What is Next 🤔?

Consider continue reading [ F# - Playing with Records - Part Two ].