Functional programming is the oldest of the three major programming paradigms, none the less it is the last which gets wide spread usage. Even in languages like C++, Java or C# we want to use a functional style of programming.

Linq is the first Monad which got wide spread use in C#, and most C# programmers were not even aware of it beeing a monad, which probably helped.

Mark Seemann points out that "Unfortunately, Maybe implementations often come with an API that enables you to ask a Maybe object if it's populated or empty, and a way to extract the value from the Maybe container. This misleads many programmers [...]"

This library is based on his example code, and should grow slowly to a library which helps to use and understand the Functional programming paradigm. Functional programming is side-effect free and the strong type system can be used to make illegal state impossible.

Use functional programming as an additional asset to write correct code.

Migrating from 2.x to 3.0

  1. Update to the latest 2.x version of Funcky (2.7.1) and fix all warnings.
  2. Update to Funcky 3.0.
  3. Run dotnet format analyzers. This will migrate Option<T>.None() to Option<T>.None for you.
    This command sometimes fails while loading your project(s). --no-restore might help with that.
  4. Build and fix other compilation failures.
  5. You might need to re-run dotnet format analyzers after fixing other errors.
  6. Important: Check if you're using System.Text.Json to serialize Options. This will no longer work automatically. You need to add the OptionJsonConverter yourself.

Functional Programming

  • No side effects
  • Pure functions
  • Referential transparency
  • Follow the types
  • Higher order functions
  • Composition

Simplify if null by using an Option

We start off with the following code: (Note that some types have been omitted for brevity)

#nullable enable

using System;

public class Example
{
    public VersionEnvironment? GetCurrentVersionEnvironment(PackageName packageName)
    {
        var currentVersion = ReadCurrentVersion(packageName);

        if (currentVersion is null)
        {
            return null;
        }

        var versionPath = GetVersionPath(packageName, currentVersion);
        return new VersionEnvironment(currentVersion, versionPath);
    }

    public PackageVersion? ReadCurrentVersion(PackageName name) => null; // Real implementation omitted

    public string GetVersionPath(PackageName name, PackageVersion version) => null!; // Real implementation omitted
}

The function GetCurrentVersionEnvironment doesn't do much, but it's not pleasant to look at, because of that early return condition.

Step Ⅰ

We start off by changing the return type of ReadCurrentVersion from PackageVersion? to an Option:

public Option<PackageVersion> ReadCurrentVersion(PackageName name) => Option.None<PackageVersion>(); // Real implementation omitted

Step Ⅱ

This immediately breaks the GetCurrentVersionEnvironment, because our null check no longer makes sense. Instead of checking for null and returning early, we can use Select on the Option to project its value.

public Option<VersionEnvironment> GetCurrentVersionEnvironment(PackageName packageName)
{
    return ReadCurrentVersion(packageName)
        .Select(currentVersion => {
            var versionPath = GetVersionPath(packageName, currentVersion);
            return new VersionEnvironment(currentVersion, versionPath);
        });
}

Step Ⅲ

This is already much simpler, since we've got rid of that explicit null check.

There's still room for simplification, since our projection takes up two lines and we ideally only want a single expression in our projection.

We can achieve this by translating our expression to query syntax:

public Option<VersionEnvironment> GetCurrentVersionEnvironment(PackageName packageName)
{
    return from currentVersion in ReadCurrentVersion(packageName)
           let versionPath = GetVersionPath(packageName, currentVersion)
           select new VersionEnvironment(currentVersion, versionPath);
}

Step Ⅳ

Since our method now consists of only one expression, we can make it an expression body:

public Option<VersionEnvironment> GetCurrentVersionEnvironment(PackageName packageName)
    => return from currentVersion in ReadCurrentVersion(packageName)
              let versionPath = GetVersionPath(packageName, currentVersion)
              select new VersionEnvironment(currentVersion, versionPath);

Formatting a calendar page

This case study is taken from Component programming with ranges by By H. S. Teoh written for D.

We shall use as example the classic task of laying out a yearly calendar on the console, such that given a particular year, the program will print out a number of lines that displays the 12 months in a nice grid layout, with numbers indicating each day within the month. Something like this:

       January              February                March        
                 1  2      1  2  3  4  5  6         1  2  3  4  5
  3  4  5  6  7  8  9   7  8  9 10 11 12 13   6  7  8  9 10 11 12
 10 11 12 13 14 15 16  14 15 16 17 18 19 20  13 14 15 16 17 18 19
 17 18 19 20 21 22 23  21 22 23 24 25 26 27  20 21 22 23 24 25 26
 24 25 26 27 28 29 30  28 29                 27 28 29 30 31
 31

         April                  May                  June  
                 1  2   1  2  3  4  5  6  7            1  2  3  4
  3  4  5  6  7  8  9   8  9 10 11 12 13 14   5  6  7  8  9 10 11
 10 11 12 13 14 15 16  15 16 17 18 19 20 21  12 13 14 15 16 17 18
 17 18 19 20 21 22 23  22 23 24 25 26 27 28  19 20 21 22 23 24 25
 24 25 26 27 28 29 30  29 30 31              26 27 28 29 30

         July                 August               September  
                 1  2      1  2  3  4  5  6               1  2  3
  3  4  5  6  7  8  9   7  8  9 10 11 12 13   4  5  6  7  8  9 10
 10 11 12 13 14 15 16  14 15 16 17 18 19 20  11 12 13 14 15 16 17
 17 18 19 20 21 22 23  21 22 23 24 25 26 27  18 19 20 21 22 23 24
 24 25 26 27 28 29 30  28 29 30 31           25 26 27 28 29 30
 31

        October              November              December  
                    1         1  2  3  4  5               1  2  3
  2  3  4  5  6  7  8   6  7  8  9 10 11 12   4  5  6  7  8  9 10
  9 10 11 12 13 14 15  13 14 15 16 17 18 19  11 12 13 14 15 16 17
 16 17 18 19 20 21 22  20 21 22 23 24 25 26  18 19 20 21 22 23 24
 23 24 25 26 27 28 29  27 28 29 30           25 26 27 28 29 30 31
 30 31

While intuitively straightforward, this task has many points of complexity.

Although generating all dates in a year is trivial, the order in which they must be processed is far from obvious. Since we're writing to the console, we're limited to outputting one line at a time; we can't draw one cell of the grid and then go back up a few lines, move a few columns over, and draw the next cell in the grid. We have to somehow print the first lines of all cells in the top row, followed by the second lines, then the third lines, etc., and repeat this process for each row in the grid. Of course, we could create an internal screen buffer that we can write to in arbitrary order, and then output this buffer line-by-line at the end, but this approach is not as elegant because it requires a much bigger memory footprint than is really necessary.

In any case, as a result of this mismatch between the structure of the calendar and the structure of the output, the order in which the days in a month must be processed is not the natural, chronological order. We have to assemble the dates for the first weeks in each of the first 3 months, say, if we are putting 3 months per row in the grid, print those out, then assemble the dates for the second weeks in each month, print those out, etc.. Furthermore, within the rows representing each week, some days may be missing, depending on where the boundaries of adjacent months fall; these missing days must be filled out in the following month's first week before the first full week in the month is printed. It is not that simple to figure out where a week starts and ends, and how many rows are needed per month. Then if some months have more full weeks than others, they may occupy less vertical space than other months on the same row in the grid; so we need to insert blank spaces into these shorter months in order for the grid cells to line up vertically in the output.

With this level of complexity, writing our calendar program using the traditional ad hoc way of resolving structure conflicts will certainly result in very complex, hard-to-understand, and bug-prone code. There would not be much hope of getting any reusable pieces out of it.

Nonetheless the end result will look pretty simple, and it will be completley streamable.

Create the date-range

There are obviously many ways to achieve this, you could write your own Implementation of IEnumerable<T> with a hand written IEnumerator<T> or you could simply write a function and take advantage of yield to create the iterator.

We want to avoid to write such a function and take advantage of the numerous generators availaible in Funcky.

Sequence.Successors creates an infinite sequence of days, starting with January first of the given year. We take all the values in the same year, so this sequence should yield all the days of a year.

return Sequence.Successors(JanuaryFirst(year), NextDay)
    .TakeWhile(IsSameYear(year));

Most of these helper function are straight forward, but IsSameYear might be a bit special if you havent worked with curried functions before.

The function IsSameYear takes one parameter and returns a function which takes another parameter, this is also called the curried form of a function, there is also the Functional.Curry and Functional.Uncurry functions which can transform between both forms without the need to write them both. IsSameYear(2000) returns a function which always returns true if the Date is from the year 2000. That way of using functions might come in handy a lot more often than you think.

private static DateOnly NextDay(DateOnly day)
    => day.AddDays(1);

