Table of Contents

About

As explained for the if-clause aggregator, LINQ-style aggregators lack conditionals even though lambda calculus can very well do flow control. Since our second-favourite control-flow element is the switch construct, the following page attempts to implement switch as a lambda abstraction for various languages.

JavaScript

function wasSwitch(q, d, ...c) {
    if(c.length % 2 != 0)
        throw "Pairs of predicates expected for cases";
 
    (Array.isArray(q) ? q : [ q ]).forEach((s) => {
        var m = false;
        for(i = 0; i < c.length; i += 2) {
            if(!c[i](s))
                continue;
            if(!c[i + 1](s))
                continue;
            m = true;
        }
 
        if(!m)
            d(s)
    });
}

The wasSwitch function operates on an input array data and for all items tests the current item against a value a == 1 such that if the test succeeds, then the body is executed, otherwise the default body is executed. Additionally, in case the test succeeds and the body returns true then the default case is skipped (semantically equivalent to break):

var data = [ 1, 2, 3, 4, 5 ];
wasSwitch(
    data,
    (a) => { 
        alert("boo"); 
    },
    (a) => a == 1,
    (a) => {
        alert("test");
        return true;
    }
);

Also part of the Wizardry and Steamworks JavaScript libraries.

C#

C# is unfortunately strongly typed, such that a lot of futzing is necessary to build a switch construct. The following implementation will work both in a parallel or sequential context.

///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2017 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
/// <summary>
///     A functional implementation of a switch clause.
/// </summary>
/// <typeparam name="T">the type of items in the query</typeparam>
/// <param name="query">the selector query</param>
/// <param name="default">the function to execute when no case matches</param>
/// <param name="case">a list of predicates representing the switch cases,
/// where each predicate has to return True or False indicating whether
/// fallthrough should occur.
/// </param>
public static void Switch<T>(this IEnumerable<T> query,
    // default
    Action<T> @default,
    // case
    // case
    // ...
    params Predicate<T>[] @case)
{
    if (@case.Length % 2 != 0)
        throw new ArgumentException("Pairs of predicates expected.");
 
    var enumerable = query as IList<T> ?? query.ToList();
    using (var iter = enumerable.GetEnumerator())
    {
        while (iter.MoveNext())
        {
            var match = false;
            for (var i = 0; i < @case.Length; i += 2)
            {
                if (!@case[i].Invoke(iter.Current))
                    continue;
 
                if (@case[i + 1].Invoke(iter.Current))
                    return;
 
                match = true;
            }
 
            if (!match)
                @default.Invoke(iter.Current);
        }
    }
}

Its usage could be illustrated as follows:

var species = new HashSet<Specie>({ Bacteria, Archaea, Eukaryota });
species
    .Select(o => o)
    .Switch(
        // default:
        specie => {
           Console.WriteLine("This must be an Eukaryota!");
        },
        // case "Bacteria":
        specie => specie.Name.Equals("Bacteria"),
        //    body of case "Bacteria"
        specie => {
           // specie is "Bacteria"
           Console.WriteLine(specie.Name);
           // switch fall-through
           return false;
        },
        // case "Archaea":
        specie => specie.Name.Equals("Archaea"),
        //    body of case "Archaea"
        specie => {
           // specie is "Archaea"
           Console.WriteLine(specie.Name);
           // break
           return true;
        }
    );