430 likes | 529 Views
Multimethods. Itay Maman Demo. Multimethods (1/2). A dynamic-dispatch mechanism The executed method is selected by the dynamic type of one (or more) argument(s) virtual methods in Java/C++ are all “ SingleMethods ” Related terms: Multi-dispatch Binary-dispatch. Multimethods (2/2).
E N D
Multimethods Itay Maman Demo
Multimethods (1/2) • A dynamic-dispatch mechanism • The executed method is selected by the dynamic type of one (or more) argument(s) • virtual methods in Java/C++ are all “SingleMethods” • Related terms: • Multi-dispatch • Binary-dispatch
Multimethods (2/2) • Single-method dispatching as a mapping: • (t, mid) -> mp • t - The dynamic type of the receiver • mid – Method id • mp – Pointer to selected method • Multimethod dispatching: • (t1, t2, … tn, mid) -> mp
The many faces of Multimethods • Design decision (responsibilities of classes) • Aspects • Compilation techniques + runtime system • Programming language • Expected to be highly popular
Motivation • The bouncing ball simulation: • A 2d plain with walls • Two kinds of walls: Blue, Yellow • Two kinds of balls: Red, Green • Collision rules • Red ball hits a Bluewall – changes direction • Red ball hits a Yellowwall – Stops moving • Green ball hits a Bluewall – changes direction + looses speed • Green ball hits a Yellowwall – wall disappears
Motivation • The OO model: • Vector of Balls, Walls • Abstract class Wall • Maintains location, color • Has an active/not active flag • Two direct sub-classes: WallX, WallY • StickyWallX is a sub-class of WallX • Class Ball • Maintains location, color • Maintains vx, vy (velocity in each axis) • One sub-class: PowerBall
Motivation – Wall // file: Wall.java public abstract class Wall { private Rectangle rect_; private Color color_; public boolean active_ = true; public Wall(int x, int y, int w, int h, Color color) { color_ = color; rect_ = new Rectangle(x, y, w, h); } public void draw(Graphics g) { if(!active_) return; g.setColor(color_); g.fillRect(rect_.x, rect_.y, rect_.width, rect_.height); } public boolean contains(double x, double y) { return active_ && rect_.contains((int) x, (int) y); } }
Motivation – Ball // file: Ball.java public class Ball { private static double chooseVelocity() { .. } public double vx_ = chooseVelocity(); public double vy_ = chooseVelocity(); public double x_ = 50; public double y_ = 50; public Color color_ = Color.RED; public void draw(Graphics g) { g.setColor(color_); g.fillArc((int) x_ - 5, (int) y_ - 5, 10, 10, 0, 360); } public void move() { x_ += vx_; y_ += vy_; } }
Motivation – The catch (1/2) // Precondition: // w.contains(b.x_, b.y_) == true // // Postcondition: // The state of b, w has changed according // to the simulation’s collision rules // void collision(Wall w, Ball b) { // ? ? ? ? // ? ? ? ? } • This function/method should implement the “collision rules” • A plain virtual method will not do
Motivation – The catch (2/2) • The collision rules depend on the dynamic type of TWO objects: • Formal argument w (static type: Wall) • Formal argument b (static type: Ball) • At least two relevant classes: Wall, Ball • No obvious class to place the code at • Solution 1: Use instanceof • Solution 2: Use the “visitor” hack
Motivation – solution 1 • Pros: • Code is located in one place • Cons: • A Complex chain of if-else • No alert if a case was not handled • No alert if we change the hierarchy • Order is significant void collision(Wall w, Ball ball) { if(w instanceof WallX) { if(ball instanceof Ball) b.vy_ *= -1; else if(ball instanceof PowerBall) b.vy_ *= -0.9; } if(w instanceof WallY) { if(ball instanceof Ball) b.vx_ *= -1; else if(ball instanceof PowerBall) b.vx_ *= -0.9; } if(w instanceof StickyWallX) { if(ball instance of Ball) b.vy_ = b.vx_ = 0; else if(ball instnaceof PowerBall) w.active_ = false; } }
Motivation – solution 2 // file: Wall.java class Wall { .. abstract void hit(Ball b); abstract void hit(PowerBall pb); } // file: Wall.java class Wall { .. abstract void hit(Ball b); abstract void hit(PowerBall pb); } // file: Ball.java void collide(Wall w) { w.hit(this); } // file: PowerBall.java void collide(Wall w) { w.hit(this); } // file: WallX.java void hit(Ball b) { b.vy_ *= -1; } void hit(PowerBall b) { b.vy_ *= -0.9; } // file: StickyWallX.java void hit(Ball b) { b.vx_ = b.vy_ = 0; } void hit(PowerBall b) { active_ = false; } // file: BouncingBallDemo.java void collision(Wall w, Ball b) { b.collide(w); }
Motivation – solution 2 (cont’d) • Pros: • Order is less significant • Some compiler alerts • Cons: • Code is highly scattered • Reduced cohesion, increased coupling • Many methods must be implemented
Motivation – A Nicer solution // file: BouncingBallDemo.nice void collision(Wall w, Ball b); collision(WallX w, Ball b) { b.vy_ *= -1; } collision(WallY w, Ball b) { b.vx_ *= -1; } collision(StickyWallX w, Ball b) { b.vx_ = b.vy_ = 0; } collision(WallX w, PowerBall b) { b.vy_ *= -0.9; } collision(WallY w, PowerBall b) { b.vx_ *= -0.9; } collision(StickyWallX w, PowerBall b) { w.active_ = false; }
Questions • Where (in the source code) can a multimethod be defined? • Just like a virtual method? • File scope? • Within a dedicated class? • Does a multimethod have a “this” reference? • Policy for selecting the “best” match • Implementation • Much more complicated than a virtual method dispatch
MultiJava (1/2) • Clifton, Leavens, Chambers, Millstein • First presented at OOPSLA 2000 • Available at: http://multijava.sourceforge.net • Currently working on various enhancements
MultiJava (2/2) • An extension to Java • A legal Java program is also a MultiJava program • Compilation/execution • The MultiJava compiler replaces javac • Produces standard .class files • No linking phase • Any standard JVM can run the program • Excluding J2SE 5.0
MultiJava – example 1 public class Shape { public String both(Shape s) { return "s-s"; } public String both(Shape@Rect r) { return "s-r"; } } public class Rect extends Shape { public String both(Shape s) { return "r-s"; } public String both(Shape@Rect r) { return "r-r"; } } public static void main(String args[]) { Shape s = new Shape(); Shape r = new Rect(); System.out.println(s.both(s)); System.out.println(s.both(r)); System.out.println(r.both(s)); System.out.println(r.both(r)); } “s-s” “s-r” “r-s” “r-r”
MultiJava – example 2 public static class Shape { } // file: Shape.java public static class Rect extends Shape { } // file: Rect.java // file: something.java public static String both(Shape s1, Shape s2) { return "s-s"; } public static String both(Shape s, Shape@Rect r) { return "s-r"; } public static String both(Shape@Rect r, Shape s) { return "r-s"; } public static String both(Shape@Rect r1, Shape@Rect r2) { return "r-r"; } public static void main(String args[]) { Shape s = new Shape(); Shape r = new Rect(); System.out.println(both(s,s)); System.out.println(both(s,r)); System.out.println(both(r,s)); System.out.println(both(r,r)); } “s-s” “s-r” “r-s” “r-r”
Nice (1/2) • Bonniot, Keller, Barber • Not an academic work • Mentioned in several articles (Scala) • Available at: http://nice.sourceforge.net • A java-like programming language • Not an extension of Java • Paradigms: OO, Functional
Nice (2/2) • Besides multimethods, offers additional features: • Functions are 1st class values, anonymous functions • Tuples • Generics • Named parameters • Limited inference • Compilation/Execution • The compiler (nicec) produces executables (jar files) • Any standard JVM can run the program • Has a linking phase • Compilation unit: package
Nice – example 1 public class Shape { } public class Rect extends Shape { } String both(Shape a1, Shape a2) { return "s-s"; } both(Shape a1, Rect a2) { return "s-r"; } both(Rect a1, Shape a2) { return "r-s"; } both(Rect a1, Rect a2) { return "r-r"; } public void main(String[] args) { Shape s = new Shape(); Shape r = new Rect(); System.out.println(both(r,s)); } “r-s”
Nice – example 2 public abstract class Shape { } public class Rect extends Shape { } String both(Shape a1, Shape a2); both(Shape a1, Rect a2) { return "s-r"; } public void main(String[] args) { Shape r1 = new Rect(); Shape r2 = new Rect(); System.out.println(both(r1,r2)); } • No need to implement both(Shape, Shape) • Q: How is it possible? • A: Linking • Dispatching is checked at link-time “s-r”
Nice – example 3 • Let’s add a circle class to the last program.. • This code will not compile !! • Pattern matching is incomplete: • E.g.: both(Circle,Circle) is not handled public abstract class Shape { } public class Rect extends Shape { } public class Circle extends Shape { } String both(Shape a1, Shape a2); both(Shape a1, Rect a2) { return "s-r"; }
Nice – Methods vs. Functions public class Rect { int w = 0; int h = 0; public int area() { return w * h; } } public void set(Rect r, int w, int h) { r.h = h; r.w = w; } public void main(String[] args) { Rect r = new Rect(); r.set(10,10); System.out.println(r.area()); set(r,10,20); System.out.println(area(r)); } • Two equivalent forms for method/function invocation • x.f(y,z) • f(x,y,z) • Every file-scope function is also a method • But, there is no “this” in file-scope functions
Consequences • (We will use Nice for most code samples)
Open classes • Let’s define a function with an Object argument • => Full support for “Open classes” • Any class can be expanded public void show(Object o) { System.out.println(‘<‘ + o.getClass() + ":" + o + ‘>‘); } public void main(String[] args) { String s = "abc"; s.show(); } “<class java.lang.String:abc>”
Playing with two arguments • Printer.show() displays the hash-code of an Object • StringPrinter.show()displays the length of a String public class Printer { void show(Object o) { // *1* System.out.println("Hashcode=" + o.hashCode()); } } public class StringPrinter extends Printer { void show(String s) { // *2* System.out.println("Len=" + s.length()); } } public void main(String[] args) { Printer p = new StringPrinter(); p.show(new java.util.Date()); // Invokes *1* p.show("abc"); // Nice: Invokes *2*. Java: Invokes *1* }
“Best match” policy String both(Shape a1, Shape a2) = "s-s"; // *1* both(Rect a1, Shape a2) = "r-s"; // *2* both(Shape a1, Circle a2) = "s-c"; // *3* both(new Rect(), new Circle()); // Invokes ??? • Which implementation is invoked? • Asymmetric multimethods: *2* • Order of parameters is significant • Not intuitive • Symmetric multimethods: None (compiler error) • Order is insignificant • May yield ambiguity (see the above sample) • Used by MultiJava, Nice
Implementation • The magic behind multimethods..
Reminder: Java’s single-dispatch • Each class has a dispatch table with N entries • Created by the compiler • N– Number of “messages” the class can receive • Each entry specifies the “address” of the relevant method • When the .class file is generated, N is known • In multimethods: • N is not known
MultiJava – Implementation (1/2) // file: something.java public static String both(Shape s1, Shape s2) { return "s-s"; } public static String both(Shape s, Shape@Rect r) { return "s-r"; } public static String both(Shape@Rect r, Shape s) { return "r-s"; } public static String both(Shape@Rect r1, Shape@Rect r2) { return "r-r"; } public class both$20 { public static void apply(Shape s1, Shape s2) { if(s1 instanceof Rect && s2 instanceof Rect) return "r-r"; if(s1 instanceof Rect && s2 instanceof Shape) return "r-s"; if(s1 instanceof Shape && s2 instanceof Rect) return "s-r"; if(s1 instanceof Shape && s2 instanceof Shape) return "s-s"; } }
MultiJava – Implementation (2/2) • both$20 is a “Generic Function class” • Provides the multi-dispatching functionality • Generated automatically by the compiler • In the previous example, both$20 is generated for both(Shape, Shape) • Is used for all versions of both() which are a specialization of both(Shape, Shape) • Call site translation by the compiler: • Translates multimethod calls to invocations of apply() on the proper generic function class • both(..) is converted to: both$20.apply(..) • Q: Drawbacks?
MultiJava – the flaw • Splitting the definition • Let’s define both() in two compilation units: • Something.java, SomethingElse.java public static class Shape { } // file: Shape.java public static class Rect extends Shape { } // file: Rect.java // file: Something.java public static String both(Shape s1, Shape s2) { return "s-s"; } public static String both(Shape s, Shape@Rect r) { return "s-r"; } // file: SomethingElse.java public static String both(Shape@Rect r, Shape s) { return "r-s"; } public static String both(Shape@Rect r1, Shape@Rect r2) { return "r-r"; } • This code will NOT compile • The compiler will not regenerate the generic function class • Separate compilation principle
Nice – the flaw • Implementation of multimethods in Nice: • Multimethod dispatching code is created during linking • All declared types are known at link-time • Thus, Pattern-matching can make sure all cases are handled • Q: Where is the flaw? public abstract class Shape { } public class Rect extends Shape { } public toString(Shape s) { return "Shape/" + s.getClass(); } String both(Shape l, Shape r); both(Rect l, Rect r) { return "r-r"; } public void main(String[] args) { Object o = Class.forName("s14.Circle").newInstance(); System.out.println("Object=" + o.toString()); if(o instanceof Shape) both(o, o); } Run s14
Overview of features • Basic features/properties • Methods functions • Open classes • Decoupling of state and behavior • Covariance • Symmetry • Additional features • Value dispatch • Exact matching • Static dispatching of multimethods (“super”) • Not all features are supported by every implementation
Summary • MultiJava • Simpler compilation model • Restrictions on the definitions of multimethods • The less-specialized method must be implemented • Nice • Complex compilation • Uses pattern matching => Compile-time safety • Dynamic loading of classes yields dispatch errors at run-time
Multimethods in ML (1/3) • Definition of Multimethods: Dispatching mechanism where the executed method is selected by the dynamic type of one (or more) argument(s) • Q:Is it possible to create a similar mechanism in ML? • First approach: “A language without virtual methods cannot offer multimethods” • Second approach: Let’s change the definition (!) • Multimethods: Dispatching mechanism where the executed function is selected by the concrete type of one (or more) polymorphic values
Multimethods in ML (2/3) • ML support polymorphism: A single variable can hold values of different types • Datatype (AKA: Variant record) -datatype Shape = Circle of real | Rect of real*real; -fun area(Circle(r)) = r*r*3.14 | area(Rect(w,h)) = w*h; val area = fn : Shape -> real • The area() function is analogous to an area() method defined by class Shape
Multimethods in ML (3/3) • Now, let’s define our both() function.. • both() accepts a tuple of Shape*Shape • Uses Pattern-matching to select correct implementation -datatype Shape = Circle of real | Rect of real*real; -fun both(Circle(_), Circle(_)) = "c-c" | both(Circle(_), Rect(_,_)) = "c-r" | both(Rect(_,_), Circle(_)) = "r-c" | both(Rect(_,_), Rect(_,_)) = "r-r"; val both = fn : Shape * Shape -> string
ML style multimethods in C++ struct Circle { }; struct Rect { }; typedef Variant<Circle,Rect> Shape; struct Both { void action(Circle a1, Circle a2) const { cout << "c-c"; } void action(Circle a1, Rect a2) const { cout << "c-r"; } void action(Rect a1, Circle a2) const { cout << "r-c"; } void action(Rect a1, Rect a2) const { cout << "r-r"; } }; int main() { Shape c = Circle(); Shape r = Rect(); dispatch(c,r,Both()); reutrn 0; }
References • Curtis, Leavens, Chambers and Todd, MultiJava: Modular open classes and symmetric Multiple Dispatch for Java, OOPSLA 2000 • Baker and Hsie, Maya: Multiple-Dispatch Syntax Extension in Java, PLDI 2002 • Dutchyn, Szafron, Bromling and Holst, Multi-Dispatch in the Java virtual machine: design and implementation, COOTS 2001 • Bonniot, Keller and Barber, The Nice user’s manual