private static DateOnly JanuaryFirst(int year)
    => new(year: year, month: 1, day: 1);

private static Func<DateOnly, bool> IsSameYear(int year)
    => day
        => day.Year == year;

This makes the the main body of the code semantic very easy to understand. All the helper functions are trivially to understand on it's own too.

Group this into months

LINQ offers you the GroupBy function which we could use easily in this case. This way we would have 12 groups, in each group we would have all the days of one month of the given year.

return Sequence.Successors(JanuaryFirst(year), NextDay)
    .TakeWhile(IsSameYear(year))
    .GroupBy(d => d.Month)

There are two things we should consider here though.

1.) GroupBy is like the SQL GROUP BY and can rearrange elements, and therefore is not a lazy Extension function. 2.) GroupBy would also Group all days from a different year into the same 12 montly buckets.

Often that is exactly what you want, but in this case if we think of an endless stream of days this is not what we need at all. The days do not need to be rearranged, all days in the same month are next to each other and if we find a second January, we would like to have a new 13th bucket.

That is why Funcky offers the extension function AdjacentGroupBy. It is a fully lazy, forward only GroupBy. Exactly what we need here.

return Sequence.Successors(JanuaryFirst(year), NextDay)
    .TakeWhile(IsSameYear(year))
    .AdjacentGroupBy(d => d.Month);

Now we have an IEnumerable<IEnumerable<DateOnly>>, where the inner IEnumerable is representing a single month.

Layout a single month

Since we have now all the days of the same month, we want to create a layout for a single month out of these days.

Currently we have a sequence of months. To transform the inner element, from a sequence of days into something else, we need to make a projection which is done with Select in C#.

return Sequence.Successors(JanuaryFirst(year), NextDay)
    .TakeWhile(IsSameYear(year))
    .AdjacentGroupBy(d => d.Month);
    .Select(LayoutMonth)

The LayoutMonth function therefore gets a sequence of all the day days in a single month, and should produce a Layout for that single Month.

This is actually pretty straight forward. What do we want? We want to format a single month, and how would a single month look like? So the result will be a sequence of strings, each string representing one line of the layout.

        April       
                1  2
 3  4  5  6  7  8  9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30

First, we decompose the problem once more: we start with one line where we print the month, then a few lines which represent a week and we also have an empty line at the end, but that is just layout sugar.

We need a few helper functions again: the most important step is grouping the days by week, and then we pass each week to a FormatWeek function.

private static IEnumerable<string> LayoutMonth(IEnumerable<DateOnly> month)
{
    yield return CenteredMonthName(month);

    foreach (var week in month.AdjacentGroupBy(GetWeekOfYear).Select(FormatWeek))
    {
        yield return week;
    }

    yield return $"{string.Empty,WidthOfAWeek}";
} 

This is a fine way to do this, it is very easy to read, but the foreach is really annoying. We already have an IEnumerable<T>, we just want to add it between the first and the last line. But with yield you are often limited to very procedural constructs.

Often you can avoid that, for simple cases we have Sequence.Return, Concat and other helpers, in this case though the nicest way is probaly creating an ImmutableList, because the syntax allows to combine ranges and single items elegantly.

private static IEnumerable<string> LayoutMonth(IEnumerable<DateOnly> month)
    => ImmutableList<string>.Empty
        .Add(CenteredMonthName(month))
        .AddRange(FormatWeeks(month))
        .Add(new string(' ', WidthOfAWeek));

Let's dive into our helper functions. First we take a look at the name of the month. The only noteworthy detail is the very functional mindest seen in the solution to the centering problem. It uses a pattern match to fill in the missing spaces: it is not very efficent, but easy to understand. The recursion will be very short because our lines are only 21 characters wide.

private static string CenteredMonthName(IEnumerable<DateOnly> month)
    => month
        .First()
        .ToString(MonthNameFormat)
        .Center(WidthOfAWeek);


internal static class StringExtensions
{
    public static string Center(this string text, int width)
        => (width - text.Length) switch
        {
            0 => text,
            1 => $" {text}",
            _ => Center($" {text} ", width),
        };
}

We have already seen the heart of FormatWeeks in the yield solution, but now it is a separate function. FormatWeeks again needs 2 very simple helper functions, the first one projects the week of the year, the other one will format a sequence of days.

The sequence of days can be either a complete week, or a partial week from the beginning or the end of the month. But because of the way we construct these sequences, there always is at least one element in it.

private static IEnumerable<string> FormatWeeks(IEnumerable<DateOnly> month)
    => month
        .AdjacentGroupBy(GetWeekOfYear)
        .Select(FormatWeek);

