[Grace-core] Multiple inheritance & module inheritance

Michael Homer mwh at ecs.vuw.ac.nz
Mon Mar 9 00:25:19 PDT 2015


This is a proposal to support multiple inheritance.

Background: in the "fresh objects" design, an inherits
statement includes a method request, the resulting method of
which must tail-return an object constructor. A method
tail-returns an object constructor iff the last element of
the method body is a (return of) an object constructor.
That object constructor is executed in the context of the
existing object identity (the one with the inherits clause
in it) and the methods defined in all inheriting objects.
The inherits clause must be the first thing inside the
object body. (End background)

This proposal builds on the fresh objects design. In
particular, any program that is valid in the current design
continues to function with the same behaviour under this
proposal.

This proposal focuses on the dynamic semantics. Additional
restrictions on behaviour (what can be inherited from and
how, etc) may be enforced by a dialect.

Inherits statements
===================

An inherits statement has the form "inherits <REQUEST>",
where <REQUEST> is any valid method request in the current
scope.

The request must be answered by a method whose last element
is a return of an object constructor, and that constructor
will be executed in the context of the object identity
already under construction. Class declarations are syntactic
sugar for an object having such a method, so "inherits
dog.aged(3)" is a valid statement.

A method tail-returning an object constructor continues to
behave as now.

Multiple inheritance
====================

Multiple "inherits" statements can appear in a single
object, executing in order of appearance. A single statement
at the top has the behaviour of the current system.

Positional inheritance
======================

Inherits statements can appear at any point in the object
body, and the inheritance occurs visibly at that point. All
side effects of the inherited request occur when execution
flow reaches that line, and by the start of the following
line all initialisation in that inheritance chain has
completed and all its methods are defined.

Methods and fields defined in the current object constructor
are available at the start of the execution of its body,
although it may not be possible to execute them successfully
until other initialisation has completed. Methods and fields
defined in a superobject are available after the "inherits"
statement introducing them, and their initialisation will be
complete.

The effect is that, with "inherits" at the top of
the object body, upcalls are safe; with it at the bottom,
downcalls to you are safe; and with care, you can order
things so both work. Common programming styles are likely to
favour either upcalls or downcalls, so a dialect may enforce
that inherits statements only appear at either the top or
bottom for its code.

This resolves issues with what Kim wants to do in the
current system, but may require some motivation to be clear
on what the point is. I have put the sequence of motivating
examples in a sidebar so they don't stretch this out. I will
post them as a follow-up, but they are available now from
<http://ecs.vuw.ac.nz/~mwh/positional-motivation.txt>.

Ambiguity
=========

An inherits statement can include an "as" clause, just like
a module import. The given name can only be used as the
receiver for directed super-sends up a particular
inheritance tree.  "self" remains bound to the same object.

When a single anonymous inherits statement exists, it is
implicitly named "super". This allows all existing code to
function exactly as now.

If an object inherits multiple methods by the same name, the
one provided by the last inherits statement wins. In all
cases, the eventual method defined in the object (whether
inherited or local) SHOULD be fully compatible with the
signatures of all inherited methods by the same name, with a
local override being provided to satisfy this criterion if
applicable.

Verifying this compatibility statically is up to the
dialect, with only the ordinary gradual checks occurring at
runtime. This is required in order to allow the "when the
programmer does it, that means that it is not illegal"
style.

In the case of trait-style inheritance of disjoint sets of
methods, no ambiguity arises. Multiple independent parents
may be composed without issue.

In the case of single-inheritance chains, no ambiguity
arises. A local definition in the initiating object
constructor will always override an inherited version.

Modules
=======

Inheriting from modules is by (shallow) cloning.  Modules
(but not other objects) implicitly get a public "clone"
method added to them with the appropriate effect.

The method may be overridden explicitly, and must either
return an object constructor or raise an exception.

Option:
- The method is called something nonspecific and can be
  provided with various semantics: cloning, delegation,
  forwarding, &c. These may be obtained by inheriting from a
  particular library class. In this way they do not need
  additional privileged access to self reflectively from
  outside.

Unresolved issues
=================

- Evaluation order of types that depend on types defined in
  a superclass.

- "Definitively static" rules out all inheritance chains not
  rooted in an object defined in the local scope of the
  method or an imported module.

- Static binding of names with type parameters.

All of the above may be related to, but are not the same as,
Tim's proposed "let".

- Introduction of method names in a subclass/use of
  unqualified names in the superclass.

- Do repeated field declarations give rise to new storage
  spaces? Some relevant examples are in another sidebar at
  <http://ecs.vuw.ac.nz/~mwh/catfish.txt>.

- Non-local returns and exceptions during object
  construction can lead to leaked partially-initialised
  objects.


More information about the Grace-core mailing list