Search code examples
c#functional-programmingimperative-programming

Functional evaluation of conditionals in C#?


In functional programming a statement like if (a || b) can be evaluated in parallel by default, since we don't make any assumptions about the evaluation order due to referential transparency. Is there any easy way to introduce this type of functional parallelism for conditionals in C#?

For instance in C# we can introduce parallelism over for loops easily using Parallel.For(), so I'm wondering if there's something similar to conditionals?

I'm trying to bring some of the benefits from functional programming to the way I write C#.


Solution

  • While possible in theory, I haven't heard about any language that supports this with syntax or simple APIs (but I also only know a handful of languages). As you may know, Haskell is often considered the gold standard of FP, but Boolean expressions in Haskell are just normal, short-circuiting expressions, just like they are in C#.

    ghci> a = True
    ghci> b = undefined
    ghci> (a || b)
    True
    

    Here, b is undefined which means that actually trying to evaluate it will throw an exception:

    ghci> b
    *** Exception: Prelude.undefined
    CallStack (from HasCallStack):
      error, called at libraries\base\GHC\Err.hs:74:14 in base:GHC.Err
      undefined, called at <interactive>:2:5 in interactive:Ghci2
    

    But as you can see in the top example, (a || b) evaluates to True because it short-circuits.

    In fact, Haskell is, in general, automatically short-circuiting because of lazy evaluation, but most languages have short-circuiting Boolean expressions.

    That's already a good optimization. If you have one cheap and one expensive Boolean expression, put the cheap one first.

    So how often do you need more optimization than that? Particularly in the context of referentially transparent functions. Usually, expensive operations are the ones involving I/O.

    Not that having two (or more) expensive, but referentially transparent Boolean expressions, is entirely inconceivable. If you're working in the realms of cryptography, protein folding, or the like, I suppose you could have two Boolean expressions, a and b, that are both referentially transparent and expensive to compute.

    While not unconceivable, it seems like a sufficiently niche problem that I wouldn't expect a mainstream language to have that as a feature built into the language itself.

    As Dai writes in a comment, concurrency comes with an overhead, so the expressions need to be more expensive than the overhead before such an optimization is warranted.

    Can you write an API that gets you there? Yes, absolutely, and I'm sure other people here will tell you how to use Task.WhenAll and Task.WhenAny for that.

    If you want hints on how to design such an API, it may help considering that Boolean comparisons form monoids, and that all monoids can be made lazy:

    Lazy<bool> x = // ...
    Lazy<bool> y = // ...
    Lazy<bool> b = x.And(y);
    

    You could do something similar with the TPL, since you can lift any monoid into an applicative functor pointwise:

    (Applicative f, Monoid a) => Monoid (Ap f a)
    

    Since C# Tasks are monads, they are also applicative functors, so we know from Haskell that this is possible, and how the API ought to look.