The GetWeekOfYear function is just calling the API with the correct parameters and always using the current culture and a little fiddling with DateTime (because DateOnly was introduced far too late in C# 10).

private static int GetWeekOfYear(DateOnly dateTime)
    => CultureInfo
        .CurrentCulture
        .Calendar
        .GetWeekOfYear(dateTime.ToDateTime(default), CultureInfo.CurrentCulture.DateTimeFormat.CalendarWeekRule, CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek);

We are almost done with FormatMonth, now we really format the week, each day has a width of 3 characters, we see that in FormatDay. This means each line should have the same width of 21 characters. We simply pad the week correctly with the PadWeek function.

private static string FormatWeek(IGrouping<int, DateOnly> week)
    => PadWeek(week.Select(FormatDay).ConcatToString(), week);
  
private static string FormatDay(DateOnly day)
    => $"{day.Day,WidthOfDay}";

We can ignore the full weeks, because they are already 21 characters long. How do we distinguish the beginning of the month from the end? The week at the end of the month must start with the first day of the week. So we pad accordingly from the left or the right.

private static string PadWeek(string formattedWeek, IGrouping<int, DateOnly> week)
    => StartsOnFirstDayOfWeek(week)
        ? $"{formattedWeek,-WidthOfAWeek}"
        : $"{formattedWeek,WidthOfAWeek}";

Now it just boils down to, what is the start of the week? It might be a surprise, that this is actually the most difficult part of this program, because the DayOfWeek enum defines Sunday as the first day of the week, which is not true for the largest part of the world, including all of europe. But CultureInfo has our back, because it tells us in the DateTimeFormat, which day of the week is the first day. However even that needs some calculation with % DaysInAWeek.

The function NthDayOfWeek gives us a 0 based index beginning with the start of the week. So we can simply check with is FirstDayOfTheWeek that we are indeed on the first day of the week. And this works independent of the given culture. Sweet.

private static bool StartsOnFirstDayOfWeek(IGrouping<int, DateOnly> week)
    => NthDayOfWeek(week.First().DayOfWeek) is FirstDayOfTheWeek;

private static int NthDayOfWeek(DayOfWeek dayOfWeek)
    => (dayOfWeek + DaysInAWeek - CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek) % DaysInAWeek;

Now we have an IEnumerable<IEnumerable<string>>, where the inner one is still representing a single month, but each line is already a completeley formatted week.

Layouting the months together.

At this point we could print a list of months, we just would need to join all the lines together and it would look like this. (shortend to 2 months)

        January      
        1  2  3  4  5
  6  7  8  9 10 11 12
 13 14 15 16 17 18 19
 20 21 22 23 24 25 26
 27 28 29 30 31      

       February      
                 1  2
  3  4  5  6  7  8  9
 10 11 12 13 14 15 16
 17 18 19 20 21 22 23
 24 25 26 27 28 29   

The next step looks simple: we would need to take line 1, 8, 15 and create the first line of the final output:

       January              February                March        

And then lines 2, 9 and 16 would be combined to:

        1  2  3  4  5                 1  2                    1

To do this lazily we use Chunk.

Chunk is supported with .NET 6, before that Funcky has nearly identical Replacement. (The return type is slightly different)

Chunk is grouping a sequnce into multiple sequnces of the same length. In our case we want to make the sequence of 12 months in to 4 sequences of length 3.

const MonthsPerRow = 3;

private static string CreateCalendarString(int year)
    => Sequence.Successors(JanuaryFirst(year), NextDay)
        .TakeWhile(IsSameYear(year))
        .AdjacentGroupBy(day => day.Month)
        .Select(LayoutMonth)
        .Chunk(MonthsPerRow);

That means we have now an IEnumerable<IEnumerable<IEnumerable<string>>> where in the outermost IEnumerable we group 3 months together respectivly.

Why does that help us with the layout? To create the first line of our final layout, we need the first lines of each month, and then the second, and so on. So we need to group the months together.

One of these chunks now looks like this:

        Januar        |         1  2  3  4  5 |   6  7  8  9 10 11 12 |  13 14 15 16 17 18 19 |  20 21 22 23 24 25 26 |  27 28 29 30 31       |
       Februar        |                  1  2 |   3  4  5  6  7  8  9 |  10 11 12 13 14 15 16 |  17 18 19 20 21 22 23 |  24 25 26 27 28 29    |
         März         |                     1 |   2  3  4  5  6  7  8 |   9 10 11 12 13 14 15 |  16 17 18 19 20 21 22 |  23 24 25 26 27 28 29 |  30 31                |

That actually already looks a lot like what we want, but we want the months on the top not on the left.

It also looks like a matrix, and on a matrix the operation to flip the rows and the columns (on a diagonal symmetry) is called transpose.

Funcky has a lazy Transpose extension function for IEnumerable<IEnumerable<T>> as long as it is a regular matrix (same length for each subsequence).

private static string CreateCalendarString(int year)
    => Sequence.Successors(JanuaryFirst(year), NextDay)
        .TakeWhile(IsSameYear(year))
        .AdjacentGroupBy(day => day.Month)
        .Select(LayoutMonth)
        .Chunk(MonthsPerRow)
        .Select(chunk => chunk.Transpose())

After this transforamtion our chunk of 3 months looks like this:

        Januar        |        Februar        |          März
        1  2  3  4  5 |                  1  2 |                     1
  6  7  8  9 10 11 12 |   3  4  5  6  7  8  9 |   2  3  4  5  6  7  8
 13 14 15 16 17 18 19 |  10 11 12 13 14 15 16 |   9 10 11 12 13 14 15
 20 21 22 23 24 25 26 |  17 18 19 20 21 22 23 |  16 17 18 19 20 21 22
 27 28 29 30 31       |  24 25 26 27 28 29    |  23 24 25 26 27 28 29
                      |                       |  30 31

I think it is obvious that at this point, we are done. We just have to join the chunks together to have our final output.

private static string CreateCalendarString(int year)
    => Sequence.Successors(JanuaryFirst(year), NextDay)
        .TakeWhile(IsSameYear(year))
        .AdjacentGroupBy(day => day.Month)
        .Select(LayoutMonth)
        .Chunk(MonthsPerRow)
        .Select(EnumerableExtensions.Transpose)
        .Select(JoinLine)
        .SelectMany(Identity)
        .JoinToString(Environment.NewLine);

For a working solution with all the details, you should take a look at the complete code in Program.cs



Program.cs

The complete calendar program:

using System.Collections.Immutable;
using Funcky.Extensions;
using Funcky;
using Funcky.Monads;
using static System.Globalization.CultureInfo;
using static Funcky.Functional;

namespace Calendar;

internal static class StringExtensions
{
    public static string Center(this string text, int width)
        => (width - text.Length) switch
        {
            0 => text,
            1 => $" {text}",
            _ => Center($" {text} ", width),
        };
}

internal class Program
{
    private const int FirstDayOfTheWeek = 0;
    private const int MonthsPerRow = 3;
    private const int DaysInAWeek = 7;
    private const int WidthOfDay = 3;
    private const int WidthOfAWeek = WidthOfDay * DaysInAWeek;
    private const string MonthNameFormat = "MMMM";

    private static void Main(string[] args)
        => Console.WriteLine(CreateCalendarString(ExtractYear(args).GetOrElse(DateTime.Now.Year)));

    private static Option<int> ExtractYear(IEnumerable<string> args)
        => args.FirstOrNone()
            .AndThen(ParseExtensions.ParseInt32OrNone);

    private static string CreateCalendarString(int year)
        => Sequence.Successors(JanuaryFirst(year), NextDay)
            .TakeWhile(IsSameYear(year))
            .AdjacentGroupBy(day => day.Month)
            .Select(LayoutMonth)
            .Chunk(MonthsPerRow)
            .Select(EnumerableExtensions.Transpose)
            .Select(JoinLine)
            .SelectMany(Identity)
            .JoinToString(Environment.NewLine);

    private static DateOnly NextDay(DateOnly day)
        => day.AddDays(1);

    private static DateOnly JanuaryFirst(int year)
        => new(year: year, month: 1, day: 1);

    private static Func<DateOnly, bool> IsSameYear(int year)
        => day
            => day.Year == year;

    private static IEnumerable<string> JoinLine(IEnumerable<IEnumerable<string>> sequence)
        => sequence.Select(t => t.JoinToString(" "));

    private static IEnumerable<string> LayoutMonth(IEnumerable<DateOnly> month)
        => ImmutableList<string>.Empty
            .Add(CenteredMonthName(month))
            .AddRange(FormatWeeks(month))
            .Add(new string(' ', WidthOfAWeek));

    private static IEnumerable<string> FormatWeeks(IEnumerable<DateOnly> month)
        => month
            .AdjacentGroupBy(GetWeekOfYear)
            .Select(FormatWeek);

    private static string FormatWeek(IGrouping<int, DateOnly> week)
        => PadWeek(week.Select(FormatDay).ConcatToString(), week);

    private static string FormatDay(DateOnly day)
        => $"{day.Day,WidthOfDay}";

    private static string PadWeek(string formattedWeek, IEnumerable<DateOnly> week)
        => StartsOnFirstDayOfWeek(week)
            ? $"{formattedWeek,-WidthOfAWeek}"
            : $"{formattedWeek,WidthOfAWeek}";

    private static bool StartsOnFirstDayOfWeek(IEnumerable<DateOnly> week)
        => NthDayOfWeek(week.First().DayOfWeek) is FirstDayOfTheWeek;

    private static int NthDayOfWeek(DayOfWeek dayOfWeek)
        => (dayOfWeek + DaysInAWeek - CurrentCulture.DateTimeFormat.FirstDayOfWeek) % DaysInAWeek;

    private static int GetWeekOfYear(DateOnly dateTime)
        => CurrentCulture
            .Calendar
            .GetWeekOfYear(
                dateTime.ToDateTime(default),
                CurrentCulture.DateTimeFormat.CalendarWeekRule,
                CurrentCulture.DateTimeFormat.FirstDayOfWeek);

    private static string CenteredMonthName(IEnumerable<DateOnly> month)
        => month
            .First()
            .ToString(MonthNameFormat)
            .Center(WidthOfAWeek);
}

Option Monad

What is the Option Monad

The Option Monad is a very simple algebraic type which is a fancy way to say you can have more than one different type of data in it. The Option monad is a combination of a value of a type and a second type which can only be one value: None. The state of the option monad is always either None, or Some with a certain value of a type you can chose. It means it can hold any value of your chosen type + None state.

This is very similar to references which can be null or Nullable Value Types which adds the "has no value"-concept to value types.

However the main issue with references and Nullable<T> is, before every access you need to check if the value is accessible. The Option Monad is an abstraction which removes all this boilerplate code, in a save way.

Create something

var something = Option.Some(1337);

Create nothing

var nothing = Option<int>.None();

Select

Option<bool> maybeBool =
    from m in maybe
    select m == 1337;

Select Many

var result = from number in someNumber
    from date in someDate
    select Tuple.Create(number, date);

Match

bool isSome = maybe.Match(
    none: false,
    some: m => true
);

How can I get the value?

If you declare

int? integer = 1337;

You can access the Value directly via i.Value. The typical beginner question on the monad is therefore how to get to the value in a monad.

The Option-Monad intentionally has no way to get to the value directly because that would be an unsafe operation. The whole point of an optional is that it sometimes has no value. Instead you should inject the behaviour into the monad.

The basic Example:

int? integer = MaybeValue();

if (integer.HasValue())
{
    Console.WriteLine($"Value: {integer.Value}");
}

Injecting the behaviour:

Option<int> integer = MaybeValue();

integer
  .AndThen(i => Console.WriteLine($"Value: {i}"));

Or in Linq syntax:

from integer in MaybeValue()
select Console.WriteLine($"Value: {integer.Value}");

The TryVerb-pattern

The TryVerb pattern is used in several instances in C# as an alternative for functions which throw an exception.

Parse throws an exception if inputString is not a number.

var number = int.Parse(inputString);

TryParse returns false in such a case, so the number is always correct if TryParse returns true. This means you have to check the return value before accessing number.

if (int.TryParse(inputString, out number)) 
{
    // ...
}

Out parameters are bad, and in consequence we think the TryVerb-pattern (TryGet, TryParse...) used in C# as an anti-pattern.

We have added an overload for each and every "Try" function we have found in the .NET Framework and we give an alternative in the Form OrNone.

GetValueOrNone

Extension functions have been added to IDictionary and IReadOnlyDictionary

GetValuesOrNone

The parse functions

Option<int> = "1234".ParseIntOrNone();

The static class Funcky.Functional is designed to be used with a static import (using static Funcky.Functional;). All examples will be as if using static was used. All the methods in Funcky.Functional are named to be easily understood without the functional prefix. They are general purpose, and their goal is to unify typical boilerplate code or be more expressive than typical C# ways of doing their job.

The NoOperation function is a more expressive way of manually creating an empty statement as a parameter to a method expecting a Action/Action<T>, supporting from 0 up to 8 generic parameters.

Example 1:

// The function we want to call:
public void DoSomething(int value, Action<int> callback)
{
  // ...
}

// How we would usually call it when we don't need the callback:
DoSomething(2, _ => {});

// How you can call it with NoOperation:
DoSomething(2, NoOperation);

NoOperation becomes especially useful when a Action<T> with many parameters is expected.

Example 2:

// The function we want to call:
public void DoSomething(int value, Action<int, string, float, AnyCustomClassThatYouWant> callback)
{
  // ...
}

// How we would usually call it when we don't need the callback (C#9):
DoSomething(2, (_, _, _, _) => {});
// Before C#9, this was even worse:
DoSomething(3, (_, __, ___, ____) => {});

// How you can call it with NoOperation:
DoSomething(2, NoOperation);

NoOperation is also useful when you want to use a expression body for a method.

Example 3:

// Abstract class:
public abstract class SomeClassWithExecuteAndHookBase
{
  public void Execute();
  
  protected abstract void PostExecutionHook();
}

// Derived class that doesn't have any use for the PostExecutionHook usually:
public class SomeClassWithExecuteAndHookDerived : SomeClassWithExecuteAndHookBase
{
  protected override void PostExecutionHook()
  {
  }
}

// Derived class that doesn't have any use for the PostExecutionHook with NoOperation:
public class SomeClassWithExecuteAndHookDerived : SomeClassWithExecuteAndHookBase
{
  protected override void PostExecutionHook() => NoOperation();
}

The Identity function is designed to replace parameter-returning lambdas, like sometimes used in LINQ.

Example 1:

// Method:
public void FunctionExpectingSelector<TIn, TOut>(Func<TIn, TOut> selector)
{
  // ...
}

// Usually:
FunctionExpectingSelector(x => x);
// Or:
FunctionExpectingSelector(item => item);

// With Identity:
FunctionExpectingSelector(Identity);

Example 2 (typical SelectMany selector):

// Usually result of a query:
IEnumerable<IEnumerable<int>> itemGroups = new[] { new[] { 1, 2, 3 }, new[] { 5, 6, 7 } };

// Goal: Get all items flattened.
// Common approach:
itemGroups.SelectMany(x => x);
// Or:
itemGroups.SelectMany(items => items);

// With Identity:
itemGroups.SelectMany(Identity);

The unit type is an alternative to the native C# struct Void (with the global alias void).

The C# compiler handles Void/void very different to all other types in C#:

  • Void can never be used. You must always use void
  • void can not be instantiated
  • void can not be used as a generic argument to any function
  • A function (sometimes also called method) "returning" void does not return a value, and the result of the function (void) can not be assigned to any variable
  • When using reflection, void-methods will return null because the compiler can not know during compile-time what the dynamically dispatched method will return

These limitations on the void type introduce annoying behaviour, especially with expressions and generics.

Example 1 - the "void is not a real type" dilemma:

This examples uses a simple algebraic datatype with a generic match method:

public abstract class SimpleAlgebraicDatatype
{
	private SimpleAlgebraicDatatype()
	{
	}

	public abstract TResult Match<TResult>(
		Func<Variant1, TResult> variant1,
		Func<Variant2, TResult> variant2);

	public class Variant1 : SimpleAlgebraicDatatype
	{
		public Variant1(string someValue)
		{
			SomeValue = someValue;
		}
		
		public string SomeValue { get; }

		public override TResult Match<TResult>(
			Func<Variant1, TResult> variant1, 
			Func<Variant2, TResult> variant2)
			=> variant1(this);
	}

	public class Variant2 : SimpleAlgebraicDatatype
	{
		public Variant2(int someValue)
		{
			SomeValue = someValue;
		}
		
		public int SomeValue { get; }

		public override TResult Match<TResult>(
			Func<Variant1, TResult> variant1, 
			Func<Variant2, TResult> variant2)
			=> variant2(this);
	}
}

It then can be used like that:

SimpleAlgebraicDatatype variant = new SimpleAlgebraicDatatype.Variant1("Hey");
// get the variant as string
var value = variant.Match(
	variant1: variant1 => variant1.SomeValue,
	variant2: variant2 => Convert.ToString(variant2.SomeValue));
Console.WriteLine(value);

But if you don't want to use the return value, you're stuck with returning a value you don't want. This does not compile:

// Error [CS0411] The type arguments for method 'method' cannot be inferred from the usage. Try specifying the type arguments explicitly.
variant.Match(
	variant1: variant1 => Console.Write(variant1.SomeValue),
	variant2: variant2 => Console.Write(Convert.ToString(variant2.SomeValue)));

Now you have to decide what it returns. One option is to return null - the best fitting return type is probably object? in that case:

variant.Match<object?>(
	variant1: variant1 =>
	{
		Console.Write(variant1.SomeValue);
		return null;
	},
	variant2: variant2 =>
	{
		Console.Write(Convert.ToString(variant2.SomeValue));
		return null;
	});

This is very unstatisfying however. We have to trick the type-system. There should be a more expressive way.

Funcky.Unit to the rescue:

variant.Match(
	variant1: variant1 =>
	{
		Console.Write(variant1.SomeValue);
		return Unit.Value;
	},
	variant2: variant2 =>
	{
		Console.Write(Convert.ToString(variant2.SomeValue));
		return Unit.Value;
	});

Now this isn't really less noise. This is why we created ActionToUnit.

This clears up the code to:

variant.Match(
	variant1: variant1 => ActionToUnit(() => Console.Write(variant1.SomeValue)),
	variant2: variant2 => ActionToUnit(() => Console.Write(Convert.ToString(variant2.SomeValue))));

See ActionToUnit for an explanation.

Example 2 - the "switch expression must return something" dilemma:

The following two code snippes do not comple:

// Error [CS0201]: Only assignment, call, increment, decrement, and new object expressions can be used as a statement.
variant switch
{
	SimpleAlgebraicDatatype.Variant1 variant1 => Console.Write(variant1.SomeValue),
	SimpleAlgebraicDatatype.Variant2 variant2 => Console.Write(Convert.ToString(variant2.SomeValue)),
	_ => throw new Exception("Unreachable"),
};

// Error [CS0029]: Cannot implicitly convert type 'thorw-expression' to 'void'
// Error [CS9209]: A value of type 'void' may not be assigned.
_ = variant switch
{
	SimpleAlgebraicDatatype.Variant1 variant1 => Console.Write(variant1.SomeValue),
	SimpleAlgebraicDatatype.Variant2 variant2 => Console.Write(Convert.ToString(variant2.SomeValue)),
	_ => throw new Exception("Unreachable"),
};

One way to resolve this dilemma is to return a method that returns null from every arm, and execute it after:

// very verbose and not very readable
Func<SimpleAlgebraicDatatype, object> action = variant switch
{
	SimpleAlgebraicDatatype.Variant1 variant1 => _ =>
	{
		Console.Write(Convert.ToString(variant1.SomeValue));
		return null;
	},
	SimpleAlgebraicDatatype.Variant2 variant2 => _ =>
	{
		Console.Write(Convert.ToString(variant2.SomeValue));
		return null;
	},
	_ => _ => throw new Exception("Unreachable"),
};
action(variant);

If we use ActionToUnit once again, we can simplify this code, by a lot:

_ = variant switch
{
	SimpleAlgebraicDatatype.Variant1 variant1 => ActionToUnit(() => Console.Write(variant1.SomeValue)),
	SimpleAlgebraicDatatype.Variant2 variant2 => ActionToUnit(() => Console.Write(Convert.ToString(variant2.SomeValue))),
};

If you cannot use the discard syntax (_ =), simply use var _ or similar, and ignore the variable after.

The ActionToUnit function wraps a action into a function that returns a Funcky.Unit instance.

You could write this method for yourself like this:

public static Func<Unit> ActionToUnit(Action action) => (Func<Unit>) (() =>
{
  action();
  return new Unit(); // or default, or Funcky.Unit.Value
});

However, if you now wanted a wrapper for a method with a parameter going into the action, you would need to write this:

public static Func<T, Unit> ActionToUnit<T>(Action<T> action) => (Func<T, Unit>) (parameter =>
{
  action(parameter);
  return new Unit();
});

Now with 2 parameters, you would need 2 generic parameters, and so on. We already support everything from 0 up to 8 in the static class Functional, so you can just write using static Funcky.Functional in your using section, and start using ActionToUnit.

For some use cases, see the Unit Type documentation.

Here one example with a switch expression:

var value = GetValue();
_ = value switch
{
	"Known" => ActionToUnit(() => Console.Write("Known")),
	_ => ActionToUnit(() => Console.Write("Unknown")),
};

Extensions-methods on IEnumerable

LINQ offers you many important higher order functions to transform IEnumerables in many ways.

Funcky offers a few additional ones which can come in handy.

AdjacentGroupBy

adjacent-group-by with marbles

AverageOrNone

CartesianProduct

In mathematics, specifically set theory, the Cartesian product of two sets A and B, denoted A×B, is the set of all ordered pairs (a, b) where a ∈ A and b ∈ B.

In other words: The Cartesian product produces all possible pairs of two given IEnumerables.

cartesian-product with marbles

Recipe

The Cartesian product can be easily implemented ad-hoc using LINQ's built-in SelectMany extension function:

using System;
using System.Linq;

// Version A: Get each pair as a tuple
var result = sequenceA.SelectMany(_ => sequenceB, ValueTuple.Create);

// Version B: Transform each pair using a selector
var result = sequenceA.SelectMany(_ => sequenceB, (a, b) => ...);

// Version C: Using LINQs declarative query syntax
var result =
    from a in sequenceA
    from b in sequenceB
    select ...;

Examples

Two sequences as input:

smiles = [😀, 😐, 🙄]
fruits = [🍉, 🍌, 🍇, 🍓]

The Cartesian products of smiles and fruits:

smiles × fruits => [[😀, 🍉], [😀, 🍌], [😀, 🍇], [😀, 🍓],
                    [😐, 🍉], [😐, 🍌], [😐, 🍇], [😐, 🍓],
				    [🙄, 🍉], [🙄, 🍌], [🙄, 🍇], [🙄, 🍓]]

In this C# example you see how all playing cards are in fact a Cartesian products of a suit and a value.

This example uses the overload with a selector, because we just want a sequence of strings.

using System;
using System.Linq;
using Funcky;

var suits = Sequence.Return("♠", "♣", "♥", "♦");
var values = Sequence.Return("2", "3", "4", "5", "6", "7", "8", "9", "T", "J", "Q", "K", "A");

var deck = suits.SelectMany(_ => values, (suit, value) => $"{value}{suit}");

Chunk

With the .Chunk(int) extension method, you can turn an IEnumerable<T> into a IEnumerable<IEnumerable<T>>, with the inner Enumerables being of the given size. Empty and negative chunk sizes are not allowed and will throw a ArgumentOutOfRangeException.

chunk with marbles

Examples

var numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var chunked = numbers.Chunk(3);
// Result: IEnumerable with Chunks of size 3:
// 1st Chunk: 1, 2, 3
// 2nd Chunk: 4, 5, 6
// 3rd Chunk: 7, 8, 9

When the last chunk isn't complete, we get a smaller, incomplete last chunk:

var numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7 };
var chunked = numbers.Chunk(4);
// Result: IEnumerable with Chunks of size 4:
// 1st Chunk: 1, 2, 3, 4
// 2nd Chunk: 5, 6, 7

