static int doesSideEffects(string text, int a, int b){
Console.Write(text);
return a + b;
}
static void Main(){
int result1, result2;
// statement 1:
result1 = doesSideEffects("first one!", 1, 2);
// statement 2:
result2 = doesSideEffects("second one!", 3, 4);
Console.Write(result1 + result2);
}
Can we shuffle the statement 1 and 2? Can we do them at the same time? The answer is no, because we must guarrantee that the first statement's Console.Write() happens before the second one.
static int noEffects(string text, int a, int b){
Console.Write(text);
return a + b;
}
static void Main(){
int result1, result2;
// statement 1:
result1 = noEffects("first one!", 1, 2);
// statement 2:
result2 = noEffects("second one!", 3, 4);
Console.Write(result1 + result2);
}
Can we shuffle the statement 1 and 2? Can we do them at the same time? The answer is yes, because result2 does not rely on result1, and the function call has no sideeffects, so it doesn't matter if result2 is computed before result1.
Some things that a pure (or shall we say a function which is constant with respect to it's arguments) function have that a side-effecting function doesn't have include:
Let A and B be one or more statements, which contain no side-effects.
Let AB denote the instruction or set of instructions A followed by the instructions in B.
1. AB has no side-effects.
2. If B does not depend on A, then AB = BA
3. If A is a function that takes arguments c1 ... cn, and c1... cn are known at the time of compilation, then the result of A can be fully determined at compile-time (i.e. never needs to be computed in the program, which makes your program faster).
4. If B does not depend on A, then A and B can be executed on different threads at the same time.
5. If A1 ... An are independent, non-side-efffecting blocks, then loops such as
int[] arr = new int[10000];
for(int i=0;i<arr.Length;i++){
arr[i] = noEffectButLongFunction(i);
}
Can be split across a number of threads without penalty.
6. If A is a constructor which is fully disposed before it is ever passed to, or assigned from a side-effecting function, it can be at least partly optimised away, e.g.
class ComplexNumer {
int re, im;
[NoEffects]
ComplexNumer(int re, int im){
this.re = re; this.im = im;
}
[NoEffects]
public static operator ComplexNumber +(ComplexNumber a, ComplexNumber b){
return new ComplexNumber(a.re + b.re, a.im + b.im);
}
[NoEffects]
public double Magnitude {
// sqrt has no side-effects.
return Math.Sqrt(re * re + im*im);
}
}
public int Main(){
double mag = (new ComplexNumber(1,0) + new ComplexNumber(4,3)).Magnitude;
}
What optimisations can we do to Main?
Step1: Let's make everything explicit:
int mag = Operator+( new ComplexNumber(1,0), new ComplexNumber(4, 3) );
but because new ComplexNumber() is constant with respect to its arguments, we know at compile time the value of new ComplexNumber(1,0) and ComplexNumber(4,3).
but Operator+ is constant with respect to its arguments, so we know that it's value can be fully determined at compile time, so we evaluate it to get
new ComplexNumber(5, 3).Magnitude.
But .Magnitude is constant with respect to it's arguments, and we know ComplexNumber(5,3) at compile time, so we can evaluate it to be Math.Sqrt(34). But Math.Sqrt is c wrt. args, so we can even evaluate it at compile time!
So ultimately we have reduced a potentially costly expression like
public int Main(){
double mag = (new ComplexNumber(1,0) + new ComplexNumber(4,3)).Magnitude;
}
to
public int Main(){
double mag = 5.830952;
}
entirely at compile time, and thus your program will run faster, and more errors can be caught before you ever ship the program to your customers.
And less errors for customers and more (provably correct) parrallized code means that your programs will be more reliable and faster. So to sum up, pure functions and functional programming techniques leads to more money and prestige for you.