Rubyslava / PyVo #32
26.09.2013
Imperative versus Functional Programming
Jan Herich
@janherich
@itedge
Core aspects of
Imperative Programming
Core aspects of
Imperative Programming
● Emphasis on mutable state
➢ In place modification of variables (memory locations)...
Core aspects of
Imperative Programming
● Emphasis on mutable state
➢ In place modification of variables (memory locations)...
Core aspects of
Imperative Programming
● Emphasis on mutable state
➢ In place modification of variables (memory locations)...
What's wrong with
Imperative programming ?
What's wrong with
Imperative programming ?
● Uncoordinated mutation
➢ No built-in facilities in the language to coordinate...
What's wrong with
Imperative programming ?
● Uncoordinated mutation
➢ No built-in facilities in the language to coordinate...
Example of harmful mutation
Our initial objects:
var record1 = {state_id:'S2',county_id:'C1',population:3439,area:97};
var...
Example of harmful mutation
Reasonably nice function without side effects:
var groupRecords = function(records,key) {
var ...
Example of harmful mutation
Reasonably nice function without side effects:
var groupRecords = function(records,key) {
var ...
How can we do better ?
How can we do better ?
● What if every datastructure in your program would be immutable ?
➢ It would solve our problem wit...
How can we do better ?
● What if every datastructure in your program would be immutable ?
➢ It would solve our problem wit...
How can we do better ?
● What if every datastructure in your program would be immutable ?
➢ It would solve our problem wit...
How persistent data structures work
● Creation of new
datastructures must be fast
and efficient.
● This is achieved by str...
Our example revisited
Our initial references to VALUES:
(def record-1 {:state-id "S2" :county-id "C1" :population 3439 :ar...
Our example revisited
(defn group-records [records key]
(reduce (fn [accumulator record]
(let [key-val (get record key)
su...
Our example revisited
(defn group-records [records key]
(reduce (fn [accumulator record]
(let [key-val (get record key)
su...
Key points from our
Imperative/Functional comparision
● In our imperative example, we created many
more identities then we...
But what if we really need
identities ?
● Most programs need identities
➢ There are programs resembling huge functions suc...
How do we model identities ?
● We need atomic references to values
➢ Because every 'value-swap' of the identity (remember,...
Example of atomic reference in
Clojure
We define out initial cache data:
(def initial-data ["D1" "D2" "D3"])
Now we create...
There is a lot more to discover
● There are more reference types in Clojure, but that is out of
scope of this talk
● And o...
Upcoming SlideShare
Loading in …5
×

Rubyslava slides-26.09.2013

484 views

Published on

Presentation about big benefits of immutable data and functional approach to programming in contrast with more traditional imperative style. Comparison of the code trying to solve the same problem written in imperative-style Javascript and then rewritten in Clojure

Published in: Technology
  • Be the first to comment

  • Be the first to like this

Rubyslava slides-26.09.2013

  1. 1. Rubyslava / PyVo #32 26.09.2013 Imperative versus Functional Programming Jan Herich @janherich @itedge
  2. 2. Core aspects of Imperative Programming
  3. 3. Core aspects of Imperative Programming ● Emphasis on mutable state ➢ In place modification of variables (memory locations) ➢ The flow of the program is determined by directly checking those memory locations → typical example is imperative looping : for(int i=1; i<11; i++) { System.out.println("Count is: " + i); }
  4. 4. Core aspects of Imperative Programming ● Emphasis on mutable state ➢ In place modification of variables (memory locations) ➢ The flow of the program is determined by directly checking those memory locations → typical example is imperative looping : for(int i=1; i<11; i++) { System.out.println("Count is: " + i); } ● Rooted in single-threaded premise ➢ Assuming that there is only one thread of execution ➢ So the world is effectively “stopped” when you look at it or change it
  5. 5. Core aspects of Imperative Programming ● Emphasis on mutable state ➢ In place modification of variables (memory locations) ➢ The flow of the program is determined by directly checking those memory locations → typical example is imperative looping : for(int i=1; i<11; i++) { System.out.println("Count is: " + i); } ● Rooted in single-threaded premise ➢ Assuming that there is only one thread of execution ➢ So the world is effectively “stopped” when you look at it or change it ● Prevalent in most OO languages ➢ C++, Java, C#, Python, Ruby, etc.
  6. 6. What's wrong with Imperative programming ?
  7. 7. What's wrong with Imperative programming ? ● Uncoordinated mutation ➢ No built-in facilities in the language to coordinate changes ➢ It could result in brittle systems, even without concurrency ➢ Add concurrency/parallelism and everything only get worse
  8. 8. What's wrong with Imperative programming ? ● Uncoordinated mutation ➢ No built-in facilities in the language to coordinate changes ➢ It could result in brittle systems, even without concurrency ➢ Add concurrency/parallelism and everything only get worse ● Complecting the state and identity in OO ➢ Object reference → Identity and value mixed together ➢ Object (identity) is a pointer to the memory that contains the value of its state ➢ There is no way to observe a stable state (even to copy it) without blocking others from changing it ➢ There is no way to associate the identity's state with a different value other than in-place memory mutation
  9. 9. Example of harmful mutation Our initial objects: var record1 = {state_id:'S2',county_id:'C1',population:3439,area:97}; var record2 = {state_id:'S5',county_id:'C2',population:85345,area:128}; var record3 = {state_id:'S2',county_id:'C3',population:7435,area:157};
  10. 10. Example of harmful mutation Reasonably nice function without side effects: var groupRecords = function(records,key) { var groups = {}; for (var i = 0; i < records.length; i++) { var current = records[i]; var keyValue = current[key]; if (!groups.hasOwnProperty(keyValue)) { groups[keyValue] = []; } groups[keyValue].push(current); } return groups; }; Our initial objects: var record1 = {state_id:'S2',county_id:'C1',population:3439,area:97}; var record2 = {state_id:'S5',county_id:'C2',population:85345,area:128}; var record3 = {state_id:'S2',county_id:'C3',population:7435,area:157}; New grouped datastructure: var grouped = groupRecords([record1, record2, record3], 'county_id');
  11. 11. Example of harmful mutation Reasonably nice function without side effects: var groupRecords = function(records,key) { var groups = {}; for (var i = 0; i < records.length; i++) { var current = records[i]; var keyValue = current[key]; if (!groups.hasOwnProperty(keyValue)) { groups[keyValue] = []; } groups[keyValue].push(current); } return groups; }; Ugly side-effect function, written by unexperienced programmer, who doesn't know much about object references: var sumRecords = function(records) { var first = records[0]; for (var i = 1; i < records.length; i++) { var current = records[i]; first.population += current.population; first.area += current.area; } return first; }; Our initial objects: var record1 = {state_id:'S2',county_id:'C1',population:3439,area:97}; var record2 = {state_id:'S5',county_id:'C2',population:85345,area:128}; var record3 = {state_id:'S2',county_id:'C3',population:7435,area:157}; New grouped datastructure: var grouped = groupRecords([record1, record2, record3], 'county_id'); We don't expect that this function call will mutate the former object record1: var state2summed = sumRecords(grouped.S2);
  12. 12. How can we do better ?
  13. 13. How can we do better ? ● What if every datastructure in your program would be immutable ? ➢ It would solve our problem with leaking mutable references all over the codebase
  14. 14. How can we do better ? ● What if every datastructure in your program would be immutable ? ➢ It would solve our problem with leaking mutable references all over the codebase ● But how can we model any progress if everything is static ? ➢ It's nice to be safe, but how can we actually accomplish anything if everything is immutable so we can't change it ? It turns out we can, if we use persistent data structures → that we can't change something in place doesn't mean that we can't model progress
  15. 15. How can we do better ? ● What if every datastructure in your program would be immutable ? ➢ It would solve our problem with leaking mutable references all over the codebase ● But how can we model any progress if everything is static ? ➢ It's nice to be safe, but how can we actually accomplish anything if everything is immutable so we can't change it ? It turns out we can, if we use persistent data structures → that we can't change something in place doesn't mean that we can't model progress ● What is a persistent data structure ? ➢ Persistent data structure is a data structure that always preserves the previous version of itself when it is modified. Such data structures are effectively immutable, as their operations do not (visibly) update the structure in-place, but instead always yield a new updated structure
  16. 16. How persistent data structures work ● Creation of new datastructures must be fast and efficient. ● This is achieved by structural sharing ● Obviously, the garbage collection is a must in this case xs d b g a c f h ys d' g' f' e
  17. 17. Our example revisited Our initial references to VALUES: (def record-1 {:state-id "S2" :county-id "C1" :population 3439 :area 97}) (def record-2 {:state-id "S5" :county-id "C2" :population 85345 :area 128}) (def record-3 {:state-id "S2" :county-id "C3" :population 7435 :area 157})
  18. 18. Our example revisited (defn group-records [records key] (reduce (fn [accumulator record] (let [key-val (get record key) subrecords (get accumulator key-val [])] (assoc accumulator key-val (conj subrecords record)))) {} records)) Our initial references to VALUES: (def record-1 {:state-id "S2" :county-id "C1" :population 3439 :area 97}) (def record-2 {:state-id "S5" :county-id "C2" :population 85345 :area 128}) (def record-3 {:state-id "S2" :county-id "C3" :population 7435 :area 157}) New merged VALUE: (def grouped (group-records [record-1 record-2 record-3] :state-id))
  19. 19. Our example revisited (defn group-records [records key] (reduce (fn [accumulator record] (let [key-val (get record key) subrecords (get accumulator key-val [])] (assoc accumulator key-val (conj subrecords record)))) {} records)) Our inexperienced programmer and his function again: (defn alter-records [records] ;; the first record remains unchaged ;; because it is a reference ;; to value, and values don't ;; change :) (assoc (first records) :population 0)) Our initial references to VALUES: (def record-1 {:state-id "S2" :county-id "C1" :population 3439 :area 97}) (def record-2 {:state-id "S5" :county-id "C2" :population 85345 :area 128}) (def record-3 {:state-id "S2" :county-id "C3" :population 7435 :area 157}) New merged VALUE: (def grouped (group-records [record-1 record-2 record-3] :state-id)) We created new VALUE state-2-altered, but our record-1 remains unchanged, even if those two VALUES partially share their structure: (def state-2-altered (alter-records (grouped "S2")))
  20. 20. Key points from our Imperative/Functional comparision ● In our imperative example, we created many more identities then we really needed. ● The identities we created (record1, record2, record2) would be better modeled as values instead. ● The value of values should not be undervalued :)
  21. 21. But what if we really need identities ? ● Most programs need identities ➢ There are programs resembling huge functions such as compilers, but most other programs need to model identities ● It's worthwhile to separate identity and state ➢ Instead of thinking about identity states as a contents of the particular memory block, it's better to think about it as a value currently associated with the identity ➢ The identity can be in different states in different times, but the state itself doesn't change ➢ Thus, the identity is not a state, the identity has a state, exactly one at any point of time
  22. 22. How do we model identities ? ● We need atomic references to values ➢ Because every 'value-swap' of the identity (remember, every identity has a state, which is immutable value) needs to be atomic (similar to atomic database commits, always resulting in consistent database state) ➢ In Clojure, those changes to references are controlled and coordinated by the system – so the cooperation is not optional and not manual ➢ The world moves forward due to the cooperative efforts of its participants and the programming language/system, Clojure, is in charge of world consistency management. The value of a reference (state of an identity) is always observable without coordination, and freely shareable between threads
  23. 23. Example of atomic reference in Clojure We define out initial cache data: (def initial-data ["D1" "D2" "D3"]) Now we create an special atomic reference – named cache: (def cache (atom initial-data)) We read the cache into some intermediate variable: (def cache-data (deref cache)) The result is true: (= initial-data cache-data) We swap the cache for different value -> we add "D4" and "D5" in this case: (swap! cache conj ["D4" "D5"]) Whenever the cache is dereferenced, we get the new data: (deref cache) But the former cache reading cache-data is still unchanged, so this remains true: (= initial-data cache-data)
  24. 24. There is a lot more to discover ● There are more reference types in Clojure, but that is out of scope of this talk ● And of course not only that, there are many, many more cool features in Clojure which you wouldn't find in other languages such as for example multimethods or true macros for metaprogramming ● Visit http://clojure.org/ to learn more

×