About

One of the shortcoming for all languages that implement linq-style data aggregators is that none of them include semantics for if-clauses. Consider the following case in C# where a selector is given (select) in the first line, perhaps, by user-input and then a lambda is executed for all items in a collection that match a property from the selector set:

var select = new HashSet<Species>({ "Bacteria", "Archaea", "Eukaryota" });
entities
    .AsParallel()
    .Where(entity => entity.species.Intersect(select).Any())
    .ForAll(entity => {
        Console.WriteLine(entity.Name);
    });

Whilst this is fine, the selection predicate in the Where clause will cause the downstream lambda function in the ForAll clause to be executed if and only if the predicate in the Where clause matched.

In case the predicate passed to the Where clause does not match, the downstream lambda function will not be executed - and that is fine, in some scenarios. However, it may be desirable to have additional functionality where the lambda function gets called for all predicate matches of the Where clause, but a different lambda function gets called when the predicate passed to the Where clause does not match.

Lambda Calculus

Conveniently, untyped lambda calculus is already capable to perform if a then b else c-branching. Consider two lambda expressions:

\begin{eqnarray*}
\lambda x.\lambda y.x &\equiv& TRUE \\
\lambda x.\lambda y.y &\equiv& FALSE
\end{eqnarray*}

and two other expressions $P$ and $Q$, then:

\begin{eqnarray*}
TRUE\mbox{ }P\mbox{ }Q &=& \\
(\lambda x.\lambda y.x) P Q &\equiv& \\
\lambda y.P Q &\equiv& \\
P \\
\end{eqnarray*}

conversely:

\begin{eqnarray*}
FALSE\mbox{ }P\mbox{ }Q &=& \\
(\lambda x.\lambda y.y) P Q &\equiv& \\
\lambda y.Q &\equiv& \\
Q \\
\end{eqnarray*}

such that we can write:

$$
(n\mbox{ }P\mbox{ }Q)
$$

where:

  • $n$ is either $TRUE$ or $FALSE$
  • and $P$, respectively $Q$ are two expressions

thereby implementing the semantic:

if (n) 
{
   P;
}
else 
{
   Q;
}

Implementations

Some implementations are more trivial than others given lambda calculus semantics in newer programming languages, however the usefulness of branching in a cascade of lambda function calls is extremely convenient, semantically elegant, and spares switching to imperative programming from functional programming in order to implement simple branches.

We can derive various semantics based on the original if a then b else c branching semantics.

If-a-Then-b-Else-c

C#

An implementation of if-a-then-b-else-c, which, given the initial example, would work as such:

var select = new HashSet<Species>({ "Bacteria", "Archaea", "Eukaryota" });
entities
    .AsParallel()
    .ForAll(entity =>
        // if a
        entity.species.Intersect(select).Any(),
        // then b 
        entity => {
            Console.WriteLine(entity.Name + " found");
        },
        // else c
        entity => {
            Console.WriteLine(entity.Name + " not found.");
        }
    );

