|
| 1 | +#Elements of Programming |
| 2 | +Every non trivial programming language provides: |
| 3 | + |
| 4 | +* primitive expressions representing the simplest elements |
| 5 | +* ways to combine expressions |
| 6 | +* ways to *abstract* expressions, which introduce a name for an expression by which it can then be referred to |
| 7 | + |
| 8 | + |
| 9 | +###The read-eval-print Loop |
| 10 | +Functional programming is a bit like using a calculator... most functional languages have an interactive shell, or REPL, that lets us write expressions and responds with their value. We can start the scala repl by typing scala, or by typing sbt console |
| 11 | + |
| 12 | +###Evaluation |
| 13 | +A non-primitive expression is evaluated as follows: |
| 14 | + |
| 15 | +* take the leftmost operator |
| 16 | +* evaluate its operands (left before right) |
| 17 | +* apply the operator to the operands |
| 18 | + |
| 19 | +A name is evaluated by replacing it with the right hand side of its definition. |
| 20 | + |
| 21 | +We apply these steps one by one until an evaluation results in a value - for the moment, a value is just a number. |
| 22 | + |
| 23 | +####Example |
| 24 | +>_(2 * pi) * radius_ |
| 25 | +
|
| 26 | +First we look up the value of pi: |
| 27 | +>_(2 * 3.14159) * radius_ |
| 28 | +
|
| 29 | +Then we perform the arithmetic operation on the left: |
| 30 | +>_6.28318 * radius_ |
| 31 | +
|
| 32 | +Then we look up the value for radius and finally we perform our final multiplication, yielding the result |
| 33 | +>_6.28318 * 10_ |
| 34 | +
|
| 35 | +> _62.8318_ |
| 36 | +
|
| 37 | +###Parameters |
| 38 | +Definitions can have parameters - for instance: |
| 39 | + |
| 40 | + def square(x: Double) = x * x |
| 41 | + def sumOfSquares(x: Double, y: Double) = square(x) + square(y) |
| 42 | + |
| 43 | +###Parameter and Return Types |
| 44 | +Function parameters come with their type, given after a colon. If a return type is given, it follows the parameter list |
| 45 | + |
| 46 | + def power(x: Double, y: Int): Double = ... |
| 47 | + |
| 48 | +###Evaluation of Function Applications |
| 49 | +Applications of parameterized functions are evaluated in a similar way as operators: |
| 50 | + |
| 51 | +* evaluate all function arguments, left to right |
| 52 | +* replace the function application by the function's right hand side, and at the same time |
| 53 | +* replace the formal parameters of the function by the actual arguments |
| 54 | + |
| 55 | +> _sumOfSquares(3, 2+2)_ |
| 56 | +
|
| 57 | +evaluates to |
| 58 | + |
| 59 | +> _sumOfSquares(3, 4)_ |
| 60 | +
|
| 61 | +then, we take the definition of sum of squares and plop it in: |
| 62 | + |
| 63 | +>_square(3) + square(4)_ |
| 64 | +
|
| 65 | +then we repeat the process with the square application |
| 66 | + |
| 67 | +>_3 * 3 + square(4)_ |
| 68 | +
|
| 69 | +>_9 + square(4)_ |
| 70 | +
|
| 71 | +>_9 + 4 * 4_ |
| 72 | +
|
| 73 | +>_9 + 16_ |
| 74 | +
|
| 75 | +>_25_ |
| 76 | +
|
| 77 | +###The substitution model |
| 78 | +This scheme of expression evaluation is called the substitution model. The idea is that all evaluation does is *reduce an expression to a value* |
| 79 | + |
| 80 | +Simple as it is, it's been shown that it can express every algorithm, and thus is equivalent to a Turing machine. ([Alonzo Church and the lambda calculus](http://en.wikipedia.org/wiki/Lambda_calculus)) |
| 81 | + |
| 82 | +The substitution can be applied to all expressions, as long as they have no side effects. What is a side effect? |
| 83 | + |
| 84 | + c++ |
| 85 | + |
| 86 | +Evaluating this expression means that we would: return the old value of c, and at the same time, we increment the value. Turns out there's no good way to represent this evaluation sequence by a simple rewriting of the term; we need something else, like a store where the current value of the variable is kept. |
| 87 | + |
| 88 | +In other words, the express c++ has a side effect on the current value of the value; that side effect cannot be expressed by the substitution model. |
| 89 | + |
| 90 | +One of the motivations for ruling out side effects in FP is that we can keep to a simple model of evaluation. |
| 91 | + |
| 92 | +###Termination |
| 93 | +Once we have the substitution model, another question comes up: does every expression reduce to a value (in a finite number of steps?) |
| 94 | + |
| 95 | +Nope. Eg: |
| 96 | + |
| 97 | + def loop: Int = loop |
| 98 | + loop |
| 99 | + |
| 100 | +So, what would happen here? According to our model, we have to evaluate that name by replacing it with what's on its right hand side. |
| 101 | + |
| 102 | + loop -> loop -> loop.... |
| 103 | + |
| 104 | +We have reduced the name to itself, so this expression will never terminate. |
| 105 | + |
| 106 | +###Changing the evaluation strategy |
| 107 | +The scala interpreter will reduce function arguments to values before rewriting the function application - that's not the only evaluation strategy. |
| 108 | + |
| 109 | +One could alternatively apply the function to unreduced arguments: |
| 110 | + |
| 111 | +>_sumOfSquares(3, 2 + 2)_ |
| 112 | + |
| 113 | +In this strategy, we keep the right hand side, and don't reduce 2+2 to 4. We simply pass it as an expression to the square function |
| 114 | + |
| 115 | +>_square(3) + square(2+2)_ |
| 116 | +
|
| 117 | +We keep it around until the last possible evaluation, even ending up with an evaluation of square(2+2) that yields (2 + 2) * (2 + 2) |
| 118 | + |
| 119 | +>_3*3 + square(2+2)_ |
| 120 | +
|
| 121 | +>_9 + square(2+2)_ |
| 122 | +
|
| 123 | +>_9 + (2 + 2) * (2 + 2)_ |
| 124 | +
|
| 125 | +>_9 + 4 * (2+2)_ |
| 126 | +
|
| 127 | +>_9 + 4 * 4_ |
| 128 | +
|
| 129 | +>_25_ |
| 130 | +
|
| 131 | +###Call by name and Call by value |
| 132 | +We've seen two evaluation strategies - the first is call by value, and the last is call by name. |
| 133 | + |
| 134 | +An important theory of lamda calculus is that both strategies reduce to the same final values, as long as: |
| 135 | + |
| 136 | +* the reduced expression consists of pure functions and |
| 137 | +* both evaluations terminate |
| 138 | + |
| 139 | +Call by value has the advantage that every function argument is only evaluated once. |
| 140 | + |
| 141 | +Call by name has the advantage that a function argument is not evaluated at all if the corresponding parameter is unused in the evaluation of the function body |
| 142 | + |
| 143 | +Call by value is basically, reduce all parameters first, then apply functions |
| 144 | +Call by name is basically, apply functions, then reduce parameters |
| 145 | + |
| 146 | +###CBN, CBV, and termination |
| 147 | +We know that the two evaluation strategies reduce an expression to the same value, as long as both evaluations terminate. What if they don't terminate? |
| 148 | + |
| 149 | +There we have an important theorum, which says if the CBV evaluation of an expression e terminates, then CBN evaluation of e terminates too. |
| 150 | + |
| 151 | +The other direction is *not* true. |
| 152 | + |
| 153 | +For example: |
| 154 | + |
| 155 | + def first(x: Int, y: Int) = x |
| 156 | + |
| 157 | +consider the expression first(1, loop): |
| 158 | + |
| 159 | +Under CBN, we'll reduce the first expression without reducing the argument, and it would just yield 1 in the first step, since first() doesn't do anything with the second parameter. |
| 160 | + |
| 161 | +Under CBV, we have to reduce the arguments to this expression, so we have to reduce loop - loop reduces to itself, infinitely, and we'd make no progress |
| 162 | + |
| 163 | +###Scala's evaluation strategy |
| 164 | +Scala normally uses call by value. Why? Well, in practice it turns out that CBV is exponentially more efficient than CBN, because it avoids this repeated recomputation of argument expressions that CBN entails. |
| 165 | + |
| 166 | +Other argument for CBV is that it plays much nicer with imperative effects and side effects, because you tend to know much better when expressions will be evaluated |
| 167 | + |
| 168 | +Except that, sometimes you really want to force CBN - you can do that in Scala by adding a name in front of the type: |
| 169 | + |
| 170 | + def constOne(x: Int, y: => Int) = 1 |
0 commit comments