λ λ CS 251 Fall 2019 CS 251 Fall 2019 Two world views Principles of Programming Languages Principles of Programming Languages Ben Wood Ben Wood FP FP: fu functions s perform so some operation FP vs. OOP OOP OOP: cl classes/prototypes give beha havi vior to some kind of data Problem Decomposition Which is better? Depends on software evolution, taste. Each can (awkwardly) emulate the other. https://cs.wellesley.edu/~cs251/f19/ 1 FP vs. OO Problem Decomposition 2 FP vs. OO Problem Decomposition Common pattern: expressions FP: behavior by operation Function per operation with branch per variant Operations over type of data Variants of a type of data eval toString usesX … eval toString usesX … VarX Sine VarX Times … Sine Datatype with constructor per variant Times … Pattern-matching selects variant. Wildcard can merge rows in a function. FP vs. OO Problem Decomposition 3 FP vs. OO Problem Decomposition 4
OOP: behavior by variant FP: extensibility Abstract base class or interface eval toString usesX depth with method per operation VarX Sine eval toString usesX … Times VarX Sqrt Sine Times Add variant: Add operation: … Subclass per variant add constructor, add function, overrides each operation method change all functions over datatype no other changes to implement variant's behavior Static type-checker gives "to-do list" via inexhaustive pattern-match warnings Dynamic dispatch selects variant. Concrete method in base class can merge rows where not overridden. FP vs. OO Problem Decomposition 5 FP vs. OO Problem Decomposition 6 OOP: extensibility Extensibility Making software extensible is valuable and hard. Ma eval toString usesX depth • If new operations likely, use FP VarX • If new variants likely, use OOP Sine • If both, use somewhat odd "design patterns" Times • Reality: The future is hard to predict! Sqrt Ex Extensibi bility is a doubl ble-ed edged ed sword. Add variant: Add operation: • No Non-in invasiv ive re reuse: original code can be reused without changing it. add subclass add method ficult local reasoning/changes: reasoning about/changing original • Di Diffi / class implementing interface, to abstract base class / interface code requires reasoning about/changing remote extensions. no other changes and all subclasses Re Restricting extensibility is valuable. Static type-checker gives "to-do list" • ML abstract types via errors about • Java final non-overridden abstract method /non-implemented interface method FP vs. OO Problem Decomposition 7 FP vs. OO Problem Decomposition 8
Binary Operations ML approach: pattern-matching What about operations that take two arguments of possibly Natural: pattern-match both simultaneously different variants? fun add_values (v1,v2) = – Include value variants Int, Rational, ... case (v1,v2) of – (Re)define Add to work on any pair of Int , Rational, ... (Int i, Int j) => Int (i+j) The addition operation alone is now a different 2D grid: | (Int i, Rational(n,d)) => Rational (i*d+n,d) | (Rational _, Int _) => add_values (v2,v1) | ... Add Int Rational ... fun eval e = Int case e of Rational ... ... | Add(e1,e2) => add_values (eval e1, eval e2) FP vs. OO Problem Decomposition 9 FP vs. OO Problem Decomposition 10 OOP approach: dynamic dispatch Explicit Double Dispatch OOP: Make variant choices using dynamic dispatch. abstract class Value extends Expr { abstract class Value extends Expr { Value addValues(Value v); ... Value addInt(MyInt v); Value addValues(Value v); Value addRational(MyRational v); } Dynamic dispatch chooses } addValues based on class Add extends Expr { result of e1.eval() class MyInt extends Value { ... ... Value eval() { Value addValues(Value) { return v.addInt(this); } e1.eval().addValues(e2.eval()) } } Dynamic dispatch Now, dispatch on second value, on first value "telling it" what kind of value this is. class MyInt extends Value { got us here. … // add this to v Value addInt(MyInt v) { ... } Value addValues(Value v) { Value addRational(MyRational v) { ... } … // what goes here? Depends on what } } Repeat for all Value subclasses… kind of value v is. } FP vs. OO Problem Decomposition 11 FP vs. OO Problem Decomposition 12
Reflecting Multiple dispatch / multimethods Dynamic dispatch on all arguments . Double dispatch manually emulates basic pattern-matching. – An analogous FP pattern emulates dynamic dispatch. – One version of method per combination of argument types. Does it change the way in which OOP handles evolution? – NOT static overloading. – Remarkably close to functions that pattern-match arguments. • Add an operation over pairs of Values: • But the individual branches may be split up. – OOP double dispatch: how many added / changed classes? • But subtyping can lead to ambiguous dispatch. – FP pattern matching: how many added / changed functions? If dynamic dispatch is essence of OOP, multiple dispatch is • Add a kind of Value: its natural conclusion. – OOP double dispatch: how many added / changed classes? – FP pattern matching: how many added / changed functions? Old research idea picked up in some recent languages (e.g., Clojure, Julia) What if we could dispatch based on all arguments at once? FP vs. OO Problem Decomposition 13 FP vs. OO Problem Decomposition 14 Closures vs. Objects Closure: – Captures code of function, by function definition. – Captures all bindings the code may use, by lexical scope of definition. Object: – Captures code for all methods that could be called on it, by class hierarchy. – Captures bindings that may be used by that code, by instance variables declared in class hierarchy. Each can (awkwardly) emulate the other. FP vs. OO Problem Decomposition 16
Recommend
More recommend