Question on rationale for FORMAT_DOUBLE non-conforming inheritance of FORMAT_INTEGER

50 views
Skip to first unread message

Finnian Reilly

unread,
Jun 6, 2026, 5:43:53 PM (7 days ago) Jun 6
to Eiffel Users

Hello all,

I've just discovered that in EiffelStudio 25.12, FORMAT_DOUBLE has been changed to inherit non-conformingly from FORMAT_INTEGER. I'd like to understand the reasoning behind this decision, because it has broken a class of mine that compiled and worked correctly under 16.05.

My class EL_FORMAT_ROUTINES maintains a cache table typed as:

format_table: EL_AGENT_CACHE_TABLE [FORMAT_INTEGER, INTEGER]

with a factory feature returning FORMAT_INTEGER:

new_format (key: INTEGER): FORMAT_INTEGER
    do
        ...
        if is_real then
            create {FORMAT_DOUBLE} Result.make (width, decimal_count)
        else
            create Result.make (width)
        end
    end

and a retrieval site that uses an object test downcast:

if attached {FORMAT_DOUBLE} format_table.item (key) as format then
    Result := format.formatted (d)
end

Both of these relied on FORMAT_DOUBLE conforming to FORMAT_INTEGER. With non-conforming inheritance that conformance is gone, so:

  1. create {FORMAT_DOUBLE} Result is no longer valid when Result is typed FORMAT_INTEGER.
  2. The attached {FORMAT_DOUBLE} object test can never succeed — FORMAT_DOUBLE objects will never be stored in a FORMAT_INTEGER-typed container.

It is worth noting that the unified table is not merely a convenience — it serves a concrete optimisation purpose. Format objects are expensive to create repeatedly, so EL_FORMAT_ROUTINES caches them by a packed integer key encoding width, decimal count, zero-padding, and type (integer vs real). A single EL_AGENT_CACHE_TABLE [FORMAT_INTEGER, INTEGER] handles both kinds transparently: on first access the appropriate object is created and stored; on subsequent calls with the same parameters the cached instance is returned directly. The polymorphic subtype relationship was what made this unified cache possible. Severing conformance forces either two separate tables with duplicated keying logic, or a wrapper type to re-unify them — both of which add complexity to compensate for a change whose motivation remains unclear.

A note on non-conforming inheritance in general

Non-conforming inheritance — and its C++ cousin, private inheritance — has a well-established reputation as a design smell in the OO literature. The core criticism is that it conflates two distinct concerns: subtype polymorphism ("is-a") and implementation reuse ("is-implemented-in-terms-of"). The latter is more honestly and transparently expressed through composition. Scott Meyers' Effective C++ (Item 39) puts it plainly: prefer composition whenever you can, and reserve private inheritance for the narrow case where the empty base class optimisation is genuinely needed. Most modern languages — Java, C#, Go, Rust, Swift — omit private inheritance entirely, a deliberate signal that the pattern solves a problem better solved another way.

Eiffel's inherit {NONE} is a more principled version of the same idea: the syntax is explicit, feature adaptation is fine-grained, and multiple inheritance is properly supported. But the fundamental tension remains. When non-conforming inheritance is applied to a class that already has a natural and well-used subtype relationship — as FORMAT_DOUBLE did with FORMAT_INTEGER — it doesn't just suppress conformance in the abstract; it actively breaks client code that depended on that relationship being there.

Back to the specific case

This is a straightforward and natural use of polymorphism: a table of formatters keyed by integer, holding either FORMAT_INTEGER or FORMAT_DOUBLE instances, with the appropriate subtype retrieved at the call site. It's hard to imagine this pattern being intentional breakage rather than an oversight.

Could someone from Eiffel Software explain:

  • What motivated the change to non-conforming inheritance?
  • Was the loss of FORMAT_DOUBLE conforming to FORMAT_INTEGER intentional?
  • Is there a recommended migration path — other than duplicating the table and factory logic for each type?

