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