Wednesday, 26 June 2024

Functional programming - Guarding against nulls with the Option monad

Monads are "elementary individual substances". We can call them "building blocks" in Functional Programming (FP) and FP is built upon such building blocks to compose more complex logic. This article sums up using the

Option

monad, which is described many places on the net and is wellknown. This article is just a walkthrough of this monad, which creates a wrapper for a value that will aid against guarding against nulls. We will effectively abstract away null, and using extension methods we will allow setting up a processing chain where nulls are wrapped inside a value inside the Option objects. First off, define a generic class for this monad and in this case we will consider reference types or classes (this includes strings). Also, equality operators can be added to make it easier to do equality checks. The class will be called Option and we will define two methods called None and Some. The field called _content of type T will be returned inside a Option wrapper instance and None will only return Option object where _content is null.
  • None() signals that the payload or _content is null. None method returns an instance of Option of T where the wrapped value is null.
  • Some() signals that the payload or _content> is not null. some method returns an instance of Option of T where the wrapped value is not null.
  • Both the None() and Some() are of type Option<T> and can be used in a chained expression.
Some concepts of what the Option should support :
  • A map method that allows transforming from T to TResult (both can be same type) along the chained expressions
  • A reduce method that allows retrieving the value inside the Option object that is wrapped
  • Extension methods for going from an object to an Option wrapper of the object
  • Predicate based filter methods for peeling away Option objects where the wrapped object which satisfy the filter can be filtered



public class Option<T> : IEquatable<Option<T>> where T : class {
	
	private T? _content;
	
	private Option(){}

	public static Option<T> None() => new();
	public static Option<T> Some(T obj) => new Option<T> { _content = obj };

	public override bool Equals(object? obj)
	{
		return this.Equals(obj as Option<T>);
	}
	
	public Option<T> Where(Func<T, bool> predicate) => 
		_content is not null && predicate(_content) ? this : Option<T>.None();

	public Option<T> WhereNot(Func<T, bool> predicate) =>
		_content is not null && !predicate(_content) ? this : Option<T>.None();

	public bool Equals(Option<T>? other) => other is null ? false :
		_content?.Equals(other._content) ?? false;
		
	public static bool operator ==(Option<T>? a, Option<T>? b) => 
		a is null ? b is null : a.Equals(b);
		
	public static bool operator !=(Option<T>? a, Option<T>? b) => !(a == b);
		
	public override int GetHashCode()
	{
		return _content?.GetHashCode() ?? 0;
	}

	public Option<TResult> MapOptional<TResult>(Func<T, Option<TResult>> map) where TResult : class =>
		_content is not null ? map(_content) : Option<TResult>.None();

	public Option<TResult> Map<TResult>(Func<T, TResult> map) where TResult : class =>
		new Option<TResult>() { _content = _content is not null ? map(_content) : null };
	
    public T Reduce(T orElse) => _content ?? orElse;
	
	public T Reduce(Func<T> orElse) => _content ?? orElse();
	
} 


We also add some extension methods to help using the monad Option above containing the Some and None methods :

 
 
 public static class OptionExtensions {
	
	public static Option<T> ToOption<T>(this T? obj) where T : class => obj is not null ? Option<T>.Some(obj) : Option<T>.None();

	public static Option<T> Where<T>(this T? obj, Func<T, bool> predicate) where T : class => obj is not null && predicate(obj) ? Option<T>.Some(obj) : Option<T>.None();
	
	public static Option<T> WhereNot<T>(this T? obj, Func<T, bool> predicate) where T : class => obj is not null && !predicate(obj) ? Option<T>.Some(obj) : Option<T>.None();

}

 
Let's also add a ForEach extension method for easier usage in chained expressions of ienumerable collections.
 
 
 public static class IEnumerableExtensions {
	
	public static IEnumerable<T> ForEach<T>(this IEnumerable<T> items, Action<T> action){
		foreach (var item in items){
			action(item);
		}
		return items;
	}
	
}
 
 

The demo code then looks like below, where we iterate through an array of names and grab the initials of the name with some simple logic. Note usage of WhereNot predicate to avoid having to think about null strings further down the chain and also the usage of the Reduce method to get the value which the Option monad wraps.