The workaround isn't difficult (separate tables, or wrapping FORMAT_DOUBLE), but the change feels like it sacrifices a well-established and useful subtype relationship without obvious gain. If the concern was preventing misuse of FORMAT_INTEGER features on DOUBLE values, that could equally have been addressed through preconditions or a refined interface rather than severing conformance entirely.

Fortunately, Eiffel's library override mechanism means this kind of mis-step doesn't have to be a permanent imposition on library users. By placing a corrected version of FORMAT_DOUBLE in an ECF override cluster, conforming inheritance can be restored without touching the EiffelBase sources. That is precisely what I intend to do for Eiffel-Loop — but it would of course be preferable not to have to.

Finnian Reilly

https://github.com/finnianr/Eiffel-Loop/blob/master/library/base/text/format/el_format_routines.e


-- 
SmartDevelopersUseUnderScoresInTheirIdentifiersBecause_it_is_much_easier_to_read

Eric Bezault

unread,
Jun 7, 2026, 10:22:41 AM (7 days ago) Jun 7
to eiffel...@googlegroups.com, Finnian Reilly
Hi Finnian,

Looking at the repository history, the modification was done on
February 22nd, 2021 with the following commit comment:

Replaced conforming inheritance with non-conforming one
where possible and appropriate.

So, I would say that the "possible and appropriate" criteria
do not apply here since you showed us that there are existing
code which relies on the conforming inheritance.

I would suggest that you submit a problem report to:

https://support.eiffel.com

to record this issue. BTW, I just submitted the 20,000th problem
report on this platform. Hopefully I will win something :-)

You can also prepare a Pull Request on Github:

https://github.com/EiffelSoftware/es-libraries

with the version of the class that you put in your override cluster.

--
Eric Bezault <er...@gobosoft.com>
Eiffel expert - available for freelance work
https://www.gobosoft.com