If required, you can also pass a result selector, that turns the inner IEnumerables into a different type:

var magicSquare = new List<int> { 4, 9, 2, 3, 5, 7, 8, 1, 6 };
var result = magicSquare.Chunk(3, Enumerable.Average); // equivalent to magicSquare.Chunk(3, number => Enumerable.Average(number));
// Result: IEnumerable<int> with 5, 5, 5 as items

ForEach

With the .ForEach extension method, you can invoke an action for each item in an enumerable, just like a foreach statement would allow you to do.

This method is already available in .NET, but just on Lists, and it makes sense for it to be available on every enumerable.

Keep in mind that .ForEach is imperative and only expects an Action<T>. It should not be used to change state of anything outside of the .ForEach. If you want to combine the enumerable into a result, consider using .Aggregate(), as that is designed for such use-cases.

Example

// Original
foreach (var item in Items)
{
   DoSomething(item);
}

// Using `.ForEach`
Items.ForEach(DoSomething); // equivalent to Items.ForEach(item => DoSomething(item));

FirstOrNone

Inspect

With the .Inspect extension method, you can invoke an action for each item in an enumerable, just like .ForEach or the foreach statement would allow you to do, but the method yields the initial enumerable back.

This can be useful when you want to apply a side-effect to a list before returning, or continue selecting on a list after applying a side-effect. Inspect can be especially useful when you want to log step(s) of a complex query, since you don't have to change the structure of the code to use it.

