Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

OOP and FP - Become a Better Programmer

5,288 views

Published on

The story of Simon, an experienced OOP Java developer, exposed to the new lambda features of JDK 8. His friend Mario, a long-bearded FP geek, will try to convince him that FP can help him develop more readable and maintainable code. A journey into the discovery of the main new feature - lambda expressions - of JDK 8

Published in: Software
  • Be the first to comment

OOP and FP - Become a Better Programmer

  1. 1. Simone Bordet Mario Fusco OOP and FP Become a Better Programmer
  2. 2. Our definition Of OOP and FP
  3. 3. Simone Bordet Mario Fusco Our Definition of OOP  In this presentation “OOP” will mean:  Idiomatic Java 7 programming style:  Use of mutable variables and state  Use of classes and void methods  Use of external iteration (for loops)  Use of threads
  4. 4. Simone Bordet Mario Fusco Our definition of FP  In this presentation “FP” will mean:  Java 8 programming style, with:  Immutable variables and state  Use classes, but avoid void methods  Internal iteration via Stream  CompletableFuture, not Thread
  5. 5. Goals
  6. 6. Simone Bordet Mario Fusco Goals  This session is about OOP and FP  NOT about OOP versus FP  We want to show that in order to be a better programmer, you have to know both  In some cases it's better to apply one paradigm  In other cases it's better to apply the other  We will hint at some guideline that helps deciding
  7. 7. Example #1 “accumulator”
  8. 8. Simone Bordet Mario Fusco Example #1, v1 public String sum(List<Student> students) { StringBuilder sb = new StringBuilder(); for (Student s : students) sb.append(s.getName()).append(“, “); return sb.toString(); }  OOP style  External iteration  Mutable variables
  9. 9. Simone Bordet Mario Fusco Example #1, v2 public String sum(List<Student> students) { StringBuilder sb = new StringBuilder(); students.stream() .forEach(s -> sb.append(s.getName()).append(“, “)); return sb.toString(); }  BAD style  Use of mutable accumulator
  10. 10. Simone Bordet Mario Fusco Example #1, v3 public String sum(List<Student> students) { String names = students.stream() .map(s -> s.getName() + “, “) .reduce(“”, (a, b) -> a + b); return names; }  FP style  Internal iteration  Mapping input to output  No mutable variables
  11. 11. Example #2 “what do you do ?”
  12. 12. Simone Bordet Mario Fusco Example #2, v1  What does this code do ? List<Student> students = ...; int min = 0; for (Student s : students) { if (s.getGradYear() != 2014) continue; int score = s.getGradScore(); if (score > min) min = score; }
  13. 13. Simone Bordet Mario Fusco Example #2, v2  Calculates the max, not the min !  And only for 2014 students ! List<Student> students = ...; students.stream() .filter(s -> s.getGradYear() == 2014) .mapToInt(Student::getScore) .max();  Somehow clearer to read  Less possibility of mistakes  max() is a method, not a variable name
  14. 14. Simone Bordet Mario Fusco Example #2, v3  But how do you get 2 results iterating once ? List<Student> students = ...; int min = Integer.MAX_VALUE, max = 0; for (Student s : students) { int score = s.getGradScore(); min = Math.min(min, score); max = Math.max(max, score); }  Easy and readable
  15. 15. Simone Bordet Mario Fusco Example #2, v4  FP version:  Pair<Integer, Integer> result = students.stream() .map(s -> new Pair<>(s.getScore(), s.getScore())) .reduce(new Pair<>(Integer.MAX_VALUE, 0), (acc,elem)-> { new Pair<>(Math.min(acc._1, elem._1), Math.max(acc._2, elem._2)) });  What !?!
  16. 16. Simone Bordet Mario Fusco Example #2, v5  How about parallelizing this ? Pair<Integer, Integer> result = students.stream().parallel() .map(s -> new Pair<>(s.getScore(), s.getScore())) .reduce(new Pair<>(Integer.MAX_VALUE, 0), (acc,elem)-> { new Pair<>(Math.min(acc._1, elem._1), Math.max(acc._2, elem._2)) });  Neat, but .parallel() can only be used under very strict conditions.
  17. 17. Example #3 “let's group them”
  18. 18. Simone Bordet Mario Fusco Example #3, v1  Group students by their graduation year Map<Integer, List<Student>> studentByGradYear = new HashMap<>(); for (Student student : students) { int year = student.getGradYear(); List<Student> list = studentByGradYear.get(year); if (list == null) { list = new ArrayList<>(); studentByGradYear.put(year, list); } list.add(student); }
  19. 19. Simone Bordet Mario Fusco Example #3, v2 Map<Integer, List<Student>> studentByGradYear = students.stream() .collect(groupingBy(student::getGradYear));
  20. 20. Example #4 “separation of concerns”
  21. 21. Simone Bordet Mario Fusco Example #4, v1  Read first 40 error lines from a log file List<String> errorLines = new ArrayList<>(); int errorCount = 0; BufferedReader file = new BufferedReader(...); String line = file.readLine(); while (errorCount < 40 && line != null) { if (line.startsWith("ERROR")) { errorLines.add(line); errorCount++; } line = file.readLine(); }
  22. 22. Simone Bordet Mario Fusco Example #4, v2 List<String> errors = Files.lines(Paths.get(fileName)) .filter(l -> l.startsWith("ERROR")) .limit(40) .collect(toList());
  23. 23. Simone Bordet Mario Fusco Example #4 List<String> errorLines = new ArrayList<>(); int errorCount = 0; BufferedReader file = new BufferedReader(new FileReader(filename)); String line = file.readLine(); while (errorCount < 40 && line != null) { if (line.startsWith("ERROR")) { errorLines.add(line); errorCount++; } line = file.readLine(); } return errorLines; return Files.lines(Paths.get(fileName)) .filter(l -> l.startsWith("ERROR") .limit(40) .collect(toList());
  24. 24. Example #5 “grep -B 1”
  25. 25. Simone Bordet Mario Fusco Example #5, v1  Find lines starting with “ERROR” and previous line List<String> errorLines = new ArrayList<>(); String previous = null; String current = reader.readLine(); while (current != null) { if (current.startsWith("ERROR")) { if (previous != null) errorLines.add(previous); errorLines.add(current); } previous = current; current = reader.readLine(); }
  26. 26. Simone Bordet Mario Fusco Example #5, v2  Not easy – immutability is now an obstacle  Must read the whole file in memory  This does not work: Stream.generate(() -> reader.readLine())  readLine() throws and can't be used in lambdas
  27. 27. Simone Bordet Mario Fusco Example #5, v2 Files.lines(Paths.get(filename)) .reduce(new LinkedList<String[]>(), (list, line) -> { if (!list.isEmpty()) list.getLast()[1] = line; list.offer(new String[]{line, null}); return list; }, (l1, l2) -> { l1.getLast()[1] = l2.getFirst()[0]; l1.addAll(l2); return l1; }).stream() .filter(ss -> ss[1] != null && ss[1].startsWith("ERROR")) .collect(Collectors.toList());
  28. 28. Example #6 “callback hell”
  29. 29. Simone Bordet Mario Fusco Example #6, v1  Find a term, in parallel, on many search engines, then execute an action final List<SearchEngineResult> result = new CopyOnWriteArrayList<>(); final AtomicInteger count = new AtomicInteger(engines.size()); for (Engine e : engines) { http.newRequest(e.url("codemotion")).send(r -> { String c = r.getResponse().getContentAsString(); result.add(e.parse(c)); boolean finished = count.decrementAndGet() == 0; if (finished) lastAction.perform(result); }); }
  30. 30. Simone Bordet Mario Fusco Example #6, v1  Code smells  Mutable concurrent accumulators: result and count  Running the last action within the response callback  What if http.newRequest() returns a CompletableFuture ?  Then I would be able to compose those futures !  Let's try to write it !
  31. 31. Simone Bordet Mario Fusco Example #6, v2 CompletableFuture<List<SearchEngineResult>> result = CompletableFuture.completed(new CopyOnWriteArrayList<>()); for (Engine e : engines) { CompletableFuture<Response> request = http.sendRequest(e.url("codemotion")); result = result.thenCombine(request, (list, response) -> { String c = response.getContentAsString(); list.add(e.parse(c)); return list; }); } result.thenAccept(list -> lastAction.perform(list));
  32. 32. Simone Bordet Mario Fusco Example #6, v3 List<CompletableFuture<SearchEngineResult>> results = engines.stream() .map(e -> new Pair<>(e, http.newRequest(e.url("codemotion")))) .map(p -> p._2.thenCombine(response -> p._1.parse(response.getContentAsString()))) .collect(toList()); CompletableFuture.supplyAsync(() -> results.stream() .map(future -> future.join()) .collect(toList())) .thenApply(list -> lastAction.perform(list));
  33. 33. Example #7 “statefulness”
  34. 34. Simone Bordet Mario Fusco Example #7, v1 class Cat { private Bird prey; private boolean full; void chase(Bird bird) { prey = bird; } void eat() { prey = null; full = true; } boolean isFull() { return full; } } class Bird { }
  35. 35. Simone Bordet Mario Fusco Example #7, v1  It is not evident how to use it: new Cat().eat() ???  The use case is instead: Cat useCase(Cat cat, Bird bird) { cat.chase(bird); cat.eat(); assert cat.isFull(); return cat; }
  36. 36. Simone Bordet Mario Fusco Example #7, v2  How about we use types to indicate state ? class Cat { CatWithPrey chase(Bird bird) { return new CatWithPrey(bird); } } class CatWithPrey { private final Bird prey; public CatWithPrey(Bird bird) { prey = bird; } FullCat eat() { return new FullCat(); } } class FullCat { }
  37. 37. Simone Bordet Mario Fusco Example #7, v2  Now it is evident how to use it: FullCat useCase(Cat cat, Bird bird) { return cat.chase(bird).eat(); } BiFunction<Cat, Bird, CatWithPrey> chase = Cat::chase; BiFunction<Cat, Bird, FullCat> useCase = chase.andThen(CatWithPrey::eat);  More classes, but clearer semantic
  38. 38. Example #8 “encapsulation”
  39. 39. Simone Bordet Mario Fusco Example #8, v1 interface Shape2D { Shape2D move(int deltax, int deltay) } class Circle implements Shape { private final Point center; private final int radius; Circle move(int deltax, int deltay) { // translate the center } } class Polygon implements Shape { private final Point[] points; Polygon move(int deltax, int deltay) { // Translate each point. } }
  40. 40. Simone Bordet Mario Fusco Example #8, v1 for (Shape shape : shapes) shape.move(1, 2);  How do you do this using an FP language ?  What is needed is dynamic polymorphism  Some FP language does not have it  Other FP languages mix-in OOP features
  41. 41. Simone Bordet Mario Fusco Example #8, v2 defn move [shape, deltax, deltay] ( // Must crack open shape, then // figure out what kind of shape is // and then translate only the points )  OOP used correctly provides encapsulation  FP must rely on OOP features to provide the same  Data types are not enough  Pattern matching is not enough  Really need dynamic polimorphism
  42. 42. Conclusions
  43. 43. Simone Bordet Mario Fusco Conclusions  If you come from an OOP background  Study FP  If you come from an FP background  Study OOP  Poly-paradigm programming is more generic, powerful and effective than polyglot programming.
  44. 44. Simone Bordet Mario Fusco Questions & Answers

×