On 06/06/2026 23:43, Finnian Reilly wrote:
> Hello all,
>
> I've just discovered that in EiffelStudio 25.12, |FORMAT_DOUBLE| has
> been changed to inherit non-conformingly from |FORMAT_INTEGER|. I'd like
> to understand the reasoning behind this decision, because it has broken
> a class of mine that compiled and worked correctly under 16.05.
>
> My class |EL_FORMAT_ROUTINES| <https://github.com/finnianr/Eiffel-Loop/
> blob/master/library/base/text/format/el_format_routines.e> maintains a
> cache table typed as:
>
> format_table: EL_AGENT_CACHE_TABLE [FORMAT_INTEGER, INTEGER]
>
> with a factory feature returning |FORMAT_INTEGER|:
>
> new_format (key: INTEGER): FORMAT_INTEGER do ... if is_real then create
> {FORMAT_DOUBLE} Result.make (width, decimal_count) else create
> Result.make (width) end end
>
> and a retrieval site that uses an object test downcast:
>
> if attached {FORMAT_DOUBLE} format_table.item (key) as format then
> Result := format.formatted (d) end
>
> Both of these relied on |FORMAT_DOUBLE| conforming to |FORMAT_INTEGER|.
> With non-conforming inheritance that conformance is gone, so:
>
> 1. |create {FORMAT_DOUBLE} Result| is no longer valid when |Result| is
> typed |FORMAT_INTEGER|.
> 2. The |attached {FORMAT_DOUBLE}| object test can never succeed — |
> FORMAT_DOUBLE| objects will never be stored in a |FORMAT_INTEGER|-
> typed container.
>
> It is worth noting that the unified table is not merely a convenience —
> it serves a concrete optimisation purpose. Format objects are expensive
> to create repeatedly, so |EL_FORMAT_ROUTINES| caches them by a packed
> integer key encoding width, decimal count, zero-padding, and type
> (integer vs real). A single |EL_AGENT_CACHE_TABLE [FORMAT_INTEGER,
> INTEGER]| handles both kinds transparently: on first access the
> appropriate object is created and stored; on subsequent calls with the
> same parameters the cached instance is returned directly. The
> polymorphic subtype relationship was what made this unified cache
> possible. Severing conformance forces either two separate tables with
> duplicated keying logic, or a wrapper type to re-unify them — both of
> which add complexity to compensate for a change whose motivation remains
> unclear.
>
>
> A note on non-conforming inheritance in general
>
> Non-conforming inheritance — and its C++ cousin, private inheritance —
> has a well-established reputation as a design smell in the OO
> literature. The core criticism is that it conflates two distinct
> concerns: subtype polymorphism ("is-a") and implementation reuse ("is-
> implemented-in-terms-of"). The latter is more honestly and transparently
> expressed through composition. Scott Meyers' /Effective C++/ (Item 39)
> puts it plainly: prefer composition whenever you can, and reserve
> private inheritance for the narrow case where the empty base class
> optimisation is genuinely needed. Most modern languages — Java, C#, Go,
> Rust, Swift — omit private inheritance entirely, a deliberate signal
> that the pattern solves a problem better solved another way.
>
> Eiffel's |inherit {NONE}| is a more principled version of the same idea:
> the syntax is explicit, feature adaptation is fine-grained, and multiple
> inheritance is properly supported. But the fundamental tension remains.
> When non-conforming inheritance is applied to a class that /already has/
> a natural and well-used subtype relationship — as |FORMAT_DOUBLE| did
> with |FORMAT_INTEGER| — it doesn't just suppress conformance in the
> abstract; it actively breaks client code that depended on that
> relationship being there.
>
> *Back to the specific case*
>
> This is a straightforward and natural use of polymorphism: a table of
> formatters keyed by integer, holding either |FORMAT_INTEGER| or |
> FORMAT_DOUBLE| instances, with the appropriate subtype retrieved at the
> call site. It's hard to imagine this pattern being intentional breakage
> rather than an oversight.
>
> Could someone from Eiffel Software explain:
>
> * What motivated the change to non-conforming inheritance?
> * Was the loss of |FORMAT_DOUBLE| conforming to |FORMAT_INTEGER|
> intentional?
> * Is there a recommended migration path — other than duplicating the
> table and factory logic for each type?
>
> The workaround isn't difficult (separate tables, or wrapping |
> FORMAT_DOUBLE|), but the change feels like it sacrifices a well-
> established and useful subtype relationship without obvious gain. If the
> concern was preventing misuse of |FORMAT_INTEGER| features on |DOUBLE|
> values, that could equally have been addressed through preconditions or
> a refined interface rather than severing conformance entirely.
>
> Fortunately, Eiffel's library override mechanism means this kind of mis-
> step doesn't have to be a permanent imposition on library users. By
> placing a corrected version of |FORMAT_DOUBLE| in an ECF override
> cluster, conforming inheritance can be restored without touching the
> EiffelBase sources. That is precisely what I intend to do for Eiffel-
> Loop — but it would of course be preferable not to have to.
>
> Finnian Reilly
>
> https://github.com/finnianr/Eiffel-Loop/blob/master/library/base/text/
> format/el_format_routines.e
>
>
> --
> SmartDevelopersUseUnderScoresInTheirIdentifiersBecause_it_is_much_easier_to_read
>
> --
> You received this message because you are subscribed to the Google
> Groups "Eiffel Users" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to eiffel-users...@googlegroups.com <mailto:eiffel-
> users+un...@googlegroups.com>.
> To view this discussion visit https://groups.google.com/d/msgid/eiffel-
> users/daaab712-2c2c-48ba-9df8-219ec066ee73%40eiffel-loop.com <https://
> groups.google.com/d/msgid/eiffel-users/
> daaab712-2c2c-48ba-9df8-219ec066ee73%40eiffel-loop.com?
> utm_medium=email&utm_source=footer>.



Alejandro Garcia

unread,
Jun 7, 2026, 3:14:59 PM (7 days ago) Jun 7
to eiffel...@googlegroups.com, Finnian Reilly
Using this thread to ask a question that has bothered me for some time:

What are the advantages of non-conforming inheritance (reuse with no subtyping)? I've seen in several places that people complain about subtyping, but I don't understand what the problem is.

