1

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)?

  • @DocBrown : I found that answer but was finding it difficult to understand – rahulaga_dev Nov 16 '19 at 10:11
  • I found top answers quoting an example of lock construct and some reference to monads, but I am trying to understand why even basic statements such as assignment or return are not considered composable. – rahulaga_dev Nov 16 '19 at 10:31
  • Could this help: softwareengineering.stackexchange.com/q/400523/209774 ? It's not exactly the same question but the answer addresses the challenges of function composition. – Christophe Nov 16 '19 at 11:27
  • yes, that was asked by me only. But am still not sure if it's exactly same question – rahulaga_dev Nov 16 '19 at 11:55
  • 1
    I took the freedom to edit your question into a form of which I believe gives it a better chance to survive. Please double check if got it all right. – Doc Brown Nov 16 '19 at 12:02
1

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.

| improve this answer | |
  • thanks lot, this helps !! one question though - you mentioned in pure functional programming you express the problem independently of the order of operations.. isn't even in your refactored example function f and growth has to be before comp and sequence does matter ? – rahulaga_dev Nov 17 '19 at 3:25
  • Also from advice perspective - Is Scala better option to start with vs F# as I just started with "Functional programming with scala by Paul Chiusano" ? Also on the contrary people suggest to go with Haskell as it forces you to think FP way vs F#/Scala which might confuse because of its mixed paradigm nature. Thanks !! – rahulaga_dev Nov 17 '19 at 6:04
  • 1
    @RahulAgarwal in a functional language, this would not be an execution order, but a declarative order: the language needs to know the functions that are used. The implementation decides the order in which things are executed depending on constraints (e.g. f(g(x)) requires that g is calculated first, but f(g(x),h(y)) leaves it open if f or h is executed first or both in same time). In the code example, you could swap the independent definition of f and growth without any consequence. In the initial sequenced code, you must to the calculation in the right order due to the res variable – Christophe Nov 17 '19 at 10:58
  • Thanks.The way you articulate and explain things, I must say - wow !!! – rahulaga_dev Nov 17 '19 at 11:06
  • 1
    @RahulAgarwal I unfortunately cannot tell about Scala and Haskel, which i do not know. You can learn FP with any langugage that supports FP. What helps to grasp the spirit is a language in which even conditionals are an expression, a language that allows to define function by using pattern matching, that allows a function to return a function, and very important IMHO: allows easy composition using pipelining (pipelining example with |>here: glot.io/snippets/fhxk9xuc6f) – Christophe Nov 17 '19 at 11:09
2

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.

| improve this answer | |
  • 2
    I don't understand the point you're making with your code example. – Robert Harvey Nov 16 '19 at 12:47
  • @RobertHarvey: You cannot replace the code in the example which a function or subroutine call. But an expression can always be replaced with a function call. Therefore expressions are more composable. – JacquesB Nov 16 '19 at 12:50
  • thx !! would you mind explaining what you meant by combination can be used in place of its parts – rahulaga_dev Nov 16 '19 at 12:50
  • @RahulAgarwal: A mathematical expression is still a mathematical expression after you simplify, evaluate or transform it. – Robert Harvey Nov 16 '19 at 13:15
1

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.

| improve this answer | |
  • thanks Karl !! what if I would have refactored it like checkDone(done) { if(done) return result;} and then used it as checDone(done) ? – rahulaga_dev Nov 17 '19 at 6:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.