270 likes | 457 Views
Attached types and their application to three open problems of object-oriented programming. Bertrend Meyer ETH Zurich and Eiffel Software. Itay Maman 236803 Seminar lecture, 8 November 2005. Motivation. Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
E N D
Attached types and their application to three open problems of object-oriented programming Bertrend Meyer ETH Zurich and Eiffel Software Itay Maman 236803 Seminar lecture, 8 November 2005
Motivation Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException at my.program.MainWindow$1.actionPerformed(MainWindow.java:33) at javax.swing.AbstractButton.fireActionPerformed(Unknown Source) at javax.swing.AbstractButton$Handler.actionPerformed(Unknown Source) at javax.swing.DefaultButtonModel.fireActionPerformed(Unknown Source) at javax.swing.DefaultButtonModel.setPressed(Unknown Source) at java.awt.Component.processMouseEvent(Unknown Source) Guarantee that in x.f(a1,a2,..), the receiver, x, will always denote an object • This talk: • Presents Meyer’s notion of non-null types • (Implemented in the Eiffel language) • We will use Java-like syntax
Thoughts on static typing • Let’s consider x.f() • Static typing: Ensures that x will point to an object that “understands” the message f() • But, x could be null • null does not understand f() • => null pointers are “holes” in the static typing system • Elimination of null pointers is a typing issue • (Not a data analysis issue)
null, what is it good for? • Special value • Indicating end-of-file, a missing value, etc. • Delayed initialization of fields • In a word-processor, there is no document object until the user opens a file • Two (or more) mutually dependent objects • Recursive data structures • Recursive data structures • Q: Can we find an alternative to null values?
Null-safety in Eiffel (A new mechanism, recently proposed) • Non-null type: A type T has no null value • This is the default • Null type: ?T is a super type of T which has the null value • Only non-null types allow invocation of methods • The same goes for field access • Automatic conversion: T -> ?T • Checked conversion: ?T -> T
Downcast+assign expression: • Check non null • Check dynamic type • Assign to a local variable A non-null program public class Printer { private PrintWriter out = System.out; public Printer(?PrintWriter pw) { if( (PrintWriter o = ) pw) { out = o; o.flush(); } } public void print(Object obj) { out.println(obj.toString()); } }
An equivalent program public class Printer { private PrintWriter out = System.out; public Printer(PrintWriter pw) { Object temp = pw; if(temp instanceof PrintWriter) { PrintWriter o = (PrintWriter) temp; out = o; o.flush(); } } public void print(Object obj) { out.println(obj.toString()); } }
CAP: Certified Attachment Patterns public class IntList { private ?IntList tail; private int n; public IntList(int n, ?IntList tail) { ... } public static void print(?IntList list) { while(list != null) { System.out.println(list.n); list = tail; } } } • The expression list.n is (statically) null-safe • Look at the loop condition! • This is a CAP of list
CAPs • Definition: CAP(x) • A segment of code, where x is ensured to be non-null by some null test • x – An expression whose type is nullable • CAPs eliminate many downcast+assign expression • => Simple, intuitive code • Currently there are several standard CAPs • A compiler implementation cannot add a new CAP • Note that CAP for fields are more restricted • Scope is limited to first instruction after the null test
Standard CAPs for locals // x is a nullable local variable: ?X x; while(x != null) { x.f(); x.g(); x = h(); } if(x != null) { x.f(); x.g(); x = h(); } if(x == null) { ... } else { x.f(); x.g(); x = h(); } x = new X(); x.f(); x.g();x = h(); b = (x != null) && (x.f() == x.g()); b = (x == null) || (x.f() == x.g()); assert x != null; x.f(); x.g(); x = h(); • If x is a field, only x.f() is legal
Soundness • Assignment • ?T -> T is illegal • null -> T is illegal • Initialization • Formal arguments • Are always assigned upon method invocation • Local variables • Compiler already ensures proper initializaion • Fields…
Soundness: Fields • The problem: Ensuring non-null initialization of fields • Historically, fields are initialized with null • Meyer’s approach: • Three distinct solutions…
Solution 1/3: Self initializing types • Field Y.x is lazily initialized on first access • Only if not previously initialized • Applicable if X has a default constructor class X { public X() { } public void f() { } } class Y { private X x; publicvoid h() { x.f(); // Lazy initialization: // Invoke default constructor of x } }
Solution 2/3: Self initializing fields • Field Circle.rect is lazily initialized on first access • Only if not previously initialized • Initialization block added to the field’s definition class Rectangle { Rectangle(int x0, int y0, int x1, int y1) { ... } } class Circle { privatefinalint cx, cy, r; private Rectangle rect { return new Rectangle(cx-r, cy-r, cx+r, cy+r); } public Circle(int cx, int cy, int r) { ... } public Rectangle getRect() { return rect; } }
Solution 3/3: Initialization by constructors • The last case: • Non-null fields w/o initialization block • (The first two solutions do not apply) • The solution: • Initialization by every constructor • Statically enforced by the compiler
Soundness: Summary • Assignment • Cannot set a null-safe entity to null • Initializaion • Arguments: Always initialized • Local variables: Initialization analysis • Fields • Non null types => Default constructor • Non null fileds = > Initialization block • Otherwise => Explicitly by all constructors
Generics: The problem • Where’s the error here? class Cell<T> { private T value; public Cell() { } public Cell(T t) { value = t; } public T get() { return value; } } void main() { Cell<?String> c1 = new Cell<?String>(); Cell<?String> c2 = new Cell<?String>("abc"); Cell<String> c3 = new Cell<String>(); Cell<String> c4 = new Cell<String>("abc"); } • Generic code may break null-safety • When the actual generic type is non-null
Generics: Solution? • The solution: Use self-initializing types • If Cell<T> uses T in a context where it hasn’t been provably initialized • => T must be self-initializing • This is statically enforced
Design by contract (1/2) • Eiffel features design by contract • Pre/Post conditions, class invariants, … • How does it interact with non-null types? // This is “old” Eiffel code String niceString(Object o) require o != null; ensureresult != null; { if(o.toString() == null) return "<>"; return "<" + o.toString() + ">"; } • We can define new CAPs • Based on pre/post conditions
Design by contract (2/2) • In “New” Eifflel, we can use non-null types • And get rid of many pre/post conditions // This is “new” Eiffel code String niceString(Object o) { if(o.toString() == null) return "<>"; return "<" + o.toString() + ">"; }
Covariant input arguments • “old” Eiffel allow covariant input arguments • => Polymorphic calls may fail at run time • “New” Eiffel is a bit different… • The hierarchy: • PrivilegedAccount extends Account • PrivilegedCustomer extends Customer • Has a getInterest() method
A covariant program • Covariance is now allowed only if the argument is nullable class Account { float interest = 0.03; Customer cus; void setCustomer(Customer c) { cus = c; } } class PrivilegedAccount { void setCustomer(?PrivilegedCustomer c) { super.setCustomer(c); // Can we invoke c.getSpecialInterest() ? } } class Account { float interest = 0.03; Customer cus; void setCustomer(Customer c) { cus = c; } } class PrivilegedAccount { void setCustomer(?PrivilegedCustomer c) { super.setCustomer(c); if((PrivilegedCustomer pc = ) c) interest = pc.getInterest(); } } • This mechanism is too flexible
Comments (1/2) • Open questions • Covariance: CAP can bypass the downcast • Access to fields from constructor code • Arrays • Generics: Why not require a non-null actual type parameter • Currently requires self-initialization • Expressions such as if(list.next != null) are quite useful • With non-null being the default this becomes a bug
Comments (2/2) • Downcast+Assign is used in two contexts • Null safety: Check-not-null and assign to a local • Covariance: Downcast to a restricted set of types • Why not split to two constructs? • Not trying to be backward compatible
Further discussion • Null safety in standard Java • Just use the final keyword • Recursive data structures • Can we replace null values with “null objects” ?
Using null-objects (1/2) // A null terminated list int max = -99999; for(Node n = start(); n != null; n = n.next) max = Math.max(max, n.value); // A null-object terminated list int max = -99999; for(Node n = start(); n.ok(); n = n.next) max = Math.max(max, n.value);
Using null-objects (2/2) Class Node { ... public int max() { return Math.max(value, next.max()); } } Class NullNode { public int max() { return -99999; } }