I mean:

  - Java has subtyping without reuse (interfaces).
  - Ruby and Smalltalk have reuse without subtyping (mixins).
  - Eiffel is the one that has reuse with subtyping (multiple inheritance).

Non-conforming inheritance gives us reuse without subtyping, which seems similar to mixins, but I don't see the benefit. Sometimes I have wished for the opposite—extracting the interface of a class without any reuse (like interfaces)—but never for something like mixins.

So what are the benefits? Is it purely to avoid the "is-a" relationship when you only want the code, or is there a deeper architectural advantage I am missing?



To unsubscribe from this group and stop receiving emails from it, send an email to eiffel-users...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/eiffel-users/819858a5-25ec-432a-bcf8-ebcf47cd6792%40gobosoft.com.


--
Alejandro García F. (elviejo)
https://elviejo79.github.io


Too brief? Here's why! https://www.emailcharter.info
EOM – End Of Message. The whole message is in the subject don't need to open it.
NNTR – No Need To Respond. Help cut down on all those “cool” and “thanks” emails.
SINGLE SUBJECT. Send one email for one topic, this makes replies easy..
CLEAR CALL TO ACTION: Ask for some specific result very clearly.

Bertrand Meyer

unread,
Jun 7, 2026, 7:22:46 PM (6 days ago) Jun 7
to eiffel...@googlegroups.com, me...@inf.ethz.ch

The core idea of object-oriented programming, the brilliant intuition of Nygaard and Dahl (although they did not explain it in so many words), is the merging of two programming concepts: module and type. A module is a structural (syntactic) unit of decomposition, affecting the static view (program text). A type is a semantic unit, affecting the dynamic view (program execution): the description of a set of possible values. It is initially quite counter-intuitive to treat such seemingly disconnected notions as one and the same, which is why it took two decades between the start of modern programming and the design of OO technology. The result of the merge is the class, the distinctive concept of OO programming (and OO design, database, requirements etc.).

 

The way the counter-intuitive merge works is that the services offered by the class, viewed as a module, are precisely the operations on instances of the class, viewed as a type. It takes a while to drill this idea into one’s mind but once you have operationalized it you understand object technology.

 

Strangely, as far as I know no one else explains OO this way. I do, starting even before my OOSC book. Just as strangely, many people (outside of this group) who claim to have read the book also do not understand the idea, even though that is really all that the book says. Maybe that is my fault for taking 1270 pages to explain it. My experience is that people usually stop around page 7.

 

The beauty of the merge extends to inheritance. You look at the class as a module, and you can extend it; in this sense inheritance subsumes the idea of module reuse, as given for example by “include” or “package use” mechanisms in C, Ada, Java etc. You look at the class as a type, and you can specialize it; in this sense inheritance subsumes the idea of subtype, as you would find it in a functional language.  The versatility of the inheritance mechanism, following from the versatility of the class mechanism, comes from the type/module duality. Kind of like wave/particle. It also explains the apparent paradox: inheritance is both extension (we add features to the module) and specialization (we restrict the set of type instances).

 

There is no reason to restrict this mechanism, for example through the horrendous “interface” facilities of languages such as Java and C#, which put an unacceptable expressive restriction on the programmer, or through the restriction to single inheritance. (For more details, see my 2024 “Right and Wrong: Ten Choices in Language Design”, part of the collective book “The French School of Programming”, preprint at https://arxiv.org/pdf/2211.16597.)

 

I can think of no obvious case in which -- out of the two views -- you need only type specialization, but it can happen that you need only module reuse. For example you might want to inherit from a facility class which encapsulates math constants. (There are other ways to reuse, such as non-object call in Eiffel, but often inheritance is simpler.) In such cases subtyping would not really  be wrong but it causes irrelevant technical problems (the need to disambiguate features in repeated inheritance) and you can avoid them by declaring the inheritance link to be non-conforming. An added level of flexibility, extending the OO programmer’s basic toolset.

 

-- BM

Ian Joyner

unread,
Jun 8, 2026, 6:03:56 AM (6 days ago) Jun 8
to eiffel...@googlegroups.com
Bertrand gives an excellent and simple explanation. Class = module.

