1 attached types and their application to three open problems of object-oriented programming...
Post on 21-Dec-2015
215 views
TRANSCRIPT
1
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
2/27
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)
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)
This talk:•Presents Meyer’s notion of non-null types•(Implemented in the Eiffel language)
•We will use Java-like syntax
Guarantee that in x.f(a1,a2,..), the receiver, x, will always
denote an object
3/27
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)
4/27
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?
5/27
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
6/27
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());
} }
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());
} }
A non-null program
Downcast+assign expression:• Check non null• Check dynamic type• Assign to a local variable
7/27
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());
} }
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());
} }
8/27
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; } }}
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
9/27
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
10/27
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();
// 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
11/27
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…
12/27
Soundness: Fields
• The problem: Ensuring non-null initialization of fields Historically, fields are initialized with null
• Meyer’s approach: Three distinct solutions…
13/27
Solution 1/3: Self initializing types
class X { public X() { } public void f() { } }
class Y { private X x;
public void h() { x.f(); // Lazy initialization: // Invoke default constructor of x} }
class X { public X() { } public void f() { } }
class Y { private X x;
public void h() { x.f(); // Lazy initialization: // Invoke default constructor of x} }
Field Y.x is lazily initialized on first access• Only if not previously initialized• Applicable if X has a default constructor
14/27
Solution 2/3: Self initializing fields
class Rectangle { Rectangle(int x0, int y0, int x1, int y1) { ... }}
class Circle { private final int 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; }}
• Field Circle.rect is lazily initialized on first access Only if not previously initialized Initialization block added to the field’s definition
15/27
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
16/27
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
17/27
Generics: The problem
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"); }
• Where’s the error here?
• Generic code may break null-safety When the actual generic type is non-null
18/27
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
19/27
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 codeString niceString(Object o) require o != null; ensure result != null;{ if(o.toString() == null) return "<>";
return "<" + o.toString() + ">";}
• We can define new CAPs Based on pre/post conditions
20/27
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 codeString niceString(Object o) { if(o.toString() == null) return "<>";
return "<" + o.toString() + ">";}
21/27
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
22/27
A covariant program
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(); } }
• Covariance is now allowed only if the argument is nullable
• This mechanism is too flexible
23/27
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
24/27
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
25/27
Further discussion
• Null safety in standard Java Just use the final keyword
• Recursive data structures Can we replace null values with “null objects” ?
26/27
Using null-objects (1/2)
// A null-object terminated listint max = -99999;for(Node n = start(); n.ok(); n = n.next) max = Math.max(max, n.value);
// A null terminated listint max = -99999;for(Node n = start(); n != null; n = n.next) max = Math.max(max, n.value);