DigitecGalaxus / Galaxus.Functional

A package bringing popular functional abstractions (e.g. Option or Result) to C#.
Other
37 stars 12 forks source link

LINQ-Extensions using Option<T> #4

Closed phkiener closed 4 years ago

phkiener commented 4 years ago

There are some LINQ methods like SingleOrDefault or FirstOrDefault which just return null if nothing matches. We should leverage the benefit of option to these extensions, providing SingleOrNone and similar extensions returning an Option<T>.

MindSwipe commented 4 years ago

How should values types be handled? As in some cases no element will exist in a collection, but the returned value will be a valid value. Take this example:

var list = new List<int> {0, 1, 2, 3};

// This element does not exist, but the value returned will be 'default(int)', which is 0, a valid element in the list
var element = list.ElementAtOrDefault(5);

The variable element now contains 0, as it is the default of int, and is also an element in our collection, but returning Option<int>.Some(0) feels wrong as we didn't actually find our desired element at index 5. This extends to all extensions I made in my code.

I see a two options:

  1. Return Option<int>.Some(default), like LINQ does
  2. Constrain our extensions to only work on reference types with ... where TSource : class

What would you suggest?

NoelWidmer commented 4 years ago

@MindSwipe Short answer: These new Linq methods would return None (Option<T>.None) if the value is not present.


Explanation: The idea would be to return an Option<T> for all Linq methods that return an optional value.

With Galaxus.Functional we do no longer need to differentiate between reference and value types. Differentiation between these two on a type system level is a design flaw in my opinion (*).

These new Linq methods would return None (Option<T>.None) if the value is not present. Note that None is not the same as 0 or null. None is basically the absence of a reference or value. You won't be able to retrieve a value or reference (not even 0 or null) from an option containing None. This is really important to ensure the elimination of NullReferenceExceptions and accidental default value usage. You can use None like this:

Option<int> x = None.Value or var x = Option<int>.None or var x = default(string).ToOption() or var x = default(int?).ToOption()

(*) Note that the differentiation is helpful for optimization and performance reasons. But it also requires you two write code for both kinds of data. This imposes unnecessary complexity which up to some point can be avoided using Galaxus.Functional. One example where this is useful are these Linq methods.