When I see people saying (actually being vociferous) to avoid inheritance, I usually respond that they don’t understand inheritance.

I think a root cause of this is that programmers understand reuse, but not so much types. Facilities like types have long been dismissed as ’training wheels for beginners’ and ‘crutches for weak programmers’ because they catch errors at compile time, rather than resorting to the hairy-chested hack-run-crash-debug repeat hacking until-it-works cycle.

People do this because they don’t have a deeper understanding of computation. They think programming is about computers. Programming is a more mathematical endeavour. I see many on social media post (in response to questions about mathematics and programming) that programming has nothing to do with mathematics, a completely mistaken and misleading view.

Many people have been taught that inheritance is the ‘is-a’ relationship, and composition ‘has-a’ (or something related). These are excellent ways to think and correct. But people miss the significance. ‘is-a’ means a very tight relationship. Subclasses are inextricably bound to parent classes. They form the same object. Invariants that must apply to parent classes must apply to the subclasses (many programmers will tune out at the word ‘invariant’). If not, it is not an inheritance relationship.

What inheritance gives us is the ability to not define each object (run-time entitiy) in terms of a single huge module, but to be able to divide that into smaller and more manageable classes (compile-time entities — Smalltalk tends to make classes part of the run time as well). Thus these small generic classes can be used in other specialised situations.

If we use inheritance for reuse, we are not thinking about types. Inheritance is simply one form of reuse, composition is the other — rather than code it ourselves in our own class, we ask another object to do something, rather than another class as in inheritance.

Perhaps one of the problems with OO teaching is that inheritance is taught early as the mechanism we should be using in everything. So it gets overused before programmers really understand types and how inheritance defines a type hierarchy. Type hierarchies are not absolutes. What makes sense in one context might not in another. For example, what defines Person in a computer game will be different than Person in a banking application — different aspects will form different essential defining aspects. These are differences between applications. For general facilities, these must span contexts for different applications.

Thus I advise people online to only use inheritance to define type taxonomies, not to force reuse. Yes, inheritance is powerful. It should be used judiciously.

One of my more recent criticisms of C++ is the plethora of module mechanisms, even one called module., keeping structs, and namespaces for dividing up the global environment. These are all distinctly non-OO and that OO was not understood by its designer, rather like structured programming was not understood in the development of C. Certainly there are benefits from adopting structured syntax and OO facilities, but these only subvert the real power. C++ sees classes like structs — something you optionally use, rather than starting with classes and everything (at least statically) organised around classes. Java, at least does better, except for still requiring ‘main’ and separating classes from the type taxonomy of the Java ‘interface'. Perhaps the idea of interface in Java was to stop programmers misusing inheritance, but then it makes reuse cumbersome, not defining default behaviour in an interface. It also means that programmers are confused about thinking about classes — all classes implicitly have an interface.

Python also makes classes take a back seat. All of these languages have the visual clutter of the explicit ‘()’ operator (not from C but an error of Christopher Strachey in CPL, where the awful C pointer also comes from). As abstractions, names implicitly invoke themselves, but because routines can be invoked via pointers (what the pointer points to is anonymous). The ‘()’ breaks the Uniform Access Principle, requiring the boilerplate of explicit getters and setters (which do not achieve encapsulation because the structure and implementation of a class is still exposed). A name should implicitly invoke what it represents. Passing around a name without invocation and then invoking that entity requiring explicit ‘call’ is the exception, but C-based languages make it the norm. Alas, many programmers have come to expect ‘()’, which sets programming back to the 1950s with explicit CALL of FORTRAN and COBOL. ‘()’ is also an aid to the compiler rather than the programmer.

Reuse has become such a victim of explicit and forced thinking. Programmers equate the OO mechanism of reuse with inheritance. This is an incorrect use of inheritance. Reuse is just something that implicitly happens in well-structured software. Programmers seem to need sledgehammers — they don’t understand the subtleties of the approach. Subtlety is difficult to teach. Mechanisms like ‘()’, inheritance, goto, return, etc, are forced mechanisms. These are easier for teachers to teach, even if they are the wrong thing, although inheritance is the right thing for subtle taxonomic purposes, just not for unsubtle reuse.