void Main()
{
	string?[] authors = new[]{
	 "Johan Bojer",
	 null,
	 "Henrik Ibsen",
	 "Kristoffer Updahl",
	};
	
    authors.Select(x => GetInitial(x).Reduce(() => string.Empty)).ForEach(Console.WriteLine);
    
    //using ToOption() method
    authors.Select(a => GetInitial(a.ToOption())).ForEach(o => Console.WriteLine(o.Reduce(() => "?")));

	
	
}

//methods GetInitial below

Option<string> GetInitial(Option<string> name) => name.WhereNot(string.IsNullOrWhiteSpace).Map(s => s.TrimStart().Substring(0, 1).ToUpper());


The example in this article with retrieving initial (first letter) of some names is trivial. The real benefit of using the Option is to safeguard against nulls in lengthy chained calls or other scenarios where you want to always return something and decide how to indicate that we got something back and decide what an empty object will be, in the example the letter "?" signals a null based name.

Sunday, 16 June 2024

Adding synchronous and asynchronous foreach support to Range in C#

In C# 8, we got Range and Index. This allows you to construct a sequence of values. This article will look at diferent ways of adding foreach support in C# to Range. Let's first look at enumeration in the synchronous case. This requires us to add an enumerator to Range as an extension method. We define the following extension method GetEnumerator on Range to achieve this: This requires us to create a custom enumerator class that has these methods :
  • MoveNext
  • Current
The rest of the custom enumerator is happening via the logic in these methods and the constructor of this custom enumerator.


public static class RangeExtensions
{
	public static CustomRangeEnumerator GetEnumerator(this Range range) => new CustomRangeEnumerator(range);

	public class CustomRangeEnumerator
	{

		private int _current;
		private readonly int _end;
		private readonly int _start;
		private readonly bool _isDescending;

		public CustomRangeEnumerator(Range range)
		{
			if (range.End.IsFromEnd)
			{
				throw new NotSupportedException();
			}
			_end = range.End.Value;
			_start = range.Start.Value;
			_isDescending = _start > _end;
			_current = _isDescending ? range.Start.Value + 1 : range.Start.Value - 1;
		}

		public int Current => _current;

		public bool MoveNext()
		{
			if (_isDescending)
			{
				_current--;
				return _current >= _end;
			}
			else
			{
				_current++;
				return _current <= _end;
			}
		}
	}
}



The following code tests out this range enumeration:


Console.WriteLine($"Iterating range 10..1");
	foreach (var num in (10..1))
	{
		Console.WriteLine(num);
	}

	Console.WriteLine($"Iterating range 20..30");
	foreach (var num in 20..30)
	{
		Console.WriteLine(num);
	}


And we get the following output, showing we can enumerate a range both increasing and decreasing :


10
9
8
7
6
5
4
3
2
1

Iterating range 20..30
20
21
22
23
24
25
26
27
28
29
30


Let's look next how we can implement async support for iterations. I first started with having a go with IAsyncEnumerator. But I could not make the cancellation token work, since I could not pass it in. Instead I landed on a method GetItemsAsync that supports cancellation tokens being passed in and will respect cancellation. Here is the extension methods I created :



public static class RangeExtensions
{

	public static async IAsyncEnumerable<int> GetItemsAsync(this Range range, CancellationToken cancellationToken = default)
	{
		await foreach (var num in range)
		{
			if (cancellationToken.IsCancellationRequested){
				Console.WriteLine($"Iteration FAILED: Exiting the IAsyncEnumerable iteration as soon as possible since cancellation was requested at index: {num}");
			}
			cancellationToken.ThrowIfCancellationRequested();
			await Task.Delay(100);
			yield return num;
		}
	}
	
	// Existing asynchronous enumeration with delay and cancellation token
	public static CustomIntAsyncEnumerator GetAsyncEnumerator(this Range range)
		=> new CustomIntAsyncEnumerator(range);

	public class CustomIntAsyncEnumerator : IAsyncEnumerator<int>
	{
		private int _current;
		private readonly int _start;
		private readonly int _end;
		private readonly bool _isDescending;

		public CustomIntAsyncEnumerator(Range range)
		{
			_start = range.Start.Value;
			_end = range.End.Value;
			_isDescending = _start > _end;
			_current = _isDescending ? _start + 1 : _start - 1;
		}

