Preface
Goal: Continue Part One
3: Finishing The Task
Map, Filter, Flatten, Unique
Here are the three idiomatic functional programming in C#
:
map
isSelect
,filter
isWhere
,reduce
isAggregate
,
Map (Select)
Extracting the tags fields
using System;
using System.Linq;
class Step06Map
{
public void Show() {
MySongs mySongs = new MySongs();
MySong[] songs = mySongs.songs;
var tagss = songs.Select(song => song.Tags);
foreach(var tags in tagss) {
Console.WriteLine("[{0}]",
string.Join(", ", (tags?? new string[] {})));
}
}
}
With the result as below lines of array of string
:
$ dotnet run
[60s, jazz]
[60s, rock]
[70s, rock]
[70s, pop]
[]
Remember that tagss
is array
of array
.
Filter (Where)
Get rid of null tags
using System;
using System.Linq;
class Step07Filter
{
public void Show() {
MySongs mySongs = new MySongs();
var tagss = (mySongs.songs)
.Select(song => song.Tags)
.Where(tags => tags != null);
foreach(var tags in tagss) {
Console.WriteLine("[{0}]",
string.Join(", ", tags));
}
}
}
With the result as below lines of array of string
:
$ dotnet run
[60s, jazz]
[60s, rock]
[70s, rock]
[70s, pop]
The tagss
is still array
of array
.
Flatten
Just like most functional programming has,
their own built in flatten method.
C#
have this SelectMany
.
using System;
using System.Linq;
class Step08Flatten
{
public void Show() {
MySongs mySongs = new MySongs();
var tags = (mySongs.songs)
.Select(song => song.Tags)
.Where(tags => tags != null)
.SelectMany(tag => tag);
Console.WriteLine("[{0}]",
string.Join(", ", tags));
}
}
With the result as below array of string
:
$ dotnet run
[60s, jazz, 60s, rock, 70s, rock, 70s, pop]
Now the tags
is just an array
.
Unique
And finally built Distinct
method to get unique values.
using System;
using System.Linq;
class Step09Unique
{
public void Show() {
MySongs mySongs = new MySongs();
var tags = (mySongs.songs)
.Select(song => song.Tags)
.Where(tags => tags != null)
.SelectMany(tag => tag)
.Distinct();
Console.WriteLine("[{0}]",
string.Join(", ", tags));
}
}
With the result as below array of string
:
$ dotnet run
[60s, jazz, rock, 70s, pop]
You can spot clearly inthe lambda how the process goes.
song => song.Tags
tags => tags != null
tag => tag
Simplified
However this linq
query is simpler:
var tags = (mySongs.songs)
.Where(song => song.Tags != null)
.SelectMany(song => song.Tags)
.Distinct();
With exactly the same result.
4: Language Integrated Query
You can go further with linq
.
Linq Clause
We can do this cool trick with linq
clause,
using System;
using System.Linq;
class Step10Linq
{
public void Show() {
// 1. Data source.
var songs = new MySongs().songs;
// 2. Query creation.
var tagss =
from song in songs
where song.Tags != null
from tag in song.Tags
select tag;
// 3. Query execution.
var tags = tagss.Distinct();
Console.WriteLine("[{0}]",
string.Join(", ", tags));
}
}
With exactly the same result.
$ dotnet run
[60s, jazz, rock, 70s, pop]
Now you can see how mature C#
is, compared to other language.
No wonder C#
is a choice to be used in industry for daily basis.
Other Linq Example
You can compare query above with my F#
article:
Expression Tree
Despite of my lack of knowldge, linq
is an ORM,
so I won’t discuss in detail.
But here it is, so you can have an overview, of what it does.
using System;
using System.Linq;
using System.Linq.Expressions;
class Step11Tree
{
public void Show() {
MySongs mySongs = new MySongs();
var parameter = Expression.Parameter(typeof(MySong), "MySong");
var type = Type.GetType("MySong").GetProperty("Tags");
var property = Expression.Property(parameter, type);
var constant = Expression.Constant(null);
var comparison = Expression.NotEqual(property, constant);
var lambda = Expression.Lambda<Func<MySong, bool>>(comparison, parameter);
Func<MySong, bool> withTagsFilterExpression = lambda.Compile();
var filteredSongs = (mySongs.songs)
.Where(withTagsFilterExpression);
foreach(var song in filteredSongs) {
Console.WriteLine("[{0}]", song);
}
}
}
With the result as below:
❯ dotnet run
[MySong { Title = Cantaloupe Island, Tags = System.String[] }]
[MySong { Title = Let It Be, Tags = System.String[] }]
[MySong { Title = Knockin' on Heaven's Door, Tags = System.String[] }]
[MySong { Title = Emotion, Tags = System.String[] }]
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 C#
is not as fancy as haskell
, or even OCaml
.
But what we have in C#
is enough to solve our little problem.
Exclude Function
Consider going back to our very first example,
then adding the exclude
function. The
excludefunction is just a
filter`.
For fancier code, I use list comprehension as written in below details:
using System;
using System.Linq;
class Step12Exclude
{
private string[] tags;
public Step12Exclude() {
tags = new string[5]
{"rock", "jazz", "rock", "pop", "pop"};
}
public string[] Exclude(string val, string[] tags) {
var excluded = from tag in tags where tag != val select tag;
return excluded.ToArray();
}
public void Show() {
Console.WriteLine("[{0}]",
string.Join(", ", Exclude("rock", tags)));
}
}
With the result as below array
:
$ dotnet run
[jazz, pop, pop]
Recursive Unique Function
With exclude
function above,
we can build unique
function recursively, as below code:
public string[] Unique(string[] tags) {
if (tags.Length <=1) {
return tags;
} else {
string head = tags.FirstOrDefault();
string[] tail = tags.Skip(1).ToArray();
string[] newhead = new string[1] {head};
string[] newtail = Unique(Exclude(head, tail));
return newhead.Concat(newtail).ToArray();
}
}
public void Show() {
Console.WriteLine("[{0}]",
string.Join(", ", Unique(tags)));
}
With the result as below array
:
$ dotnet run
[rock, jazz, pop]
Although the syntax looks is, not exactly the same as commonly used in functional programming, the algorithm is the same.
Switch Expression
New in C# 8.0
I try to explore newly feature from C# 8.0
and I failed.
Until I found this good article.
Now I can rewrite unique
function above to below code,
with exactly the same result.
public string[] Unique2(string[] tags) {
string head = tags.FirstOrDefault();
string[] tail = tags.Skip(1).ToArray();
string[] newhead = new string[1] {head};
string[] newtail = Unique(Exclude(head, tail));
return newhead.Concat(newtail).ToArray();
}
public string[] Unique(string[] tags) {
return tags switch {
{ Length: 0 } => tags,
{ Length: 1 } => tags,
_ => Unique2(tags)
};
The switch expression
is designed for oneliner.
So we we have to split into two function.
This is a great experience.
Although I decide to discard switch expression
for my use case.
Custom Utility Module
We can, however, make our previous code to be generics. This would be great if we can reuse, the generic unique function into its own module, as written in below code:
using System;
using System.Linq;
class MyUtils
{
public T[] Exclude<T>(T val, T[] tags) where T: class {
var excluded = from tag in tags where tag != val select tag;
return excluded.ToArray();
}
public T[] Unique<T>(T[] tags) where T: class {
if (tags.Length <=1) {
return tags;
} else {
T head = tags.First();
T[] tail = tags.Skip(1).ToArray();
T[] newhead = new T[1] {head};
T[] newtail = Unique(Exclude(head, tail));
return newhead.Concat(newtail).ToArray();
}
}
}
It should be clear what the function do by now.
Using MyUtils Class
Now we can have a very short code.
using System;
class Step15Generics
{
private string[] tags;
public Step15Generics() {
tags = new string[5]
{"rock", "jazz", "rock", "pop", "pop"};
}
public void Show() {
MyUtils myUtils = new MyUtils();
Console.WriteLine("[{0}]",
string.Join(", ", myUtils.Unique(tags)));
}
}
With exactly the same result.
Apply to Songs
We can also apply the module to our songs code. The complete code is as below:
using System;
using System.Linq;
class Step16Unique
{
public void Show() {
MySongs mySongs = new MySongs();
MyUtils myUtils = new MyUtils();
var tags = (mySongs.songs)
.Select(song => song.Tags)
.Where(tags => tags != null)
.SelectMany(tag => tag);
Console.WriteLine("[{0}]", string.Join(", ",
myUtils.Unique(tags.ToArray())));
}
}
With the result as below array
:
$ dotnet run
[60s, jazz, rock, 70s, pop]
What is Next 🤔?
We still have interesting concurrency topic.
Consider continue reading [ C# - Playing with Records - Concurrency ].