1 attached types and their application to three open problems of object-oriented programming...

27
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

Post on 21-Dec-2015

215 views

Category:

Documents


1 download

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);

27/27

Using null-objects (2/2)

Class Node { ... public int max() { return Math.max(value, next.max()); }}

Class NullNode { public int max() { return -99999; }}