		public int Current => _current;

		public async ValueTask<bool> MoveNextAsync()
		{
			// Wait for the specified delay time
			await Task.Delay(0);

			if (_isDescending)
			{
				if (_current > _end)
				{
					_current--;
					return true;
				}
			}
			else
			{
				if (_current < _end)
				{
					_current++;
					return true;
				}
			}

			return false;
		}

		public ValueTask DisposeAsync()
		{
			// Perform any necessary cleanup here
			return new ValueTask();
		}
	}
}



The code below tests the async enumeration. The benefit of the IAsyncEnumerator is that you can iterate without any hassles with cancellation token, but you should respect cancellation tokens. So the last method call calling the method GetItemsAsync do respect the cancellation token and also accepts such a token being passed in. I tested out the code in this article with Linqpad 7, and developed it using this small IDE.


    var rangeOneHundredDownTo90 = 100..90;

	Console.WriteLine($"Await foreach over variable {nameof(rangeOneHundredDownTo90)}");
	await foreach (var num in rangeOneHundredDownTo90)
	{
		Console.WriteLine(num);
	}

	Console.WriteLine("Async iteration from 140 to 180, waiting 500 ms between each iteration");

	var range = 140..180;

	var cts = new CancellationTokenSource();
	var token = cts.Token;

	await foreach (var num in range.GetItemsAsync(token))
	{
		if (num >= 170)
		{
			cts.Cancel();
		}
		Console.WriteLine(num);
	}



The screen shot below shows that the cancellation token was throwing a OperationCanceledException as was the intent of the demo code.

Saturday, 18 May 2024

Discriminated Union Part Two - The C# side of things

In this article , discriminated unions will be further looked into, continuing from the last article. It visited these topics using F#. The previous article showing the previous article focused on F# and discriminated unions is available here:

https://toreaurstad.blogspot.com/2024/05/discriminated-unions-part-one-f-side-of.html

In this article, C# will be used. As last noted, discriminated unions are a set of types that are allowed to be used. In F#, these types dont have to be in an inheritance chain, they can really be a mix of different types. In C# however, one has to use a base type for the union itself and declare this as abstract, i.e. a placeholder for our discriminated union, called DU from now in this article. C# is a mix of object oriented and functional programming language. It does not support discriminated unions as built-in constructs, such as F#. We must use object inheritance still, but pattern matching in C# with type testing. Lets first look at the POCOs that are included in this example, we must use a base class for our union. In F# we had this:


type Shape =
    | Rectangle of width : float * length : float
    | Circle of radius : float
    | Prism of width : float * depth:float * height : float
    | Cube of width : float


In C# we use an abstract record, since they possess immutability after construction has been made and are therefore a good match for functional programming (FP). Also, records offer a compact syntax which lets itself nice to FP. (we COULD use an abstract class too, but records are now available and lends themsevles better to FP since they are immutable after construction is finished). We could define this abstract record baseclass which will function as a Discriminated Union (DU) like:


public abstract record Shape;


However, keeping note of which types are allowed into the DU is easier if we nest our types. I have also included the methods on the Shape objects as static methods that uses pattern matching with type testing to define the bounds of our DU.



public abstract record Shape
{

	public record Rectangle(float Width, float Length) : Shape;
	public record Circle(float Radius) : Shape;
	public record Prism(float Width, float Depth, float Length) : Shape;
	public record Cube(float Width) : Shape;
	public record Torus(float LargeRadius, float SmallRadius) : Shape; //we will discriminate this shape, not include it in our supported calculations

	public static double CalcArea(Shape shape) => shape switch
	{
		Rectangle rect => rect.Width * rect.Length,
		Circle circ => Math.PI * Math.Pow(circ.Radius, 2),
		Prism prism => 2.0*(prism.Width*prism.Depth) + 2.0*(prism.Width+prism.Depth)*prism.Length,
		Cube cube => 6 * Math.Pow(cube.Width, 2),
		_ => throw new NotSupportedException($"Area calculation for this Shape: ${shape.GetType()}")
	};

	public static double CalcVolume(Shape shape) => shape switch
	{
		Prism prism => prism.Width * prism.Depth * prism.Length,
		Cube cube => Math.Pow(cube.Width, 3),
		_ => throw new NotSupportedException($"Volume calculation for this Shape: ${shape.GetType()}")
	};

};


