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

rahulaga-msft
  • 1,402
  • 1
  • 11
  • 24
  • @DocBrown : I found that answer but was finding it difficult to understand – rahulaga-msft 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-msft Nov 16 '19 at 10:31
  • Could this help: https://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-msft 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
  • @DocBrown thanks for making it precise and bringing out subtleties for better response – rahulaga-msft Nov 16 '19 at 12:12

3 Answers3

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.

JacquesB
  • 57,310
  • 21
  • 127
  • 176
  • 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-msft 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

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.

Christophe
  • 74,672
  • 10
  • 115
  • 187
  • 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-msft Nov 17 '19 at 03: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-msft Nov 17 '19 at 06: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-msft 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: https://glot.io/snippets/fhxk9xuc6f) – Christophe Nov 17 '19 at 11:09
  • @RahulAgarwal Thank you very much for this positive feedback. – Christophe Nov 17 '19 at 11:10
  • one more point. When people talk about favoring usage of expression vs statement in FP, I believe they mean expressions that have a property of `referential transparency` and not expression in general. Am I correct ? – rahulaga-msft Nov 24 '19 at 09:17
  • @rahulaga_dev interesting remark. The referential transparency is the reason behind the purity. It’s a guaranty of no surprise and side effects. But personally, I think that the quest for expressions is orthogonal to the referential transparency. You’ll find it also in traditional languages, e.g. when people are chaining (sometimes impure) functions in referentially opaque expression, just because it’s practical to express the operation in this way. By the way, keep in mind that impure and opaque starts with producing some output for the user ;-) – Christophe Nov 24 '19 at 09:59
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.

Karl Bielefeldt
  • 146,727
  • 38
  • 279
  • 479
  • thanks Karl !! what if I would have refactored it like `checkDone(done) { if(done) return result;}` and then used it as `checDone(done)` ? – rahulaga-msft Nov 17 '19 at 06:09