vectorgraphics / asymptote

2D & 3D TeX-Aware Vector Graphics Language
https://asymptote.sourceforge.io/
GNU General Public License v3.0
533 stars 89 forks source link

Inheritance should be transitive; implicit type-casting is not transitive #426

Open charlesstaats opened 5 months ago

charlesstaats commented 5 months ago

In the documentation, there is a section explaining how to "fake" inheritance using implicit typecasting. This is somewhat misleading since inheritance is transitive, but implicit typecasting is not.

For instance, in Java, an ArrayList is a kind of List which is itself a kind of Iterable. Consequently, you can pass an ArrayList object to any function that accepts an Iterable parameter. But if you tried to imitate this in Asymptote by creating types ArrayList, List, and Iterable, together with implicit typecasts ArrayList -> List and List -> Iterable, you would still not be able to pass an ArrayList to a function that expects an Iterable parameter:

struct Iterable { }

struct List { }
Iterable operator cast(List L) { return null; }

struct ArrayList { }
List operator cast(ArrayList L) { return null; }

void doNothing(Iterable i) { }

ArrayList L = new ArrayList;
doNothing((List)L);  // works
doNothing(L);  // does not work (but would if implicit casting were transitive)

I see two potential solutions to this issue:

  1. (Recommended) Remove or amend the section in the documentation about imitating inheritance. (This technique doesn't seem to have been used much, and without transitivity it's not useful for complex hierarchies.)
  2. Make implicit casting transitive. Basically, this would involve making the it so that if you have, e.g., implicit cast operators guide -> path and path -> path[], you'd automatically get an implicit path operator guide -> path[] (it would no longer need to be defined explicitly in plain_paths.asy). This sounds nice, but there's a catch: If you also had implicit casts guide -> guide[] and guide[] -> path[], you'd end up with an ambiguous "casting route" and go back to needing an explicitly defined guide -> path[]. Even worse, someone could have code that was working perfectly well but then broke because someone introduced a new cast guide -> guide[] and therefore made the existing code ambiguous. This last issue — the great potential for very sensible changes to break very sensible downstream code — is why I am skeptical of introducing transitivity, even if it could be done easily (which I'm guessing it could not).