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

1,257 views

Published on

OOP and FP

Published in: Software
  • If you want a girl to "chase" you, then you have to use the right "bait". We discovered 4 specific things that FORCE a girl to chase after you and try to win YOU over. copy and visiting... ●●● http://ishbv.com/unlockher/pdf
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Stop getting scammed by online, programs that don't even work! ★★★ http://ishbv.com/ezpayjobs/pdf
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

OOP and FP

  1. 1. OOP vs and FP by Mario Fusco Red Hat – Principal Software Engineer @mariofusco
  2. 2. A bad (and mostly wrong) joke
  3. 3. ... and I did the same
  4. 4. A world of false dichotomies
  5. 5. A world of false dichotomies
  6. 6. A world of false dichotomies
  7. 7. A world of false dichotomies
  8. 8. False dichotimies in software engineering
  9. 9. False dichotimies in software engineering
  10. 10. False dichotimies in software engineering
  11. 11. False dichotimies in software engineering
  12. 12. OOP as we know it
  13. 13. Actually I made up the term "object-oriented", and I can tell you I did not have C++ in mind. Alan Kay
  14. 14. The way OOP is implemented in most common imperative languages is probably the biggest misunderstanding in the millenarian history of engineering This is Class Oriented Programming
  15. 15. A Smalltalk object can do exactly three things: OOP == Message Passing ✔ Hold state (references to other objects). ✔ Receive a message from itself or another object. ✔ In the course of processing a message, send messages to itself or another object.
  16. 16. A Smalltalk object can do exactly three things: OOP == Message Passing ✔ Hold state (references to other objects). ✔ Receive a message from itself or another object. ✔ In the course of processing a message, send messages to itself or another object. That’s the Actor Model !!!
  17. 17. Functional Programming Myths ✔ FP is new: it is older than computer, origining from 1930s lambda calculus. Haskell is 5 years older than Java
  18. 18. Functional Programming Myths ✔ FP is new: it is older than computer, origining from 1930s lambda calculus. Haskell is 5 years older than Java ✔ FP is well-defined: are lambdas enough? Should it follow the mathemathical definition? Something in the middle?
  19. 19. Functional Programming Myths ✔ FP is new: it is older than computer, origining from 1930s lambda calculus. Haskell is 5 years older than Java ✔ FP is well-defined: are lambdas enough? Should it follow the mathemathical definition? Something in the middle? ✔ FP is the opposite of OOP: they are orthogonal and can be effectively mixed in any program
  20. 20. Functional Programming Myths ✔ FP is new: it is older than computer, origining from 1930s lambda calculus. Haskell is 5 years older than Java ✔ FP is well-defined: are lambdas enough? Should it follow the mathemathical definition? Something in the middle? ✔ FP is the opposite of OOP: they are orthogonal and can be effectively mixed in any program ✔ FP is hard: OOP is not easier, just more familiar
  21. 21. Functional Programming Myths ✔ FP is less efficient: it’s the compiler job to produce efficient code ✔ FP is new: it is older than computer, origining from 1930s lambda calculus. Haskell is 5 years older than Java ✔ FP is well-defined: are lambdas enough? Should it follow the mathemathical definition? Something in the middle? ✔ FP is the opposite of OOP: they are orthogonal and can be effectively mixed in any program ✔ FP is hard: OOP is not easier, just more familiar
  22. 22. Functional Programming Myths ✔ FP is less efficient: it’s the compiler job to produce efficient code ✔ FP is good only for math-like stuff: it is good for any task including GUIs, think to functional reactive programming ✔ FP is new: it is older than computer, origining from 1930s lambda calculus. Haskell is 5 years older than Java ✔ FP is well-defined: are lambdas enough? Should it follow the mathemathical definition? Something in the middle? ✔ FP is the opposite of OOP: they are orthogonal and can be effectively mixed in any program ✔ FP is hard: OOP is not easier, just more familiar
  23. 23. Functional Programming Myths ✔ FP is less efficient: it’s the compiler job to produce efficient code ✔ FP is good only for math-like stuff: it is good for any task including GUIs, think to functional reactive programming ✔ FP is an all or nothing game: you can choose the amount of FP that is right for you according to your task, skills and tastes ✔ FP is new: it is older than computer, origining from 1930s lambda calculus. Haskell is 5 years older than Java ✔ FP is well-defined: are lambdas enough? Should it follow the mathemathical definition? Something in the middle? ✔ FP is the opposite of OOP: they are orthogonal and can be effectively mixed in any program ✔ FP is hard: OOP is not easier, just more familiar
  24. 24. Functional Programming Myths ✔ FP is less efficient: it’s the compiler job to produce efficient code ✔ FP is good only for math-like stuff: it is good for any task including GUIs, think to functional reactive programming ✔ FP is an all or nothing game: you can choose the amount of FP that is right for you according to your task, skills and tastes ✔ FP solves all problems: oh yes, you can write horrible code also doing pure functional programming ✔ FP is new: it is older than computer, origining from 1930s lambda calculus. Haskell is 5 years older than Java ✔ FP is well-defined: are lambdas enough? Should it follow the mathemathical definition? Something in the middle? ✔ FP is the opposite of OOP: they are orthogonal and can be effectively mixed in any program ✔ FP is hard: OOP is not easier, just more familiar
  25. 25. Object Oriented Programming Object-oriented programming (OOP) represents the concept of objects that have data fields (attributes that describe the object) and associated procedures known as methods. Definitions Functional Programming Functional programming (FP) treats computation as the evaluation and composition of mathematical functions and avoids changing-state and mutable data.
  26. 26. What’s the difference? All programs are functions operating on data OOP and FP seems to differ in the manner in which they bind functions and data together FP: f(o)OOP: o.f()
  27. 27. What’s the difference? (f o) All programs are functions operating on data OOP and FP seems to differ in the manner in which they bind functions and data together FP: f(o)OOP: o.f() But does it really matter? In reality it is just Functions ARE data, do you remember map/reduce? But does it really matter?
  28. 28. What’s the difference? (f o) All programs are functions operating on data polymorphism OOP and FP seems to differ in the manner in which they bind functions and data together FP: f(o)OOP: o.f() But does it really matter? In reality it is just Functions ARE data, do you remember map/reduce? But does it really matter?
  29. 29. Biggest OOP advantage: Polymorphism Polymorphism is THE thing that truly differentiates OO programs from non-OO ones It is true that it can be achieved in FP with long if/else chains, or better with pattern matching Polymorphism does NOT create a source code dependency from the caller to the callee BUT in OOP
  30. 30. OOP vs FP Decomposition eval toString hasZero ... Int Addition Negation ...
  31. 31. OOP vs FP Decomposition In OOP, break programs down into classes that give behaviour to some kind of data eval toString hasZero ... Int Addition Negation ... fill the grid with one class per row
  32. 32. OOP vs FP Decomposition In FP, break programs down into functions that perform some operations over its arguments In OOP, break programs down into classes that give behaviour to some kind of data eval toString hasZero ... Int Addition Negation ... fill the grid with one function per column fill the grid with one class per row
  33. 33. OOP Decomposition public interface Expression { int eval(); boolean hasZero(); String toString(); } public class Int implements Expression { private final int i; public Int( int i ) { this.i = i; } @Override public int eval() { return i; } @Override public boolean hasZero() { return i == 0; } @Override public String toString() { return "" + i; } } public class Addition implements Expression { private final Expression e1; private final Expression e2; public Addition( Expression e1, Expression e2 ) { this.e1 = e1; this.e2 = e2; } @Override public int eval() { return e1.eval() + e2.eval(); } @Override public boolean hasZero() { return e1.hasZero() || e2.hasZero(); } @Override public String toString() { return e1.toString() + " + " + e1.toString(); } } public class Negation implements Expression { private final Expression e; public Negation( Expression e ) { this.e = e; } @Override public int eval() { return - e.eval(); } @Override public boolean hasZero() { return e.hasZero(); } @Override public String toString() { return "-" + e.toString(); } }
  34. 34. FP Decomposition public interface Expression { } public class Int implements Expression { final int i; public Int( int i ) { this.i = i; } } public class Negation implements Expression { final Expression e; public Negation( Expression e ) { this.e = e; } } public class Addition implements Expression { final Expression e1; final Expression e2; public Addition( Expression e1, Expression e2 ) { this.e1 = e1; this.e2 = e2; } } static int eval(Expression expr) { if (expr instanceof Int) return (( Int ) expr).i; if (expr instanceof Negation) return - eval((( Negation ) expr).e); if (expr instanceof Addition) return eval((( Addition ) expr).e1) + eval((( Addition ) expr).e2); throw new UnsupportedOperationException( "Unknown type: " + expr.getClass()); } static boolean hasZero(Expression expr) { if (expr instanceof Int) return (( Int ) expr).i == 0; if (expr instanceof Negation) return hasZero((( Negation ) expr).e); if (expr instanceof Addition) return hasZero((( Addition ) expr).e1) || hasZero((( Addition ) expr).e2); throw new UnsupportedOperationException( "Unknown type: " + expr.getClass()); } static String toString(Expression expr) { if (expr instanceof Int) return "" + (( Int ) expr).i; if (expr instanceof Negation) return "-" + toString((( Negation ) expr).e); if (expr instanceof Addition) return toString((( Addition ) expr).e1) + " + " + toString((( Addition ) expr).e2); throw new UnsupportedOperationException( "Unknown type: " + expr.getClass()); }
  35. 35. Which one is better? To write an interpreter FP decomposition is more natural: I’m evaluating an expression and I have separate cases for the different expression’s kinds
  36. 36. Which one is better? To write an interpreter FP decomposition is more natural: I’m evaluating an expression and I have separate cases for the different expression’s kinds In a GUI there are lots of different things on the screen with different behaviours and response to user’s actions: better to keep these behaviours together with the graphical objects using OO decomposition
  37. 37. Which one is better? It’s mainly a matter of personal tastes and using the tool that fits better the problem at hand To write an interpreter FP decomposition is more natural: I’m evaluating an expression and I have separate cases for the different expression’s kinds In a GUI there are lots of different things on the screen with different behaviours and response to user’s actions: better to keep these behaviours together with the graphical objects using OO decomposition
  38. 38. Composition(?) Functional composition
  39. 39. Composition(?) OOP implements composition using 2 mechanisms: 1. Inheritance (IS-A) Functional composition
  40. 40. Composition(?) OOP implements composition using 2 mechanisms: 1. Inheritance (IS-A) 2. Association (HAS-A) Functional composition
  41. 41. Composition(?) OOP implements composition using 2 mechanisms: 1. Inheritance (IS-A) 2. Association (HAS-A), often implemented with dependency injection Functional composition
  42. 42. Biggest FP advantage: Immutability ✔ Thread-safety: immutable objects are inherently thread- safe since race conditions are impossible ✔ Parallelizability: no race conditions implies also no need for any synchronization ✔ Caching: the hardest problem in using a cache is invalidation, but with immutable objects it is not necessary ✔ Correctness: immutability makes it easier to write, read and reason about the code ✔ Consistency: once you are given an immutable object and verify its state, you know it will always remain safe ✔ Better encapsulation: objects can always be passed by reference and there is no need to have to worry about solutions like defensive copying
  43. 43. OOP makes code understandable by encapsulating moving parts FP makes code understandable by minimizing moving parts - Michael Feathers (im)mutability – OOP vs FP ... but this is only for historical reasons nothing mandates that OOP implies mutability
  44. 44. An expression e is referentially transparent if for all programs p, all occurrences of e in p can be replaced by the result of evaluating e, without affecting the observable behavior of p A function f is pure if the expression f(x) is referentially transparent for all referentially transparent x Referential transparency
  45. 45. RT String x = "purple"; String r1 = x.replace('p', 't'); String r2 = x.replace('p', 't'); String r1 = "purple".replace('p', 't'); r1: "turtle" String r2 = "purple".replace('p', 't'); r2: "turtle" StringBuilder x = new StringBuilder("Hi"); StringBuilder y = x.append(", mom"); String r1 = y.toString(); String r2 = y.toString(); String r1 = x.append(", mom").toString(); r1: "Hi, mom" String r2 = x.append(", mom").toString(); r1: "Hi, mom, mom" Non-RT vs.
  46. 46. Under a developer point of view: Easier to reason about since effects of evaluation are purely local Use of the substitution model: it's possible to replace a term with an equivalent one Under a performance point of view: The JVM is free to optimize the code by safely reordering the instructions No need to synchronize access to shared data Possible to cache the result of time consuming functions (memoization), e.g. with Map.computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction) Referential transparency means no side-effects
  47. 47. OOP vs FP Error Management public static int parseInt(String s) throws NumberFormatException What’s wrong with this signature?
  48. 48. OOP vs FP Error Management public static int parseInt(String s) throws NumberFormatException What’s wrong with this signature? 1. There isn’t anything exceptional in a String that cannot be parsed into a number 2. Breaking referential transparency prevents use of memoization public static OptionalInt parseInt(String s) Map<String, OptionalInt> cache = new HashMap<>(); public OptionalInt memoizedParse(String s) { return cache.computeIfAbsent(s, Integer::parseInt); } would allow memoization
  49. 49. ✔ Often abused, especially for flow control ✔ Checked Exceptions harm API extensibility/modificability ✔ They plays very badly with lambdas syntax ✔ Not composable: in presence of multiple errors only the first one is reported ✔ In the end just a GLORIFIED MULTILEVEL GOTO Use Exception only Exceptionally Exceptions should be used only for non-recoverable errors
  50. 50. OOP : FP = Imperative : Declarative List<String> errors = 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")) { errors.add(line); errorCount++; } line = file.readLine(); } List<String> errors = Files.lines(Paths.get(fileName)) .filter(l -> l.startsWith("ERROR")) .limit(40) .collect(toList());
  51. 51. What a beautiful code!
  52. 52. Let’s change it a bit
  53. 53. Let’s change it a bit We are now asked to create a report with the first 40 error lines AND the line before each of them
  54. 54. OOP version List<String[]> errors = new ArrayList<>(); int errorCount = 0; BufferedReader file = new BufferedReader(new FileReader(fileName)); String previous = null; String current = file.readLine(); while (errorCount < 40 && current != null) { if (current.startsWith("ERROR")) { errors.add(new String[] { previous, current }); errorCount++; } previous = current; current = file.readLine(); }
  55. 55. FP version 1 List<String[]> errors = 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")) .limit(40) .collect( Collectors.toList());
  56. 56. FP version 1 List<String[]> errors = 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")) .limit(40) .collect( Collectors.toList()); mutability must read the whole file in memory
  57. 57. FP version 2 List<String[]> errors = zip( Stream.concat(Stream.of(null), Files.lines(Paths.get(fileName))), Stream.concat(Files.lines(Paths.get(fileName)), Stream.of(null)), (s1, s2) -> new String[] { s1, s2 }) .filter(ss -> ss[1] != null && ss[1].startsWith("ERROR")) .limit(40) .collect( Collectors.toList() ); static <A,B,C> Stream<C> zip( Stream<? extends A> as, Stream<? extends B> bs, BiFunction<? super A, ? super B, ? extends C> zipper) { Iterator<? extends B> itr = bs.iterator(); return as.filter( x -> itr.hasNext() ).map( x -> zipper.apply( x, itr.next() ) ); }
  58. 58. OOP – FP (false?) dichotomies Polymorphism – Functional decomposition Mutable – Immutable Exceptions – Optional / Validation Imperative – Declarative
  59. 59. OOP – FP (false?) dichotomies Polymorphism – Functional decomposition Mutable – Immutable Exceptions – Optional / Validation Imperative – Declarative Stateful – Stateless Threads – Futures Statements – Expressions Iteration – Recursion
  60. 60. Threads vs (Completable)Future public double loadConversionRate(String currency1, String currency2) { return Double.NaN; // TODO – some long computation } public double loadUSDPrice(String productCode) { return Double.NaN; // TODO – some long computation } public double getPrice(String productCode, String currency) { return loadUSDPrice( productCode ) * loadConversionRate( "USD", currency ); }
  61. 61. Threads vs (Completable)Future public double loadConversionRate(String currency1, String currency2) { return Double.NaN; // TODO – some long computation } public double loadUSDPrice(String productCode) { return Double.NaN; // TODO – some long computation } public double getPrice(String productCode, String currency) { return loadUSDPrice( productCode ) * loadConversionRate( "USD", currency ); } private double result = 1.0; public synchronized void processResult(double value) { result *= value; } public double getPriceUsingThreads(String productCode, String currency) throws InterruptedException { Thread t1 = new Thread( new Runnable() { @Override public void run() { processResult( loadConversionRate( "USD", currency ) ); } } ); Thread t2 = new Thread( new Runnable() { @Override public void run() { processResult( loadUSDPrice( productCode ) ); } } ); t1.start(); t2.start(); t1.join(); t2.join(); return result; }
  62. 62. Threads vs (Completable)Future public double loadConversionRate(String currency1, String currency2) { return Double.NaN; // TODO – some long computation } public double loadUSDPrice(String productCode) { return Double.NaN; // TODO – some long computation } public double getPrice(String productCode, String currency) { return loadUSDPrice( productCode ) * loadConversionRate( "USD", currency ); } private double result = 1.0; public synchronized void processResult(double value) { result *= value; } public double getPriceUsingThreads(String productCode, String currency) throws InterruptedException { Thread t1 = new Thread( new Runnable() { @Override public void run() { processResult( loadConversionRate( "USD", currency ) ); } } ); Thread t2 = new Thread( new Runnable() { @Override public void run() { processResult( loadUSDPrice( productCode ) ); } } ); t1.start(); t2.start(); t1.join(); t2.join(); return result; }
  63. 63. Threads vs (Completable)Future The problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle. - Joe Armstrong public double loadConversionRate(String currency1, String currency2) { return Double.NaN; // TODO – some long computation } public double loadUSDPrice(String productCode) { return Double.NaN; // TODO – some long computation } public double getPrice(String productCode, String currency) { return loadUSDPrice( productCode ) * loadConversionRate( "USD", currency ); } private double result = 1.0; public synchronized void processResult(double value) { result *= value; } public double getPriceUsingThreads(String productCode, String currency) throws InterruptedException { Thread t1 = new Thread( new Runnable() { @Override public void run() { processResult( loadConversionRate( "USD", currency ) ); } } ); Thread t2 = new Thread( new Runnable() { @Override public void run() { processResult( loadUSDPrice( productCode ) ); } } ); t1.start(); t2.start(); t1.join(); t2.join(); return result; }
  64. 64. Threads vs (Completable)Future public double loadConversionRate(String currency1, String currency2) { return Double.NaN; // TODO – some long computation } public double loadUSDPrice(String productCode) { return Double.NaN; // TODO – some long computation } public double getPrice(String productCode, String currency) { return loadUSDPrice( productCode ) * loadConversionRate( "USD", currency ); }
  65. 65. Threads vs (Completable)Future public double loadConversionRate(String currency1, String currency2) { return Double.NaN; // TODO – some long computation } public double loadUSDPrice(String productCode) { return Double.NaN; // TODO – some long computation } public double getPrice(String productCode, String currency) { return loadUSDPrice( productCode ) * loadConversionRate( "USD", currency ); } public double getPriceUsingFutures(String productCode, String currency) throws ExecutionException, InterruptedException { return CompletableFuture.supplyAsync( () -> loadUSDPrice( productCode ) ) .thenCombine( CompletableFuture.supplyAsync( () -> loadConversionRate( "USD", currency ) ), (price, rate) -> price * rate ).get(); }
  66. 66. Threads vs (Completable)Future public double loadConversionRate(String currency1, String currency2) { return Double.NaN; // TODO – some long computation } public double loadUSDPrice(String productCode) { return Double.NaN; // TODO – some long computation } public double getPrice(String productCode, String currency) { return loadUSDPrice( productCode ) * loadConversionRate( "USD", currency ); } public double getPriceUsingFutures(String productCode, String currency) throws ExecutionException, InterruptedException { return CompletableFuture.supplyAsync( () -> loadUSDPrice( productCode ) ) .thenCombine( CompletableFuture.supplyAsync( () -> loadConversionRate( "USD", currency ) ), (price, rate) -> price * rate ).get(); }
  67. 67. Expressions vs Statements Object result = condition ? doSomething() : doSomethingElse(); Object result; if (condition) { result = doSomething(); } else { result = doSomethingElse(); } Favor expressions when possible, but in most cases this is not a choice and it is determined by the language’s syntax
  68. 68. Iteration vs Recursion int sumAll(int n) { int result = 0; for (int i = 0; i <= n; i++) { result += i; } return result; } int sumAll(int n) { return IntStream.rangeClosed(0, n).sum(); }
  69. 69. Iteration vs Recursion int sumAll(int n) { int result = 0; for (int i = 0; i <= n; i++) { result += i; } return result; } int sumAll(int n) { return IntStream.rangeClosed(0, n).sum(); } int sumAll(int n) { return n == 0 ? 0 : n + sumAll(n - 1); }
  70. 70. FP in the small, OOP in the large ... FP for calculation parts OOP for stateful and architectural parts
  71. 71. FP in the small, OOP in the large ... FP for calculation parts OOP for stateful and architectural parts FP to orchestrate and combine small components (objects) in a functional way ... or viceversa?
  72. 72. 85% functional language purity My real position is this: 100% pure functional programing doesn’t work. Even 98% pure functional programming doesn’t work. But if the slider between functional purity and 1980s BASIC-style imperative messiness is kicked down a few notches — say to 85% — then it really does work. You get all the advantages of functional programming, but without the extreme mental effort and unmaintainability that increases as you get closer and closer to perfectly pure. - James Hague
  73. 73. 85% functional language purity My real position is this: 100% pure functional programing doesn’t work. Even 98% pure functional programming doesn’t work. But if the slider between functional purity and 1980s BASIC-style imperative messiness is kicked down a few notches — say to 85% — then it really does work. You get all the advantages of functional programming, but without the extreme mental effort and unmaintainability that increases as you get closer and closer to perfectly pure. - James Hague Perfect is the enemy of good ­ Voltaire
  74. 74. To recap OOP + FP does not have to look like this
  75. 75. ● OOP approach models the data (noun-based) while FP processes them (action/verb-based) ● Good software is written in both styles, because it has more than one need to satisfy ● Programming languages are becoming evolved enough so that any attempt of categorization is fuzzy and unpractical ● Clearly delineate which part of your code are purely functional (e.g. because they need to run in parallel) and which are not (for efficiency or readability) ● Poly-paradigm programming is more generic, powerful and effective than polyglot programming ● From object-oriented to functional programming to functions-first programming ● If you come from an OOP, study FP. If you come from an FP, study OOP. Key Takeaways BE PRAGMATIC
  76. 76. Mario Fusco Red Hat – Principal Software Engineer mario.fusco@gmail.com twitter: @mariofusco Q A Thanks ... Questions?

×