cli  
 
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


3: Finishing The Task

Map, Filter, Flatten, Unique

Here are the three idiomatic functional programming in C#:

  • map is Select,
  • filter is Where,
  • reduce is Aggregate,

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]

.NET C#: Finishing The Task

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]

.NET C#: Language Integrated Query

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:

  1. Exclude
  2. 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 afilter`. 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]

.NET C#: Exclude Function

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();
    }
  }
}

.NET C#: The MyUtils Class

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

.NET C#: Using MyUtils Class

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