Using a parallel query (the semantics of ForAll in C# only works in a parallel query context):

///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2017 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
/// <summary>
///     Invokes pass if and only if predicate resovles or fail otherwise.
/// </summary>
/// <typeparam name="T">the type of items in the query</typeparam>
/// <param name="query">the selector query</param>
/// <param name="predicate">the condition for invoking pf</param>
/// <param name="pass">the function to invoke in case the predicate resolves</param>
/// <param name="fail">the function to invoke otherwise</param>
public static void ForAll<T>(this ParallelQuery<T> query, Predicate<T> predicate, Action<T> pass, Action<T> fail)
{
    query.ForAll(o =>
    {
        switch (predicate.Invoke(o))
        {
            case true:
                pass.Invoke(o);
                return;
 
            default:
                fail.Invoke(o);
                return;
        }
    });
}

Or, by extending a boolean:

///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2017 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
/// <summary>
///     Invokes pass if and only if condition holds or fail otherwise.
/// </summary>
/// <typeparam name="T">the return type of the pass and fail functions</typeparam>
/// <param name="condition">the branch condition</param>
/// <param name="pass">function with no parameters and return type T in case condition passes</param>
/// <param name="fail">function with no parameters and return type T in case condition fails</param>
/// <returns>the result of pass in case condition holds or the result of fail otherwise</returns>
public static T IfElse<T>(this bool condition, Func<T> pass, Func<T> fail)
{
    return condition ? pass.Invoke() : fail.Invoke();
}

Or by passing an extra optional argument to the functions to be invoked conditionally:

///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2017 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
/// <summary>
///     Invokes pass if and only if condition holds or fail otherwise.
/// </summary>
/// <typeparam name="U">the type of the argument to pass and fail</typeparam>
/// <typeparam name="V">the return type of pass and fail</typeparam>
/// <param name="condition">the branch condition</param>
/// <param name="pass">function that takes argument arg and returns type V in case condition holds</param>
/// <param name="fail">function that takes argument arg and returns type V in case condition fails</param>
/// <param name="arg">the argument passed to pass or fail functions</param>
/// <returns>the result of pass in case condition holds or the result of fail otherwise</returns>
public static V IfElse<U, V>(this bool condition, Func<U, V> pass, Func<U, V> fail, U arg = default(U))
{
    return condition ? pass.Invoke(arg) : fail.Invoke(arg);
}

Or perhaps make pass and fail functions return nothing but take an argument as in the previous examples:

///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2017 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
/// <summary>
///     Invokes pass if and only if condition holds or fail otherwise.
/// </summary>
/// <typeparam name="T">the type of the argument to pass and fail</typeparam>
/// <param name="condition">the branch condition</param>
/// <param name="pass">function that takes argument arg and returns nothing in case condition holds</param>
/// <param name="fail">function that takes argument arg and returns nothing in case condition fails</param>
/// <param name="arg">the optional argument passed to pass or fail functions</param>
public static void IfElse<T>(this bool condition, Action<T> pass, Action<T> fail, T arg = default(T))
{
    switch (condition)
    {
        case true:
            pass.Invoke(arg);
            return;
 
        default:
            fail.Invoke(arg);
            return;
    }
}

A variadic Func or Action is not available in C#, so how about a function with no return but taking two arguments:

///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2017 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
/// <summary>
///     Invokes pass if and only if condition holds or fail otherwise.
/// </summary>
/// <typeparam name="U">the type of the first argument to the pass or fail functions</typeparam>
/// <typeparam name="V">the type of the second argument to the pass or fail functions</typeparam>
/// <param name="condition">the branch condition</param>
/// <param name="pass">function that takes argument arg and returns nothing in case condition holds</param>
/// <param name="fail">function that takes argument arg and returns nothing in case condition fails</param>
/// <param name="arga">first optional argument passed to pass or fail functions</param>
/// <param name="argb">second optional argument passed to pass or fail functions</param>
public static void IfElse<U, V>(this bool condition, Action<U, V> pass, Action<U, V> fail, U arga = default(U), V argb = default(V))
{
    switch (condition)
    {
        case true:
            pass.Invoke(arga, argb);
            return;
 
        default:
            fail.Invoke(arga, argb);
            return;
    }
}

If-a-Then-b-ElseIf-c-Then-d

C#

An implementation of if-a-then-b-elseif-c-then-d, which, given the initial example, would work as such:

var seta = new HashSet<Species>({ "Bacteria", "Archaea" });
var setb = new HashSet<Species>({ "Eukaryota" });
entities
    .AsParallel()
    .ForAll(
        // if a
        entity => entity.species.Intersect(seta).Any(),
        // then b
        entity => {
            Console.WriteLine(entity.Name);
        },
        // elseif c
        entity => entity.species.Intersect(setb).Any(),
        // then d
        entity => {
            Console.WriteLine(entity.FullName);
        }
   );
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2017 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
/// <summary>
///     Invokes pf in case the predicate p resolves or qf in case the predicate q resolves.
/// </summary>
/// <typeparam name="T">the type of items in the query</typeparam>
/// <param name="query">the selector query</param>
/// <param name="p">the condition for invoking pf</param>
/// <param name="pf">the action to invoke in case p resolves</param>
/// <param name="q">the condition for invoking qf</param>
/// <param name="qf">the action to invoke in case q resolves</param>
public static void ForAll<T>(this ParallelQuery<T> query, Predicate<T> p, Action<T> pf, Predicate<T> q, Action<T> qf)
{
    query.ForAll(o =>
    {
        if (p.Invoke(o))
        {
            pf.Invoke(o);
            return;
        }
 
        if (q.Invoke(o))
        {
            qf.Invoke(o);
        }
    });
}

If-a-Then-b-ElseIf-c-Then-d-Else-e

A full if-branching example:

if (a) {
    b;
}
else if(c) {
    d;
}
else {
    e;
}

C#

///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2017 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
/// <summary>
///     Invokes pf in case the predicate p resolves or qf in case the predicate q resolves, or ef otherwise.
/// </summary>
/// <typeparam name="T">the type of items in the query</typeparam>
/// <param name="query">the selector query</param>
/// <param name="p">the condition for invoking pf</param>
/// <param name="pf">the action to invoke in case p resolves</param>
/// <param name="q">the condition for invoking qf</param>
/// <param name="qf">the action to invoke in case q resolves</param>
/// <param name="ef">the action to invoke otherwise</param>
public static void ForAll<T>(this ParallelQuery<T> query, Predicate<T> p, Action<T> pf, Predicate<T> q, Action<T> qf, Action<T> ef)
{
    query.ForAll(o =>
    {
        if (p.Invoke(o))
        {
            pf.Invoke(o);
        }
        else if (q.Invoke(o))
        {
            qf.Invoke(o);
        }
        else
        {
            ef.Invoke(o);
        }
    });
}

Control-Flow Constructs

Very similar, yet more powerful, is the switch aggregator in lambda calculus:


fuss/lambda_calculus/functional_programming/aggreagators/if.txt ยท Last modified: 2022/04/19 08:28 by 127.0.0.1

Access website using Tor Access website using i2p Wizardry and Steamworks PGP Key


For the contact, copyright, license, warranty and privacy terms for the usage of this website please see the contact, license, privacy, copyright.