In imperative programming, we usually have code that looks the following way:
func addOneToSlice(xs []int) []int {
rs := make([]int, len(xs))
for i, value := range xs {
rs[i] = value + 1
}
return rs
}
However, notice the following about the for
loop:
- Each iteration has a specific purpose, which is to add one to the current element.
- However, each iteration has no constraint on which element it can operate.
- Operating with
xs[i+2]
andrs[i+3]
wouldn't fundamentally alter the structure of the code we have, while making the end result incorrect.
Compare how the same task would be done in F#:
let rec addOneToList =
function
| [] -> []
| x :: xs -> x + 1 :: addOneToList xs
Now consider the following:
- We have a list as a function argument.
- A list in functional languages is a linked list.
- The efficient and standard operations on linked lists are:
- Separating the head
x
from its tailxs
- Doing something to the head
x
- Comparing the list passed as a parameter with the empty list
[]
- Separating the head
Given these restrictions, adding 1
to any element y
not at the head of the list would significantly alter the structure of our function.
Now compare how the computation progresses in both styles:
- In the functional style, we create a new scope with new values, which involves making a recursive call in the example above.
- In the imperative style, we mutate an existing value without changing the scope.
In functional style, marrying both scope with computational progress has the following consequences:
- We avoid mutation.
- The execution flow is explicit.
- The structure we are dealing with becomes clear.
Top comments (0)