220 likes | 231 Views
Explore class families and the “OnOffGraph” example in C++, comparing traditional polymorphism with family polymorphism. Learn about mutable instances, safe type handling, and the challenges of implementation. Discover how Family Polymorphism increases flexibility and safety in managing polymorphic classes together.
E N D
Family Polymorphism by Erik Ernst
Outline • Class families • The “OnOffGraph” example • Traditional polymorphism • C++ approach • Family polymorphism • Implementation difficulties • Mutable instances • Where to find the pointer • When is it type safe • Comparison with VirtualTypes
Class Families • Express multi-object relations • Flexibility • Safety • Classes are declared and managed in a “family” and they polymorphic together.
Node Edge Families OnOffNode OnOffEdge The OnOffGraph Example • There are edges and nodes in a graph. • I.e. Edge and Node is in a family Graph. • OnOffEdge and OnOffNode are in the family OnOffGraph.
Traditional Polymorphism(The Naïve approach) class Node{ boolean touches(Edge e){… } }class Edge{ Node n1, n2; } class OnOffNode extends Node{ boolean touches(Edge e){ return ((OnOffEdge)e).enabled ? super.touches(e) : false; }}class OnOffEdge extends Edge{ boolean enabled; } void build(Node n, Edge e){ e.n1 = e.n2 = n; if(n.touches(e)) println(“OK”);}
Traditional Polymorphism (cont.) • build(new Node(), new Edge()) • build(new OnOffNode(), new OnOffEdge()) • build(new OnOffNode(), new Edge())// ClassCastException • build(new Node(), new OnOffNode())// “works” • Reuse: Yes; Safety: No
template<class N, class E> struct EdgeF{ N* n1, n2; };template<class N, class E> struct NodeF{ virtual bool touches(E* e); }; struct Edge: public EdgeF<Node, Edge>{};struct Node: public NodeF<Node, Edge>{}; template<class ON, class OE> struct OnOffEdgeF: public EdgeF<ON, OE>{ bool enabled; };template<class ON, class OE> strut OnOffNodeF: public NodeF<ON, OE>{ bool touches(OE* e){ return e->enabled ? NodeF<ON, OE>::touches(e) : false; }}; struct OnOffEdge: public OnOffEdgeF<OnOffNode, OnOffEdge>{};struct OnOffNode: public OnOffNodeF<OnOffNode, OnOffEdge>{}; void build1(Node* n, Edge* e){ e->n1 = e->n2 = n; if(n->touches(e)) cout << “OK\n”;} void build2(OnOffNode* n, OnOffEdge* e){ e->n1 = e->n2 = n; if(n->touches(e)) cout << “OK\n”;} C++ approach
C++ approach (cont.) • build1(new Node, new Edge) • build2(new OnOffNode, new OnOffEdge) • build1(new OnOffNode, new Edge)// type error • build2(new Node, new OnOffEdge)// type error • Safety: Yes; Reuse: No
C++ approach problems • Reusability: using template (but not really “reuse” the function) template<class N, class E> void build(N* n, E* e){ … } • But problems arise
template<class N, class E>struct GraphF{ typedef N Node; typedef E Edge;}; typedef GraphF<Node, Edge> Graph;typedef GraphF<OnOffNode, OnOffEdge> OnOffGraph; template<class G>void build(const G&, typename G::Node* n, typename G::Edge* e){ … } build(Graph(), new Node, new Edge); C++ approach problems (cont.) • Every involved function have to be template functions and the exact type (Node and Edge, or OnOffNode and OnOffEdge) should be known, not any thing like “a subfamily of Graph.”
C++ approach problems (cont.) • A template function may not be a virtual member function • Limits usage on known Node-Edge system • Generic in Java 1.5 doesn’t have such problem
C++ approach problems (cont.) • No coupled containers to store nodes and edges belonging together template<class G>struct NodesAndEdges{ vector<typename G::Node> nodes; vector<typename G::Edge> edges;}
C++ approach problems (cont.) • All usages of nodes and edges are performed in a context where the exact classes are known • May increase the dependency of implementation details
Family polymorphism (# Graph: (# Node:< (# touches:< (# e: ^Edge; b: @boolean enter e[] do (this(Node)=e.n1) or (this(Node)=e.n2) -> b exit b #) exit this(Node)[] #); Edge:< (# n1, n2: ^Node exit this(Edge)[] #) #); OnOffGraph: Graph (# Node::<(# touches::<!(# do (if e.enabled then INNER if) #)#) Edge::<(# enabled: @boolean #) #); build: (# g:< @Graph; n: ^g.Node; e: ^g.Edge enter(n[], e[]) do n -> e.n1[] -> e.n2[]; (if (e->n.touches) then ‘OK’ ->putline if) #);
Family polymorphism (cont.) • g1: @Graph; g2: @OnOffGraph • (g1.Node, g1.Edge) -> build(# g::@g1 #) • (g2.Node, g2.Edge) -> build(# g::@g2 #) • (g2.Node, g1.Edge) -> build(# g::@g1 #)(* type error *) • (g2.Node, g1.Edge) -> build(# g::@g2 #)(* type error *) • Reuse: Yes; Safety: Yes • Class families are associated with instances (g1, g2), not classes (Graph, OnOffGraph)
Family polymorphism (cont.) • Coupled containers: NodesAndEdges:(# g:< @Graph; nodes: @list(# element::g.Node #); edges: @list(# element::g.Edge #)#) myGraph: @LabelledGraph;myNodesAndEdges: @NodesAndEdges(# g::@myGraph #) listBuild:(# ne:< @NodesAndEdges; n: ne.g.Node; e: ^ne.g.Edge do ne.nodes.head -> n[]; ne.edges.head -> e[]; (n, e) -> build(# g::@ne.g #)#) m1: (# x: ^NodesAndEdges enter x[] do listBuild(# ne::@x #) #)m2: (# x: ^NodesAndEdges enter x[] do x[] -> m1 #)
Implementation DifficultiesMutable Instances • Mutable instance “ne” in listBuild will change the type at runtime and destroy the correctness. • Only the types from the immutable references are considered the same in multiple appearances
Implementation DifficultiesWhere Is The Pointer • Circle and Rectangle and subclasses of GraphicalObjects with method draw • Traverse a List whose elements are typed as GraphicalObjects and invoke draw of them. • There may not exist any pointers typed by the actual class to each object in the list in the program state
Implementation DifficultiesWhere Is The Pointer (cont.) • To type the families correctly, the single-object way does not work • Possible approach: use wrapper classes like NodesAndEdges which stores a Graph instance then have a list of these wrappers • Rediscover: instanceof • Restrict usage on known families
Implementation DifficultiesWhen is it type safe • Virtual patterns (virtual types) are attributes of objects, not classes • Virtual patterns have a kind of existential type:x is an object of type T (typed as T)V is a virtual attribute in T x.V has the type T.V i.e. ∃T’V ≤ TV . T’V • y is another instance typed as Ty.V can be ∃T’’V ≤ TV . T’’VT’’V may not equal to T’V
Implementation DifficultiesWhen is it type safe (cont.) • Virtual types can be change into ordinary types by final binding[Tor98] • Immutable reference is equally valid • this is an immutable reference
Comparison with VirtualTypes • [Bru98] • Based on exact references • Virtual types are attributes of classes • [Tor98] • Type system similar • The language in it is a pseudo one • Not emphasized that virtual types are attributes of objects