Question

I have been using c# and trying to learn FP. In context of FP I often hear that usage of basic assignment or return statements are not considered composable, hence their usage is not advised in FP paradigm.

I already found this older Stackoverflow question "What does composability mean in context of functional programming?", but the question (and its answers) focusses on control structures, threads and monads, not on basic statements.

I also asked this former question about functions with side-effects, but my new question here is not specifically about impure functions. Even in case it is essentially same question, then does it mean that function having statement/instructions are considered impure ?

Can someone help me understand this better with some examples (preferably some example using assignment or return statement)?

Was it helpful?

Solution

There's aready a nice compact answer above. However, since you ask several related questions about FP, I propose some more explanations, if deemed useful.

Functional programming handles everything as either a declaration or an expression. In this paradigm, functions are the main mean of abstraction: they are combined in expression (composition), can themselves be an operand/argument in an expression, and can be the result of a higher order function.

The driving idea is to move away from the sequential paradigm in which you tell the machine to do one thing after the other. This is not easy to understand in a statement oriented language like C#. Take the following simple calculation: statements:

double res;    // computes something based on previously defined a and b 
if (a>b) 
   res = Math.Sqrt( a*a - b*b ); 
else 
   res = - Math.Sqrt ( b*b - a*a); 

This if statement is not composable. If I want to use the result of the calculation in another calculation, I have to add a new statement that use res, which means that I rely on a side effect:

res = res *2;  // another side effect

if (res > 100) 
...

The natural way forward would be to extract your statements into a function. THis isolates the side effects into local variables that are without effect on the rest of the code:

double f(double a, double b) {   // abstract calculation in a function
    double res;     
    if (a>b) 
       res = Math.Sqrt( a*a - b*b ); 
    else 
       res = - Math.Sqrt ( b*b - a*a); 
    return res; 
}
double growth(double x) {
    return x*2; 
}

You can then easily compose the functions:

if (growth(f(a,b))>100) 
... 

You could also try to express your functions as much as possible as an expression thus avoiding to rely on side effect. But be careful, because long expressions are also more difficult to write and to read:

double f(double a, double b) {   // abstract calculation in a function
    return a>b ?  Math.Sqrt( a*a - b*b ) : - Math.Sqrt ( b*b - a*a);   
}

C# also gives you use a more functional style. This facilitates breaking down problems into simpler functions (here local functions that do not polute the namespace) and use composition:

    Func<double, double, double> f = (a,b) => (a>b ?  Math.Sqrt( a*a - b*b ) : - Math.Sqrt ( b*b - a*a));
    Func<double, double> growth = (x) => x*2;
    Func<double, double, double> comp = (x,y) => growth (f(x,y));

THe idea behind this paradigm, is that in pure functional programming you express the problem independently of the order of operations. In theory, it's then the implementation's job to find out the most efficient execution path, whether it evaluates the expression in a sequential manner, or it distributes subexpressions for exploiting parallelisation potential.

This is, by the way, why functional programmers like immutability and don't like side-effects. Mutability and side-effects create constraints and expectations about the evaluation order.

If you want to grasp the spirit of functional programming, it would worth to give functional programming languages such as OCaml and F# a try. After a tutorial you can then easily go back to C#, and use functinoal style where it can help to make the difference.

OTHER TIPS

Composability means that components can be combined, and the combination can be used in place of its parts.

Expressions are composable in the sense they can be combined with other expressions to form new expressions. And a more complex expression can be used anywhere a simple expression can be used.

More importantly for your question, any expression or subexpression can be extracted to a function, and function calls can be used instead of any expression term.

But this is not always the case for statements! Consider these statements:

if (foo) {
   return bar;
}

You cannot replace this with a simple function call, even if this fragment occurs multiple places in the code.

Let's say you had some code like:

if (done) {return result}
// more calculation
if (done) {return result}
// some more calculation
if (done) {return result}

You have a fair amount of repetition here. Maybe you want to factor it out. If if and return statements were referentially transparent and composable, you could write it like:

checkDone = if (done) {return result}
checkDone
// more calculation
checkDone
// some more calculation
checkDone

Obviously, that doesn't work, but if you stick to purely functional code, you can always make that sort of substitution without affecting the result. This example of the State monad in Scala Cats is a great illustration of that substitutability.

Licensed under: CC-BY-SA with attribution
scroll top