Wednesday, December 21, 2011

Insert or non-conforming inheritance

All programming languages have been weak spots that are criticized, Eiffel is no exception.
One of the most sound critique to the original Eiffel is that inheritance was the only modularity mechanism available.
In fact it is a fundamental part of the object oriented approach to programming.
Yet in its pristine, original form inheritance it does not allow to implement shared constant or commodity features (either queries or commands) in a clean way.
For example to implement dictionaries, hash tables and many cryptografic algorithms there's the need to have a handy list of prime numbers or a function that tells the first prime higher than a given integer. Let's say we put all these informations into a PRIMES_LIST class; in pristine Eiffel we would have written:
class DICTIONARY [A_VALUE, A_KEY]
inherit PRIMES_LIST export all {NONE} end
....
end -- class DICTIONARY

In this (oversimplified) example a DICTIONARY actually is a list of primes, but a really strange one, since with "export all {NONE}" we told the compiler that all the features originally defined in PRIMES_LIST cannot be invoked, queried or used by any code handling a DICTIONARY.
Beside looking a palese breach in the OO architecture it actually answered in the wrong way to a rightful need: sharing some code or data between different parts of the programs; the pecualiar aspect is that all that code or data is not linked or bounded to a particular type at all, but they are quite generic.
There's the need to have all the features - queries, commands constants and attributes - of another class without being a subtype of that class; we need what is known as non-conforming inheritance or feature insertion.
That's exactly the role of "insert": to have all the feature on another class without being conforming to that class.
In fact with the previous example you may write:
feature strange is
  local primes_lists: LINKED_LIST[PRIMES_LIST]
  do
    create primes_list
    primes_list.append ( {PRIMES_LIST} )
    primes_list.append ( {DICTIONARY[STRING; INTEGER]} )
    primes_list.append ( {CRYPTO_USING_PRIMES} )
    ...
end
What a strange collection: a list compraising a list of primes, a dictionary containing strings and a cryptografic stream! The designer of a program may actually do not want to have such objects.
When we do want to share some definitions or a list of primes like in this example we may have inserted the features of PRIMES_LIST, like this:
class DICTIONARY [A_VALUE, A_KEY]
insert PRIMES_LIST
....
end -- class DICTIONARY
Actually it is customary to declare those auxiliary classes deferred, in order to forbid direct usage of that class itself.
ECMA Eiffel has the same non-conforming mechanism, but its syntax is "inherit {NONE} PRIMES_LIST", meaning inherit conforming to no class. While this is correct it seems to allow to write "inherit {MY_CLASS} PRIMES_LIST", bringing confusion together with a useful, simple mechanism. I have discussed the rationale behind this some time before SmartEiffel team decided not to implement ECMA, I'll try to dig it out.

2 comments:

  1. Question, if using INSERT: - does the client code (the caller) still need to respect the pre- and postconditions of the called features?

    Hans

    ReplyDelete