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]
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]
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]
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]
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]
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]
What is Next 🤔?
We still have interesting concurrency topic.
Consider continue reading [ F# - Playing with Records - Concurrency ].