Examples

// Original using .ForEach
var items = someList.Select(TransformSomething);
Items.ForEach(DoSomething);
return items;

// Using `.Inspect`
return someList.Select(TransformSomething).Inspect(DoSomething);
// Original using foreach
var items = someList.Select(TransformToSomething);
foreach (var item in items) 
{
  DoSomething(item);
}
var transformedItems = items.Select(TransformToSomethingElse);

// Using `.Inspect`
var transformedItems = someList
  .Select(TransformSomething)
  .Inspect(DoSomething)
  .Select(TransformToSomethingElse);

Deferred Execution

It is important to understand at which moment .Inspect is executed. The exact moment of execution is the same as if it were a Select, Where or any other deferred LINQ-method. See Microsoft Docs for more information about deferred execution in LINQ. This is also an important difference between .ForEach (eager) and .Inspect (deferred).

Consider the following example:

Enumerable.Range(1, 100)
  .Inspect(n => Console.WriteLine($"before where: {n}"))
  .Where(n => n % 2 == 0)
  .Inspect(n => Console.WriteLine($"after where: {n}"))
  .Inspect(Console.WriteLine)
  .Take(2)
  .ToImmutableList(); // <- Side effects of .Inspect happen here
  
// Prints:
// before where: 1
// before where: 2
// after where: 2
// 2
// before where: 3
// before where: 4
// after where: 4
// 4

Interleave

interleave with marbles

Intersperse

intersperse with marbles

Materialize

Merge

merge with marbles

Examples

Given two sequences which are already ordered the same way:

  sequence1 = [1, 2, 7, 9, 14]
  sequence2 = [3, 6, 8, 11, 12, 16]

By merging we get one single sequence with the all elements of the given sequences with the same order.

  sequence1.Merge(sequence2) => 
              [1, 2,       7,    9,         14    ]
              [      3, 6,    8,    11, 12,     16]
              -------------------------------------
              [1, 2, 3, 6, 7, 8, 9, 11, 12, 14, 16]

None

With the .None extension method, you can make !enumerable.Any() calls easier.

That's all there is. You can replace:

if (!enumerable.Any()) { ... }

with the easier to read

if (enumerable.None()) { ... }

Just like with .Any(), you can additionally pass a predicate as a parameter:

