Search code examples
functional-programmingtheoryparadigms

What is order of execution and how does it affect code?


I am trying to understand the difference between functional programming and imperative programming (and all other derivatives). However, I am stumped by one simple concept in which many websites and books never explain: what is an order of execution.

From what I understand, functional programming does not place importance on order of execution, while imperative programming does place a very high importance on it.

However, I do not understand this order of importance. I mean, if I multiplied and then divided an input number (uno = input * 5000 followed by dos = uno / 300), would using a functional language mean that the program may execute the arithmetic operations in any way it pleases (without any regard to any rule, such as order of operations)? Where's the output predictability in functional programming?


Solution

  • Order of evaluation is the order the program expands functions into their bodies until you are left with primitives and the reduction of those to compute a result. Eg.

    test(fun1(), fun2(), fun3());
    

    Imagine this is an eager language, In order to apply test you have to have the values produced from fun1-3. In some languages the runtime is free to choose to expand fun3 first, then fun1, and take fun2 last. If no side effects are used it doesn't matter. If each one is displaying something on screen you suddenly have 6 different outcomes.

    In functional programming you avoid side effects in most of your code. Without side effects the order of evaluation really doesn't matter.

    When that said in all programming languages there are methods to force a particular order. In eager languages that have no evaluation order on arguments, like Scheme, you nest the expressions in their own lambda forms.

    ((lambda (val1) 
      ((lambda (val2) 
        ((lambda (val3) (/ (* val1 val2) val3)) 
         expression3)) ; evaluated third
       expression2))   ; evaluated second
     expression1)      ; evaluated first
    

    This is of course what let* does so you could write this:

    (let* ((val1 expression1)
           (val2 expression2)
           (val3 expression3))
      (/ (* val1 val2) val3))
    

    Haskell, which is a purely functional lazy evaluation language can do the exact same thing using monads. Then your function to produce the next value is dependant on some value returned on the previous. The chain makes the order of execution a steady path.

    Now you don't want to decide the order where the order is not important. Then the compiler can decide and perhaps it ends up being done in parallel or do the shortest job first after constant folding.