Simone Bordet Mario Fusco OOP and FP Become a Better Programmer
Our definition Of OOP and FP
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
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
Goals
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
Example #1 “accumulator”
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
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
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
Example #2 “what do you do ?”
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; }
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
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
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 !?!
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.
Example #3 “let's group them”
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); }
Simone Bordet Mario Fusco Example #3, v2 Map<Integer, List<Student>> studentByGradYear = students.stream() .collect(groupingBy(student::getGradYear));
Example #4 “separation of concerns”
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(); }
Simone Bordet Mario Fusco Example #4, v2 List<String> errors = Files.lines(Paths.get(fileName)) .filter(l -> l.startsWith("ERROR")) .limit(40) .collect(toList());
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());
Example #5 “grep -B 1”
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(); }
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
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());
Example #6 “callback hell”
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); }); }
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 !
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));
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));
Example #7 “statefulness”
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 { }
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; }
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 { }
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
Example #8 “encapsulation”
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. } }
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
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
Conclusions
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.
Simone Bordet Mario Fusco Questions & Answers

OOP and FP - Become a Better Programmer

  • 1.
    Simone Bordet Mario Fusco OOPand FP Become a Better Programmer
  • 2.
  • 3.
    Simone Bordet Mario Fusco OurDefinition 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.
    Simone Bordet Mario Fusco Ourdefinition 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.
  • 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.
  • 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.
    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.
    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.
  • 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.
    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.
    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.
    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.
    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.
  • 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.
    Simone Bordet Mario Fusco Example#3, v2 Map<Integer, List<Student>> studentByGradYear = students.stream() .collect(groupingBy(student::getGradYear));
  • 20.
  • 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.
    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.
    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.
  • 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.
    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.
    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.
  • 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.
    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.
    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.
    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.
  • 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.
    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.
    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.
    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.
  • 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.
    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.
    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.
  • 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.