Incremental Programming with

Extensible Decisions

Doug Orleans College of Computer Science

Northeastern University

dougo@ccs.neu.edu

April 19, 2002

Incremental Programming

“The construction of new program components by specifying how they differ from existing components.” [Cook & Palsberg, OOPSLA 1989] A language that supports incremental programming:

• allows good separation of concerns

• reduces code duplication

• improves extensibility and reuse

Extensible Decisions

In OOP, whenever a message is sent, a decision occurs (dynamic dispatch), but the branches of the decision are specified separately: methods that correspond to the message signature.

Incremental programming is supported because methods can override each other, but only through inheritance. Different branches of an extensible decision must involve different classes.

Design Patterns

Many behavioral design patterns [Gamma et al, 1994] are essentially workarounds for this constraint of OOP. For example, the State pattern implements state-based dispatch with one class per state.

But…

• Objects must delegate all state-dependent messages to their state objects: runtime overhead and a coding burden.

• The state objects must be manually kept up to date when the state condition changes: no implicit states.

A Different Solution

Change the language to make programs easier to express instead of changing your program to fit the constraints of the language.

Two approaches to allowing incremental programming without requiring inheritance:

• Aspect-Oriented Programming (AOP) [Kiczales et al, 1997]

• Predicate Dispatching [Ernst, Kaplan, & Chambers, ECOOP 1998.]

Aspect-Oriented Programming (AspectJ)

Incremental programming for crosscutting concerns: an aspect can override behavior in other classes without using inheritance.

• Each piece of advice has a pointcut which specifies when the advice is applicable to a message send.

• Pointcuts can be arbitrary boolean expressions.

Predicate Dispatching

A form of dynamic dispatch that unifies and generalizes the dispatch mechanisms found in many programming languages, including OO single and multiple dispatch.

• Each method implementation has a predicate which specifies when the method is applicable to a message send.

• Predicates can be arbitrary boolean expressions.

Advantages of AspectJ Over Predicate Dispatching

• Pointcuts can access more information about a message send than just the receiver and arguments:

– message signature

– control flow history

– location of message send code

• Method combination: before/after advice, proceed() in

around advice

Advantages of Predicate Dispatching Over AspectJ

• More elegant model, natural generalization of OO dispatch. Not tacked onto the side of Java.

• Method overriding is based on logical implication:

– A method with predicate p_{1} overrides a method with predicate p_{2} if p_{2} is true in all cases where p_{1} is true.

– Example: is-a (x, A) ⟹ is-a (x, B) (where A is a subclass of B)

– I.e., subclass methods override superclass methods, just like in OOP.

Fred

A new programming language that takes the best from both worlds.

Fred’s dispatch mechanism unifies those of AOP and OOP languages, and provides uniform support for incremental programming whether the concerns implemented by the components are crosscutting or not.

• Behavior is specified as branches, each of which has a condition predicate over decision points.

• Decision points capture message signature, message arguments, source branch, previous decision point.

Fred (continued)

• Overriding is based on logical implication of predicates, but around branches always override plain branches.

• follow-next-branch allows method combination.

• Syntactic sugar allows more declarative syntax for common kinds of branches, to look more like multimethods or advice.

• Prototype implementation is embedded in MzScheme.

OOP in Fred (1/3)

(define-class person () (fname lname))
(define-msg full-name)
(define-branch
;; condition predicate:
(lambda (dp) (and (eq? (dp-msg dp) full-name)
                  (= (length (dp-args dp)) 1)
                  (is-a? (car (dp-args dp)) person)))
;; body:
(lambda (dp) (let ((this (car (dp-args dp))))
               (string-append (get-fname this) " "
                              (get-lname this)))))

OOP in Fred (2/3)

With sugar:

(define-branch (&& (call full-name) (args person))
  (with-args (this)
    (string-append (get-fname this) " " (get-lname this))))

More sugar:

(define-method full-name (this) & (is-a? this person)
    (string-append (get-fname this) " " (get-lname this)))

Still more sugar:

(define-method full-name ((person this))
    (string-append (get-fname this) " " (get-lname this)))

OOP in Fred (3/3)

(define-class knight (person) ())
(define-method full-name ((knight this))
  (string-append "Sir " (follow-next-branch)))

Example output:

> (define gandalf (make knight "Ian" "McKellen"))
> (full-name gandalf)
"Sir Ian McKellen"

AOP in Fred (1/2)

(define-around
  (lambda (dp) (is-a? (car (dp-args dp)) person))
  (with-msg (msg)
    (printf "Received message ~a.~n" (msg-name msg))
    (follow-next-branch)))

With sugar:

(define-before (args person ..)
  (with-msg (msg)
    (printf "Received message ~a.~n" (msg-name msg))))

AOP in Fred (2/2)

(define-before (&& (args person ..) (! in-full-name-cflow?))
  (with-msg-and-args (msg this . rest)
    (printf "~a received message ~a.~n"
            (full-name this) (msg-name msg))))
(define (in-full-name-cflow? dp)
  (let ((prev (dp-previous dp)))
    (and prev (or (eq? (dp-msg prev) full-name)
                  (in-full-name-cflow? prev)))))

With sugar:

(define in-full-name-cflow? (cflowbelow (call full-name)))

Future Work

• Compare with other AOP models (composition filters, hyperslices, mixin layers, aspectual collaborations, QoS regions)

• Variable binding in predicates (like AspectJ’s context exposure in pointcuts)

• Customized branch overriding relationships

• Extensible predicates

• Modularization, using MzScheme’s units [Flatt & Felleisen, PLDI 1998]