Sample code of using this source code is shown below:


void Main()
{
	var torus = new Shape.Torus(LargeRadius: 7, SmallRadius: 3);
	//var torusArea = Shape.CalcArea(torus);

	var rect = new Shape.Rectangle(Width: 1.3f, Length: 10.0f);
	var circle = new Shape.Circle(Radius: 2.0f);
	var prism = new Shape.Prism(Width: 15, Depth: 5, Length: 7);
	var cube = new Shape.Cube(Width: 2.0f);

	var rectArea = Shape.CalcArea(rect);
	var circleArea = Shape.CalcArea(circle);
	var prismArea = Shape.CalcArea(prism);
	var cubeArea = Shape.CalcArea(cube);

	//var circleVolume = Shape.CalcVolume(circle);
	var prismVolume = Shape.CalcVolume(prism);
	var cubeVolume = Shape.CalcVolume(cube);
	//var rectVolume = Shape.CalcVolume(rect);

	Console.WriteLine("\nAREA CALCULATIONS:");
	Console.WriteLine($"Circle area: {circleArea:F2}");
	Console.WriteLine($"Prism area: {prismArea:F2}");
	Console.WriteLine($"Cube area: {cubeArea:F2}");
	Console.WriteLine($"Rectangle area: {rectArea:F2}");

	Console.WriteLine("\nVOLUME CALCULATIONS:");
	//Console.WriteLine( "Circle volume: %A", circleVolume);
	Console.WriteLine($"Prism volume: {prismVolume:F2}");
	Console.WriteLine($"Cube volume: {cubeVolume:F2}");
	//Console.WriteLine( "Rectangle volume: %A", rectVolume);
}


I have commented out some lines here, they will throw an UnsupportedException if one uncomments them running the code. The torus forexample lacks support for area and volume calculation by intent, it is not supported (yet). The calculations of the volume of a circle and a rectangle is not possible, since they are 2D geometric figures and not 3D, i.e. do not posess a volume. Output from running the program is shown below:

AREA CALCULATIONS:
Circle area: 12,57
Prism area: 430,00
Cube area: 24,00
Rectangle area: 13,00

VOLUME CALCULATIONS:
Prism volume: 525,00
Cube volume: 8,00

Conclusions F# vs C#

True support for DU is only available in F#, but we can get close to it using C#, inheritance, pattern matching with type checking. F# got much better support for it for now, but C# probably will catch up in a few years and also finally get support for it as a built-in construct. The syntax for DU in F# an C# is fairly similar, using records and pattern switching with type checking makes the code in C# not longer than in F#, but F# got direct support for DU, in C# we have to add additional code to support something that is a built-in functionality of F#. Listed on the page What's new in C# 13, DU has not made their way into the list, .NET 9 Preview SDK will be available probably in November this year (2024).

https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-13

There are different approaches to writing DU in C# for now. Some go for the OneOf operator of functional programming, not presented further in this article. Probably discriminated unions will make their way in .NET 10 in November 2027, so there will still be a lot of waiting around for getting this feature into C#. For now, being aware what the buzz about DU is all about, my two articles on it hopefully made it a bit clearer. One disadvantage of this is that it’s not consistent like in F#. We have to manually manage which types we want to support in each method. However, this is done using inheritance in C#. At the same time, we need to adjust the inheritance hierarchy so that all types inherit from such a discriminated union (DU). If a type needs to be part of MULTIPLE different DUs, we face limitations in C# since we can only inherit from a specific type in the hierarchy. This is likely why many C# developers are requesting DU functionality. As of now, Microsoft’s language team seems to be leaning toward something called ENUM CLASSES. It appears that this feature will be included in .NET 10, which means it won’t be available until 2027

Further viewing/reading of the topic

There are proposals for better support of DU in C# is taking its form now in concrete propals. A proposal for Enum classes are available here, it could be the design choice C# language team lands on:

https://github.com/dotnet/csharplang/blob/main/proposals/discriminated-unions.md

Lead Designer Mads Torgersen comments around DU in C# in this video at 21:00 :

https://learn.microsoft.com/en-us/shows/ask-the-expert/ask-the-expert-whats-new-in-c-100