if (enumerable.None(item => item.SomeNumericProperty == 2) { ... }

PairWise

pairwise with marbles

Example

animals = [ 🐵, 🐶, 🐺, 🐱, 🦄, 🐷, 🦁]
 
animals.PairWise() =>
    [[🐵, 🐶],
	 [🐶, 🐺],
	 [🐺, 🐱],
	 [🐱, 🦄],
	 [🦄, 🐷],
	 [🐷, 🦁]]

Partition

partition with marbles

Example

plantBasedFood = [🍉, 🍩 , 🎂, 🍌, 🍫, 🍓, 🍒, 🥕, 🌽, 🥧 ]

plantBasedFood.Partition(IsProcessedFood?) 
  => [[🍩 , 🎂, 🍫, 🥧],
      [🍉, 🍌, 🍓, 🍒, 🥕, 🌽]]

PowerSet

power-set with marbles

Shuffle

shuffle with marbles

SlidingWindow

sliding-window with marbles

Split

split with marbles

TakeEvery

take-every with marbles

Transpose

transpose with marbles

WhereNotNull

where-not-null with marbles

WhereSelect

where-select with marbles

WithFirst

with-first with marbles

WithIndex

with-index with marbles

WithLast

with-last with marbles

WithPrevious

with-previous with marbles

Example

animals = [ 🦄, 🐺, 🐷, 🦁, 🐵, 🐶 ]
 
animals.WithPrevious() =>
    [[∅, 🦄],
	 [🦄, 🐺],
	 [🐺, 🐷],
	 [🐷, 🦁],
	 [🦁, 🐵],
	 [🐵, 🐶]]

ZipLongest

zip-longest with marbles

String Extensions

IndexOf

The classical IndexOf methods provide a special form of error handling by returning -1 when nothing is found. This is very cumbersome and a potential footgun, since you're not forced to check the return value.

Funcky offers extension methods on string for each overload of IndexOf, IndexOfAny, LastIndexOf, and LastIndexOfAny. The extension methods follow the simple convention of being suffixed with OrNone.

Option<string> ParseKey(string input)
    => input.IndexOfOrNone('[')
         .Select(startIndex => ParseKeyWithMultipleParts(input, startIndex))
         .GetOrElse(() => ParseRegularKey(input));

Example usage of IndexOfOrNone

λ0001: Disallowed use of TryGetValue

TryGetValue should not be used.

Cause

TryGetValue is used outside one of the allowed use cases.

Reason for rule

TryGetValue is an advanced API that is needed to interoperate with imperative constructs, such as loops and catch filter clauses. Use of this API is restricted to prevent misuse. This is critical since TryGetValue is essentially a function to get the value out of the option.

How to fix violations

Use one of the functions provided on Option<T> such as Select, SelectMany, Match or GetOrElse.

Examples

Disallowed

static void Example(Option<int> option)
{
    const int fallback = 42;
    var valueOrFallback = option.TryGetValue(out var value) ? value : fallback;
}

Allowed

Iterator

public static IEnumerable<TItem> Successors<TItem>(Option<TItem> first, Func<TItem, Option<TItem>> successor)
    where TItem : notnull
{
    var item = first;
    while (item.TryGetValue(out var itemValue))
    {
        yield return itemValue;
        item = successor(itemValue);
    }
}

Catch filter clause

try
{
    // ...
} catch (Exception exception) when (FindHandlerForException(exception).TryGetValue(out var handler))
{
    handler.Handle(exception);
}

Option<IExceptionHandler> FindHandlerForException(Exception exception) => ...;

Changelog

All notable changes to this project will be documented in this file. Funcky adheres to Semantic Versioning.

Funcky 3.4.0 | Funcky.Async 1.3.0 | Funcky.XUnit 2.0.2

This update is mainly to update to .NET 8 but also has several smaller improvements.

Native AOT

Both Funcky and Funcky.Async have been annotated to be compatible with Native AOT. The only exception is OptionJsonSerializer which is not compatible with Native AOT.

.NET 8

We use the new C#12 and .NET features in the code, and expose new features through our API.

  • .NET 8 added new overloads to their TryParse APIs. These changes are reflected in Funcky's ParseOrNone APIs.
    • ParseByteOrNone overloads with ReadOnlySpan<byte> and string?
    • ParseSByteOrNone overloads with ReadOnlySpan<byte>
    • ParseSingleOrNone overloads with ReadOnlySpan<byte>
    • ParseDoubleOrNone overloads with ReadOnlySpan<byte>
    • ParseDecimalOrNone overloads with ReadOnlySpan<byte>
    • ParseInt16OrNone overloads with ReadOnlySpan<byte>
    • ParseInt32OrNone overloads with ReadOnlySpan<byte>
    • ParseInt64OrNone overloads with ReadOnlySpan<byte>
    • ParseUInt16OrNone overloads with ReadOnlySpan<byte>
    • ParseUInt32OrNone overloads with ReadOnlySpan<byte>
    • ParseUInt64OrNone overloads with ReadOnlySpan<byte>
    • ParseNumberOrNone<TNumber> overloads
    • ParseOrNone<TParsable> overloads

String Extensions

We implemented a few of the IEnumerable extensions which are very useful on strings.

  • Chunk on string.
  • SlidingWindow on string.

Monads

  • Implemented UpCast for the monds Option, Either, Result and System.Lazy.
  • Implemented InspectEmpty on IEnumerable and IAsyncEnumerable
  • Implemented ToAsyncEnumerable extension on Option

IEnumerator

  • MoveNextOrNone extension on IEnumerator<T>

Consistency

  • FindIndexOrNone and FindLastIndexOrNone extensions on List

Funcky 3.3.0 | Funcky.Analyzers 1.3.0 | Funcky.Xunit 2.0.1

This is a relatively minor release focuses on convenience for our monads Option, Either and Result.

GetOrElse and OrElse for all

We've added GetOrElse and OrElse to Either and Result bringing them on par with Option.
The corresponding analyzer now also correctly suggests using these methods instead of Match for Result and Either.

Inspect for the error case

All three alternative monads Option, Either and Result now support inspecting the «error» case:

  • Option.InspectNone - executes a side effect only when the option is None.
  • Either.InspectLeft - executes a side effect only when the either is on the Left side.
  • Result.InspectError - executes a side effect only when the result is an Error.

These methods are particularly useful for logging warnings/errors.

Funcky.XUnit

  • Funcky.XUnit is only compatible with XUnit 2.4, this is now correctly declared.

Funcky 3.2.0 | Funcky.Async 1.2.0

List Pattern for Option

We've added support for C# 11's List Patterns to Option<T>. This means that you can use regular switch expressions / statements to match on options:

var greeting = person switch
{
    { FirstName: var firstName, LastName: [var lastName] } => $"Hello {firstName} {lastName}",
    { FirstName: var firstName } => $"Hi {firstName}",
};

record Person(string FirstName, Option<string> LastName);

Discard

The new Discard.__ field provides a short-hand for Unit.Value to be used with switch expressions.

using static Funcky.Discard;

return __ switch
{
    _ when user.IsFrenchAdmin() => "le sécret",
    _ when user.IsAdmin() => "secret",
    _ => "(redacted)",
};

Retry with Exception

We've added overloads to the Retry and RetryAsync functions that allow retrying a function as long as an exception is thrown.

Example from IoRetrier:

// Retries an action until the file is no longer in use.
Task RetryWhileFileIsInUseAsync(IRetryPolicy policy, Action action)
    => RetryAsync(ActionToUnit(action), exception => exception is IOException && exception.HResult == FileInUseHResult, policy);

New Extensions

  • EnumerableExtensions.MinByOrNone
  • EnumerableExtensions.MaxByOrNone
  • ImmutableListExtensions.IndexOfOrNone
  • ImmutableListExtensions.LastIndexOfOrNone
  • ListExtensions.IndexOfOrNone

Funcky 3.1.0 | Funcky.Async 1.1.0 | Funcky.Analyzers 1.2.0

New APIs

  • OptionExtensions.ToNullable
  • StreamExtenions.ReadByteOrNone
  • New overloads for ElementAtOrNone that take an Index.
  • New overload for JoinToString that takes an IEnumerable<string>.

.NET 7

  • .NET 7 added new overloads to their TryParse APIs. These changes are reflected in Funcky's ParseOrNone APIs.
  • The ParseOrNone methods include the new [StringSyntax] attribute from .NET 7.

Analyzers

The new Option.Match analyzer suggests simpler alternatives over custom Matches including the all-new ToNullable extension.

Funcky 3.0.0 | Funcky.Async 1.0.0 | Funcky.XUnit 2.0.0

There's a handy Migration Guide available.

New APIs

  • Result.GetOrThrow
  • EnumerableExtensions.GetNonEnumeratedCountOrNone

PriorityQueue

  • PriorityQueueExtensions.DequeueOrNone
  • PeekOrNone

Traversable

The new Traverse and Sequence extension methods allow you to «swap» the inner and outer monad (e.g. Result<Option<T>> -> Option<Result<T>>)

Memoize

The new Memoize extension function returns an IBuffer / IAsyncBuffer.
This new type represents ownership over the underlying enumerator (and is therefore IDisposable).

CycleRange and RepeatRange have also been changed to return an IBuffer.

ParseExtensions

The parse extensions have been improved with the goal of aligning more with the BCL. Many of the changes are breaking.

  • The functions now use BCL type names instead of C# type names (e.g. ParseIntOrNone has been renamed to Parse)
  • The parameter names and nullability have been changed to align with the BCL.
  • Added HttpHeadersNonValidatedExtensions

IReadOnlyList / IReadOnlyCollection

Funcky now communicates materialization in the IEnumerable<T> extensions by returning IReadOnlyList or IReadOnlyCollection. This reduces «multiple enumeration» warnings.

  • Materialize
  • Chunk
  • Partition
  • Shuffle
  • SlidingWindow
  • Split
  • Transpose
  • Sequence.Return

Disallowing null Values

Our Option<T> type has always disallowed null values. This has been extended to our other monads: Result<T>, Either<L, R> and Reader<E, R>.

Breaking Changes

  • Option.None() has been changed to a property. There's an automatic fix available for this.
  • Our Match functions now differentiate between Func and Action. The Action overloads have been renamed to Switch.
  • The return type of EnumerableExtensions.ForEach has been changed to Unit.
  • Many parameter names and generic type names have been renamed in an attempt to unify naming across Funcky.
  • All Action extensions have been moved to a new class ActionExtensions.
  • EitherOrBoth has been moved to the Funcky namespace.
  • The retry policies have been moved to the Funcky.RetryPolicies namespace.
  • Partition returns a custom Partitions struct instead of a tuple.

Obsoleted APIs Removed

APIs that have been obsoleted during 2.x have been removed:

  • ObjectExtensions.ToEnumerable
  • Funcky.GenericConstraints.RequireClass and RequireStruct
  • All Try* APIs (TryGetValue, TryParse*, etc.). These APIs use the OrNone suffix instead.
  • Sequence.Generate has been superceded by Sequence.Successors
  • CartesianProduct

JSON Converter

We have removed the implicit System.Text.Json converter for Option<T>. This means that you'll have to register the OptionJsonConverter yourself.
⚠️ Test this change carefully as you won't get an error during compilation, but rather at runtime.

Moved to Funcky.Async

All APIs related to IAsyncEnumerable and Task have been moved to the new Funcky.Async package:

  • AsyncEnumerableExtensions
  • Functional.RetryAsync -> AsyncFunctional.RetryAsync
  • Option<Task> and Option<ValueTask> awaiters

Funcky.Async

AsyncSequence

This class exposes all of the same factory functions as Sequence, but for IAsyncEnumerable:

  • Return
  • Successors
  • Concat
  • Cycle
  • CycleRange
  • FromNullable
  • RepeatRange

New IAsyncEnumerable extensions

We've worked hard towards the goal of parity between our extensions for IEnumerable and IAsyncEnumerable:

  • AdjacentGroupBy
  • AnyOrElse
  • AverageOrNoneAsync / MaxOrNoneAsync / MinOrNoneAsync
  • Chunk
  • ConcatToStringAsync
  • ExclusiveScan
  • InclusiveScan
  • Inspect
  • Interleave
  • Intersperse
  • JoinToStringAsync
  • MaterializeAsync
  • Memoize
  • Merge
  • NoneAsync
  • PartitionAsync
  • PowerSet
  • Sequence / SequenceAsync / Traverse / TraverseAsync
  • ShuffleAsync
  • SlidingWindow
  • Split
  • Transpose
  • WhereNotNull
  • WithIndex / WithLast / WithPrevious / WithFirst
  • ZipLongest

Funcky.Xunit

  • Breaking: The Is prefix has been dropped from assertion methods for consistency with XUnit's naming scheme for assertion methods.

Funcky 2.7.1

Deprecations

  • Option.None<T>(): We originally introduced the Option.None<T> method as a future proof replacement to Option<T>.None for use in method groups, because Funcky 3 changes Option<T>.None to a property. This turned out to be confusing to users especially because both method are always suggested in autocomplete.

Funcky 2.7.0 | Funcky.XUnit 1.0.0 | Funcky.Analyzers 1.1.0

This release is the last non-breaking release for Funcky before 3.0.

Deprecations

  • EnumerableExtensions.CartesianProduct will be removed in Funcky 3.
  • To align our naming with that of the BCL, the ParseOrNone methods that return a type that has a keyword in C# int, long, etc. use the name of the BCL type instead.
    Example: ParseIntOrNone becomes ParseInt32OrNone.
    The old methods will be removed in Funcky 3.
  • In preparation for Funcky 3 we deprecated Option<T>.None when used as method group. Use Option.None<T> instead.

New ParseOrNone extensions

With the help of a source generator we have added a lot of new ParseOrNone methods for various types from the BCL:

  • Unsigned integer types
  • DateOnly, TimeOnly
  • Version
  • Support for ReadOnlySpan<T> as input
  • ... and more

Convenience for Either and Result

  • Added implicit conversions for Either and Result.
  • Implement Inspect for Either and Result.
  • Added Partition for IEnumerable<Either> and IEnumerable<Result>.
  • Added ToString on Either and Result.
  • Implement ToEither on Option.

IEnumerable<T> extensions

  • AnyOrElse
  • Prefix sum: InclusiveScan and ExclusiveScan

Analyzers

This release adds two new analyzer rules:

  • λ1003: Warning when certain methods, such as Match are used without argument labels
  • λ1004: Warning that suggests .ConcatToString() over .JoinToString("")

Both of these warnings come with corresponding code fixes.

Funcky.Xunit

  • Breaking: Funcky.Xunit now uses the Funcky namespace, instead of Funcky.Xunit.
  • Add assertion methods for testing Either: FunctionalAssert.IsLeft and FunctionalAssert.IsRight.

Funcky 2.6.0 | Funcky.Analyzers 1.0.0

Analyzers

This release comes with a new package Funcky.Analyzers, which we'll use to guide users of Funcky

New extensions

  • Add extensions DequeueOrNone and PeekOrNone on Queue and ConcurrentQueue.
  • Add extension ConcatToString as an alias for string.Concat.
  • Add overload to WhereSelect with no parameter.
  • Add methods to convert from Either to Option: #439
    • LeftOrNone: Returns the left value or None if the either value was right.
    • RightOrNone: Returns the right value or None if the either value was left.
  • Extension functions for System.Range to allow the generations of IEnumerable<T>s from Range-Syntax:
    foreach(var i in 1..5) { }
    
    // negative numbers are not supported
    from x in 5..2
    from y in 1..3
    select (x, y)
    

Improvements to Sequence

  • Sequence.Return now accepts multiple parameters:
    Sequence.Return(1, 2, 3)
    
  • ⚠️ Sequence.Generate has been deprecated in favour of the newly added Sequence.Successors function which includes the first element (seed) in the generated sequence.

Improvements to Option

  • Add Option.FromBoolean to create an Option<T> from a boolean.

Improvements to Result

The behaviour of the Result.Error constructor has been changed regarding exceptions with an already set stack trace. The original stack trace is now preserved. Previously this resulted in the stacktrace being replaced (.NET < 5.0) or an error (.NET ≥ 5.0).

Improvements to Either

  • Add Either.Flip to swaps left with right.

Tooling

  • Funcky automatically adds global usings for the most important namespaces of funcky when the FunckyImplicitUsings property is set. This requires .NET SDK ≥ 6.0 and C# ≥ 10.0.
  • Funcky now supports trimming for self-contained deployments.
  • Option<T> now works with the new System.Text.Json source generation.
  • The Funcky package now supports Source Link and deterministic builds.
  • The symbols package is now finally working again.

Funcky 2.5.0

Reader Monad

This release includes the Reader monad including a bunch of factory methods and convenience extensions.

public static Reader<Enviroment, IEnumerable<string>> DefaultLayout(IEnumerable<DateTime> month)
    => from colorizedMonthName in ColorizedMonthName(month)
       from weekDayLine in WeekDayLine()
       from weeksInMonth in month
        .GroupBy(GetWeekOfYear)
        .Select(FormatWeek)
        .Sequence()
       select BuildDefaultLayout(colorizedMonthName, weekDayLine, weeksInMonth);

Improved Action Extensions

Funcky now supports Curry, Uncurry and Flip for Actions too.
This release also adds the inversion of ActionToUnit: UnitToAction

More Extensions for IEnumerable<T>

  • Intersperse: Adds a given item in between all items of an enumerable.
  • JoinToString: Alias for string.Join.
  • WithPrevious: Similar to WithFirst/Last/Index but with the predecessor of each item.
  • ForEach: Add an overload to ForEach that accepts a Unit-returning Func.

Additional Factory Methods

  • EitherOrBoth.FromOptions creates an EitherOrBoth from two options.
  • Lazy.FromFunc creates a Lazy<T> from a Func.
    This is sugar over the Lazy<T> constructor, with the additional benefit of supporting type inference.
  • Lazy.Return creates a Lazy<T> from a value.
    This is sugar over the Lazy<T> constructor, with the additional benefit of supporting type inference.

Documentation Improvements

This release comes with a few small documentation improvements. Funcky users will now also see the [Pure] attributes which were previously not emitted.

Funcky 2.4.1

  • Remove upper bounds on all Microsoft.Bcl.* dependencies. Between the 2.3.0 and 2.4.0 release an overly restrictive upper bound was accidentally introduced for Microsoft.Bcl.AsyncInterfaces.

Funcky 2.4.0

Try**OrNone

We've renamed all Try* methods, such as TryParse, TryGet value to *OrNone. The old methods are still available, but marked as obsolete and will be removed in 3.0.0.

Factory methods for IEnumerable<T>

This release adds some new factory methods for creating IEnumerable<T> to the Sequence class:

  • Sequence.RepeatRange: Generates a sequence that contains the same sequence of elements the given number of times
  • Sequence.Cycle: Cycles the same element over and over again as an endless generator.
  • Sequence.CycleRange: Generates a sequence that contains the same sequence of elements over and over again as an endless generator
  • Sequence.Concat

More Extension Methods

for IEnumerable<T>

  • Materialize: Materializes all the items of a lazy enumerable.
  • PowerSet: Returns a sequence with the set of all subsets
  • Shuffle: Returns the given sequence in random Order in O(n).
  • Split: Splits the source sequence a separator.
  • ZipLongest: Zips two sequences with different lengths.

for string

  • SplitLazy: Splits a string by separator lazily.
  • SplitLines: Splits a string by newline lazily.

for Func

  • Curry
  • Uncurry
  • Flip
  • Compose

EitherOrBoth

EitherOrBoth is a new data type that can represent Left, Right and Both. It is used in ZipLongest.

Monad.Return

This release adds a Return method for all monad types in Funcky:

  • Option.Return
  • Either<TLeft>.Return
  • Result.Return

OptionEqualityComparer

To support more advanced comparison scenarios, OptionEqualityComparer has been added similar to the already existing OptionComparer.

Smaller Improvements

  • Added a missing Match overload to Either that takes Actions
  • Added additional overloads for Functional.True and Functional.False for up to four parameters.

Funcky 2.3.0

  • net5.0 has been added to Funcky's target frameworks.

Improvements to Option<T>

  • Option<T> is now implicitly convertible from T.
    public static Option<int> Answer => 42;
    
  • Option adds support for System.Text.Json:
    The custom JsonConverter is picked up automatically when serializing/deserializing. None is serialized as null and Some(value) is serialized to whatever value serializes to.

Factory methods for IEnumerable<T>

This release adds factory methods for creating IEnumerable<T> with the static class Sequence:

  • Sequence.Return: Creates an IEnumerable<T> with exactly one item.
  • Sequence.FromNullable: Creates an IEnumerable<T> with zero or one items.
  • Sequence.Generate: Creates an IEnumerable<T> using a generation function and a seed.

More Extension Methods for IEnumerable<T>

This release adds a bunch of new extension methods on IEnumerable<T>:

  • AdjacentGroupBy
  • AverageOrNone
  • CartesianProduct
  • Chunk
  • ElementAtOrNone
  • Interleave
  • MaxOrNone
  • Merge
  • MinOrNone
  • Pairwise
  • Partition
  • SlidingWindow
  • TakeEvery
  • Transpose
  • WithFirst
  • WithIndex
  • WithLast

IAsyncEnumerable<T> Support

This release adds a couple of extension methods that provide interoperability with Option<T> to IAsyncEnumerable<T>:

  • WhereSelect
  • FirstOrNoneAsync
  • LastOrNoneAsync
  • SingleOrNoneAsync
  • ElementAtOrNoneAsync

A couple of the new extension methods on IEnumerable<T> have async counterparts:

  • Pairwise
  • TakeEvery

The naming of the extension methods and their overloads follows that of System.Linq.Async.

Improved IQueryable Support

This release adds specialized extension methods for IQueryable<T> that are better suited especially for use with EF Core:

  • FirstOrNone
  • LastOrNone
  • SingleOrNone

Dependencies

To support .NET Standard, Funcky conditionally pulls in dependencies that provide the missing functionality:

  • Microsoft.Bcl.AsyncInterfaces for .NET Standard 2.0
  • System.Collections.Immutable and System.Text.Json for .NET Standard 2.0 and 2.1
  • The version constraints for all these packages have been relaxed to allow 5.x.

Improvements

  • ConfigureAwait(false) is now used everywhere await is used.
  • The IRetryPolicy implementations now use correct Timespan with double multiplication when targeting .NET Standard 2.0.

Deprecations

  • ObjectExtensions.ToEnumerable has been deprecated in favor of Sequence.FromNullable.
  • RequireClass and RequireStruct have been obsoleted with no replacement.

Funcky 2.2.0 | Funcky.xUnit 0.1.3

  • Added overload to Functional.Retry with a IRetryPolicy.
  • Added None overload that takes no predicate.

Funcky 2.1.1 | Funcky.xUnit 0.1.2

  • Re-release of previous release with correct assemblies.

Funcky 2.1.0 | Funcky.xUnit 0.1.1

  • Add Inspect method to Option akin to IEnumerable.Inspect.
  • Add ToTheoryData extension for IEnumerable<T> for xUnit.
  • Add Unit.Value as a way to a get a Unit value.
  • Add Functional.Retry which retries a producer until Option.Some is returned.

Funcky 2.0.0

Breaking Changes

  • Remove Reader monad based on await.
  • Remove IToString.
  • Remove overload for Option.From that flattens passed Options.
  • Move ToEnumerable extension method to its own class. This is only a breaking change if you've used the extension method as normal method. In that case you need to change EnumerableExtensions.ToEnumerable to ObjectExtensions.ToEnumerable.
  • Rename Option.From to Option.FromNullable and remove overload that takes non-nullable value types.
  • Unify Option<T>.ToEnumerable and Yield to ToEnumerable
  • Rename OrElse overloads that return the item to GetOrElse which improves overload resolution.
  • The Each extension method on IEnumerable<T> has been renamed to ForEach.
  • Move the Ok constructor of Result<T> to a non-generic class. This allows for the compiler to infer the generic type. Old: Result<int>.Ok(10). New: Result.Ok(10).
  • Use Func<T, bool> instead of Predicate<T> in predicate composition functions (Functional.All, Functional.Any, Functional.Not), because most APIs in System use Func.
  • Functional.Any now returns false when the given list of predicates is empty.

Fixes

  • Fix incorrect Equals implementation on Option. Equals previously returned true when comparing a None value with a Some value containing the default value of the type.
  • Exception created by Result monad contains valid stack trace
  • Fix incorrect implementation on Result.SelectMany which called the selectedResultSelector even when the result was an error. As a result (pun intended) of the fix, ResultCombinationException is no longer needed and also removed.

Additions

  • Add IndexOfOrNone, LastIndexOfOrNone, IndexOfAnyOrNone and LastIndexOfAnyOrNone extension methods to string.
  • Added Curry, Uncurry and Flip to the Functional Class
  • Add extension method for HttpHeaders.TryGetValues, which returns an Option.
  • Add extension methods for getting Stream properties that are not always available, as Option: GetLengthOrNone, GetPositionOrNone, GetReadTimeoutOrNone, GetWriteTimeoutOrNone.
  • Add None extension method to IEnumerable.
  • Option<Task<T>>, Option<Task> and their ValueTask equivalents are now awaitable:
    var answer = await Option.Some(Task.FromResult(42));
    

Improvements

  • Full nullable support introduced with C# 8.
  • Mark our functions as [Pure].
  • Implement IEquatable on Option, Result and Either.

Funcky 2.0.0-rc.2

  • Move the Ok constructor of Result<T> to a non-generic class. This allows for the compiler to infer the generic type. Old: Result<int>.Ok(10). New: Result.Ok(10).
  • Add IndexOfOrNone, LastIndexOfOrNone, IndexOfAnyOrNone and LastIndexOfAnyOrNone extension methods to string.
  • Rename OrElse overloads that return the item to GetOrElse which improves overload resolution.
  • Added Curry, Uncurry and Flip to the Functional Class
  • Remove IToString.
  • Mark our functions as [Pure].
  • Fix incorrect implementation on Result.SelectMany which called the selectedResultSelector even when the result was an error. As a result (pun intended) of the fix, ResultCombinationException is no longer needed and also removed.

Funcky 2.0.0-rc.1

  • Full nullable support introduced with C# 8
  • Rename Option.From -> Option.FromNullable and remove overload that takes non-nullable value types.
  • Use Func<T, bool> instead of Predicate<T> in predicate composition functions (Functional.All, Functional.Any, Functional.Not), because most APIs in System use Func.
  • Functional.Any now returns false when the given list of predicates is empty.
  • The Each extension method on IEnumerable<T> has been renamed to ForEach.
  • Unify Option<T>.ToEnumerable and Yield to ToEnumerable
  • Remove Reader monad based on await.
  • Exception created by Result monad contains valid stack trace

Funcky 1.8.0

  • Added overload for AndThen which flattens the Option
  • Add Where method to Option<T>, which allows filtering the Option by a predicate.
  • Add overload for Option<T>.SelectMany that takes only a selector.
  • Add WhereNotNull extension method for IEnumerable<T>.

Funcky 1.7.0

  • Add nullability annotations to everything except for Monads.Reader.
  • Add a function for creating an Option<T> from a nullable value: Option.From.
  • Either.Match now throws when called on an Either value created using default(Either<L, R>).
  • Add True and False functions to public API
  • Match of Result Monad accepts actions
  • Add FirstOrNone, LastOrNone and SingleOrNone extension functions

Funcky 1.6.0

  • Add ToEnumerable function to Option<T>.
  • Add WhereSelect extension function for IEnumerable<T>.
  • Add missing overload for nullary actions to ActionToUnit.