These are the kinds of problems in people’s thinking that I glean from engaging with them on Quora and Medium. Unfortunately, teachers at universities and other courses just pass on the same lack of understanding. They look for explicit mechanisms rather than understanding deeper issues.

Anyway, there are some thoughts.

Ian

João Rocha

unread,
Jun 8, 2026, 7:10:40 AM (6 days ago) Jun 8
to eiffel...@googlegroups.com
We should start a movement:

        “You think you know OO.”

João

On 8 Jun 2026, at 11:03 AM, Ian Joyner <joyne...@gmail.com> wrote:

Bertrand gives an excellent and simple explanation. Class = module.

Finnian Reilly

unread,
Jun 8, 2026, 8:53:43 AM (6 days ago) Jun 8
to eiffel...@googlegroups.com, Eric Bezault

Hi Eric,

Thank you for digging into the repository history, that's very helpful. The commit comment itself rather makes the case: "where possible and appropriate" is precisely the question, and as you note, the existence of client code relying on the conforming relationship is a clear signal that "appropriate" was not satisfied here.

I'll submit a problem report as you suggest, and will look at preparing a Pull Request with the corrected class once the override is in place in Eiffel-Loop.

As for the 20,000th report, congratulations . Your dedication is admirable. I'd suggest your prize should be 1 euro per report: at that rate you've clearly earned a cruise-ship holiday at minimum. But I expect you will be bringing your dev laptop with you ;-)

regards

Finnian

Hi Finnian,

Looking at the repository history, the modification was done on
February 22nd, 2021 with the following commit comment:

    Replaced conforming inheritance with non-conforming one
    where possible and appropriate.

So, I would say that the "possible and appropriate" criteria
do not apply here since you showed us that there are existing
code which relies on the conforming inheritance.

I would suggest that you submit a problem report to:

   https://support.eiffel.com

to record this issue. BTW, I just submitted the 20,000th problem
report on this platform. Hopefully I will win something :-)

You can also prepare a Pull Request on Github:

   https://github.com/EiffelSoftware/es-libraries

with the version of the class that you put in your override cluster.


-- 
SmartDevelopersUseUnderScoresInTheirIdentifiersBecause_it_is_much_easier_to_read

Ulrich Windl

unread,
Jun 12, 2026, 1:32:32 PM (2 days ago) Jun 12
to eiffel...@googlegroups.com
Some random thoughts:
Maybe a problem with the bad reputation of inheritance is that one can do "cool things" via dynamic bindings, and teachers may try to impress (or confuse) students showing code  that actually breaks type contract.
The other is "feature inheritance" vs. "type inheritance": Probably any need for type inheritance is a sign of bad type design, maybe requiring "repatenting".
From the faint back of my memory: Didn't even BM list "feature inheritance" in OSC2?

Ulrich

08.06.2026 12:03:38 Ian Joyner <joyne...@gmail.com>:

> come

Ulrich Windl

unread,
Jun 12, 2026, 1:34:40 PM (2 days ago) Jun 12
to eiffel...@googlegroups.com
You mean: "
> You think you know OO. We think you don't."?

08.06.2026 13:10:23 João Rocha <rocha....@gmail.com>:

"João M. Rocha"

unread,
Jun 12, 2026, 6:02:32 PM (2 days ago) Jun 12
to eiffel...@googlegroups.com
Yeah, I was implying the "we think you don't" :-)

It's provocative, but I think it almost needs to be given how saturated the OO discussion space is. But I don't mean it to be offensive. The goal would be to make people curious enough to take a second look at some assumptions.

Imagine going through online discussions and code reviews and simply dropping a link:
The website could be a translation of OOSC for the "masses". A way to communicate the core ideas in a form that is easier to link to, easier to reference, easier to digest.

It could be good enough to make people experience the "click" that many of us had when reading OOSC.

João

--
You received this message because you are subscribed to the Google Groups "Eiffel Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to eiffel-users...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages