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: Continue Part One


4: Using List Comprehension

We can go low level to solve the task using list comprehension.

Yield

The comprehension list in F# have this syntax.

  [ for  in  do if  then yield  ]

Just look at this example

#load "MySongs.fsx"

open System
open MySongs

let unwrap myOption: MyTags = 
   match myOption with | Some x -> x | None -> []

let yieldTagss songs: List<MyTags> =
  [ for (song: MySong) in songs do
      if (song.Tags: Option<MyTags>).IsSome then
        yield (song.Tags |> unwrap)
  ]

[<EntryPoint>]
for tags in (songs |> yieldTagss) do
    tags |> Console.WriteLine

With the result similar as below sequential lines of list:

$ dotnet fsi 09-comprehension.fsx
[60s; jazz]
[60s; rock]
[70s; rock]
[70s; pop]

.NET F#: Yield in List Comprehension

Yield!

In order to push collection, we can use yield! instead.

#load "MySongs.fsx"

open System
open MySongs

let unwrap myOption: MyTags = 
   match myOption with | Some x -> x | None -> []

let yieldTags songs: List<string> =
  [ for (song: MySong) in songs do
      if (song.Tags: Option<MyTags>).IsSome then
        yield! (song.Tags |> unwrap)
  ]

[<EntryPoint>]
songs
  |> yieldTags
  |> fun (tags) ->
     "[" + (tags |> String.concat ", ") + "]"
  |> Console.WriteLine

With the result similar as below list:

$ dotnet fsi 10-songs-flatten.fsx
[60s, jazz, 60s, rock, 70s, rock, 70s, pop]

This is very useful for flattening.

Exclude

Now we can utilize list comprehension to get unique values.

Consider use simpler example as below:

open System

let tags: List<string> =
  ["rock"; "jazz"; "rock"; "pop"; "pop"]

let exclude value tags: List<string> =
  [ for (tag: string) in tags do
      if (tag <> value) then yield tag
  ]

[<EntryPoint>]
tags
  |> exclude("rock")
  |> fun (tags) ->
     "[" + (tags |> String.concat ", ") + "]"
  |> Console.WriteLine

With the result similar as below list:

$ dotnet fsi 11-tags-exclude.fsx
[jazz, pop, pop]

Unique

For unique, we are going to use pattern matching in recursive fashioned. There is no need to use list comprehension.

open System

let tags: List<string> =
  ["rock"; "jazz"; "rock"; "pop"; "pop"]

let exclude value tags: List<string> =
  [ for (tag: string) in tags do
      if (tag <> value) then yield tag
  ]

let rec unique tags: List<string> =
  match tags with
  | [] -> []
  | [head] -> [head]
  | (head::tail) -> [head] @
                    (unique (exclude head tail))

[<EntryPoint>]
tags
  |> unique
  |> fun (tags) ->
     "[" + (tags |> String.concat ", ") + "]"
  |> Console.WriteLine

With the result similar as below list:

$ dotnet fsi 12-tags-unique.fsx
[rock, jazz, pop]

.NET F#: Unique Using Recursive Pattern Matching

Separating Module

To make our code simpler, consider to separate the function to other module.

module MyUtils

#load "MySongs.fsx"
open MySongs

let unwrap myOption: MyTags = 
   match myOption with | Some x -> x | None -> []

let yieldTags songs: List<string> =
  [ for (song: MySong) in songs do
      if (song.Tags: Option<MyTags>).IsSome then
        yield! (song.Tags |> unwrap)
  ]

let exclude value tags: List<string> =
  [ for (tag: string) in tags do
      if (tag <> value) then yield tag
  ]

let rec unique tags: List<string> =
  match tags with
  | [] -> []
  | [head] -> [head]
  | (head::tail) -> [head] @
                    (unique (exclude head tail))

Finishing The Task

Now we can apply to our previous code.

#load "MySongs.fsx"
#load "MyUtils.fsx"

open System
open MySongs
open MyUtils

[<EntryPoint>]
songs
  |> yieldTags
  |> unique
  |> fun (tags) ->
     "[" + (tags |> String.concat ", ") + "]"
  |> Console.WriteLine

With the result similar as below list:

$ dotnet fsi 13-songs-unique.fsx
[60s, jazz, rock, 70s, pop]

.NET F#: Finishing The Task

There is still this linq topic to go.


5: Language Integrated Query

Using Linq

Since F# is a part of .NET, we can utilize the powerful linq library.

I’m a beginner, and I spent hours to cast List to iQueryable. Until Russian F# community helping me. Thank you Ayrat Hudaygulov and Prunkles.

Simple Query

Now we can apply to our previous code.

#load "MySongs.fsx"

open System
open System.Linq
open MySongs

let extractTagss songs =
  query {
    for song in songs do
    where (song.Tags <> None)
    select song.Tags
  }

[<EntryPoint>]
for tags in (songs
    |> Queryable.AsQueryable 
    |> extractTagss
  ) do
    tags |> Console.WriteLine

With the result similar as below list:

❯ dotnet fsi 14-linq-simple.fsx
Some([60s; jazz])
Some([60s; rock])
Some([70s; rock])
Some([70s; pop])

Nested Query

Flatten

Now we can apply to our previous code.

#load "MySongs.fsx"

open System
open System.Linq
open MySongs

let unwrap myOption: MyTags = 
   match myOption with | Some x -> x | None -> []

let extractTags songs =
  query {
    for song in songs do
      where (song.Tags <> None)
      for tag in (song.Tags |> unwrap) do
      select tag
  }

[<EntryPoint>]
songs
  |> Queryable.AsQueryable 
  |> extractTags
  |> Seq.toList |> List.distinct
  |> fun (tags) ->
     "[" + (tags |> String.concat ", ") + "]"
  |> Console.WriteLine

With the result similar as below list:

❯ dotnet fsi 15-linq-nested.fsx
[60s, jazz, rock, 70s, pop]

.NET F#: Language Integrated Query

Other Linq Example

You can compare query above with my C# article:


6: More About Function

Dive Deeper with Functional Programming

Finished Task

Consider to have a look at our last code in part one.

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

Function Composition

We can rewrite the code above with function composition.

let processAll = (mapTagss 
    >> List.concat >> List.distinct
    >> dumpArray   >> Console.WriteLine
  )

[<EntryPoint>]
processAll <| songs

The complete code is as below:

#load "MySongs.fsx"

open System
open MySongs

let unwrap myOption = 
   match myOption with | Some x -> x | None -> []

let dumpArray tags =
   "[" + (tags |> String.concat ", ") + "]"

let mapTagss songs =
    songs |> List.map (
      fun (song: MySong) ->
          (song.Tags |> unwrap)
    )

let processAll = (mapTagss 
    >> List.concat >> List.distinct
    >> dumpArray   >> Console.WriteLine
  )

[<EntryPoint>]
processAll <| songs

With the result similar as below list:

$ dotnet fsi 16-composition.fsx
[60s, jazz, rock, 70s, pop]

.NET F#: Function Composition

Function composition is pretty common in functional world. There is no surprise that F# has this too.

Function Signature

I really like how Haskell write function signature, that make the code looks tidy.

Actually we can write similar things with F#, with slighly different syntax.

#load "MySongs.fsx"

open System
open MySongs

let unwrap: Option<MyTags> -> MyTags
   = fun myOption -> 
     match myOption with | Some x -> x | None -> []

let dumpArray: List<string> -> string
   = fun tags ->
     "[" + (tags |> String.concat ", ") + "]"

let mapTagss: List<MySong> -> List<MyTags>
  = fun songs ->
    songs |> List.map (
      fun (song: MySong) ->
          (song.Tags |> unwrap )
    )

let processAll: List<MySong> -> unit
  = (mapTagss 
      >> List.concat >> List.distinct
      >> dumpArray >> Console.WriteLine
    )

[<EntryPoint>]
processAll <| songs

With the result similar as below list:

$ dotnet fsi 17-signature.fsx
[60s, jazz, rock, 70s, pop]

.NET F#: Function Signature


What is Next 🤔?

We still have interesting concurrency topic.

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