Family Polymorphism
590P Seminar
February 16, 2005
Traditional polymorphism Let x refer to an object of class C {C1,C2,
…,Cn}, where each Ci provides (or inherits) a method m()
Call site x.m() provides safety: Type checking can ensure that x has an m(). Late-binding ensures correct m() invoked.
Also provides reuse: x can refer to any Ci
What is family polymorphism? A family describes a set of related classes. Given a set of families providing a common
interface: {{A1,B1,C1},…,{An,Bn,Cn}} If m() is a method provided by all A’s and
taking an argument of class B, we want reuse: x.m(y) works for any Ai and Bi
Also want safety: Ai and Bi must be from the same family
Motivation Traditional OOP allows variability at the level
of a single class. Paper argues that modern software
engineering requires support for variability on a larger scale.
Argue that popularity of aspect-oriented programming demonstrates need for multi-class variability.
Examples??
Example: Graphs Two mutually recursive classes Edge
and Node. An Edge has two Nodes. A Node has a method touches(Edge e) that returns true if the edge e touches the node.
Extending Graph An OnOffGraph:
OnOffEdge has an extra field enabled OnOffNode.touches(e) only returns
true if e is enabled. Can extend in other ways as well:
Colored graphs Labelled graphs
Safety and Reusability Reuse:
Want to write methods that can work with either Edges and Nodes, or with OnOffEdges and OnOffNodes.
Safety: The methods should not allow mixing–e.g.,
attaching an OnOffEdge to a Node. Exisiting approaches can provide reuse or
safety, but not both.
Reuse via subtyping
class Node { boolean touches(Edge e) { return (this==e.n1) || (this==e.n2); }}
class Edge { Node n1, n2;}
Reuse via subtyping
class OnOffNode extends Node { boolean touches(Edge e) { return ((OnOffEdge)e).enabled? super.touches(e) : false; }}
class OnOffEdge extends Edge { boolean enabled; OnOffEdge() { this.enabled=false; }}
Reuse via subtypingpublic class Main { static void build(Node n,Edge e,boolean b){ e.n1=e.n2=n; if (b == n.touches(e)) System.out.println(“OK”); } public static void main(String[] args) { build(new Node(), new Edge(), true); build(new OnOffNode(), new OnOffEdge(), false); }}
Why is this unsafe?public class Main { static void build(Node n,Edge e,boolean b){ e.n1=e.n2=n; if (b == n.touches(e)) System.out.println(“OK”); } public static void main(String[] args) { build(new Node(), new Edge(), true); build(new OnOffNode(), new OnOffEdge(), false); build(new OnOffNode(), new Edge(), true); }}
Runtime Error!
public static void main(String[] args) { build(new Node(), new Edge(), true); build(new OnOffNode(), new OnOffEdge(), false); build(new OnOffNode(), new Edge(), true); }
class OnOffNode extends Node { boolean touches(Edge e) { return ((OnOffEdge)e).enabled? ...
Another unsafe call
This “works” (i.e., no errors, prints OK). But not the intended semantics.
Nodes should be used with Edges, not OnOffEdges.
Node::touches() ignores enabled field
build(new Node(), new OnOffEdge(), true);
Try again, with templates
template <class N,class E> struct NodeF;template <class N,class E> struct EdgeF { N *n1,*n2; };template <class N,class E> struct NodeF { virtual bool touches(E* e) { return (this==e->n1) || (this==e->n2); }
struct Edge;struct Node: public NodeF<Node,Edge> {};struct Edge: public EdgeF<Node,Edge> {};
On-off graphs
template <class ON,class OE>struct OnOffEdgeF: public EdgeF<ON,OE> { bool enabled; OnOffEdgeF(): enabled(false) {} };template <class ON,class OE>struct OnOffNodeF: public NodeF<ON,OE> { bool touches(OE* e) { return e->enabled ? NodeF<ON,OE>::touches(e) : false; }};
On-off graphs, cont…
struct OnOffEdge;struct OnOffNode : public OnOffNodeF<OnOffNode,OnOffEdge> {};struct OnOffEdge : public OnOffEdgeF<OnOffNode,OnOffEdge> {};
But no reuse …
void build1(Node* n, Edge* e, bool b) { e->n1=e->n2=n; if (b == n->touches(e)) cout << “OK\n”;}
void build2(OnOffNode *n,OnOffEdge *e,bool b){ e->n1=e->n2=n; if (b == n->touches(e)) cout << “OK\n”;}
OnOffNode not a subtype of Node
A templated build()? Could get reuse by templating the
arguments to build. Problems with this approach:
Types must be known statically at call site. When we pass a node to a template
function, every function it passes through in the call chain must be templated.
Can’t create data structures containing nodes and edges belonging together.
Reuse and Safety via Virtual Types Virtual types allow types to be attributes
of objects. Thus, we can make an object that acts
as a “repository of types”. These “repositories” can store our
families. We’ll show how this works with gbeta -
beta with support for families.
Graphs in gbetaGraph: (# 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)[] #) #);
On-off graphs in gbeta
OnOffGraph: Graph (# Node::< (# touches::<!(# do (if e.enabled then INNER if) #) #) Edge::< (# enabled: @boolean #)#);
build() in gbeta
build: (# g:<@Graph; n:^g.Node; e:^g.Edge; b:@boolean enter (n[],e[],b) do n->e.n1[]->e.n2[]; (if (e->n.touches)=b then ‘OK’->putline if)#);g1:@Graph; g2:@OnOffGraph
build() in gbeta
build: (# g:<@Graph; n:^g.Node; e:^g.Edge; b:@boolean enter (n[],e[],b) do n->e.n1[]->e.n2[]; (if (e->n.touches)=b then ‘OK’->putline if)#);g1:@Graph; g2:@OnOffGraph
(g1.Node, g1.Edge, true) -> build(#g::@g1#);
build() in gbeta
build: (# g:<@Graph; n:^g.Node; e:^g.Edge; b:@boolean enter (n[],e[],b) do n->e.n1[]->e.n2[]; (if (e->n.touches)=b then ‘OK’->putline if)#);g1:@Graph; g2:@OnOffGraph
(g2.Node, g2.Edge, false) -> build(#g::@g2#);
build() in gbeta
build: (# g:<@Graph; n:^g.Node; e:^g.Edge; b:@boolean enter (n[],e[],b) do n->e.n1[]->e.n2[]; (if (e->n.touches)=b then ‘OK’->putline if)#);g1:@Graph; g2:@OnOffGraph
(g2.Node, g1.Edge, false) -> build(#g::@g2#);
build() in gbeta
build: (# g:<@Graph; n:^g.Node; e:^g.Edge; b:@boolean enter (n[],e[],b) do n->e.n1[]->e.n2[]; (if (e->n.touches)=b then ‘OK’->putline if)#);g1:@Graph; g2:@OnOffGraph
(g2.Node, g1.Edge, false) -> build(#g::@g2#);
TYPE ERROR! (because g1 ≠ g2)
Family-polymorphic data structures
NodesAndEdges: (# g:< @Graph; nodes: @list(# element::g.Node #); edges: @list(# element::g.Edge #) #)
myGraph: @LabelledGraph;myNodesAndEdges: @NodesAndEdges(#g::@myGraph#)