I'm very much enjoying thinking about this and considering what might be the most Julian approach
> I thought one problem with not having "proper" inheritance is that this doesn't really help? Even small variations across AbstractAtoms types require a full implementation? Actually, I don't think there's any substantial difference with Julia's inheritance model. Because if the concrete types share field names, methods can still access those fields even when they only know they have an abstract type. And if the fields are different, then any inheritance model would require a new implementation. The only difference is that in the case of same-name fields, the declarations must be repeated for each concrete type, which a fairly mild tax given that a macro can do it easily. > This was my initial thought as well. The problem with that is that the Atom and Calculator, NeighbourList and Preconditioner objects are "linked": when Atoms is updated, then a "message" is sent to Calculator, Preconditioner and NeighbourList so that they can update themselves also. The Calculator, NeighbourList and Preconditioner objects in fact store additional data that is dependent on the data stored in Atoms. > Because the same issues arise for NeighbourLists, for Preconditioners and potentially other objects that will be linked to Atoms type objects. If you don't even know which objects will be linked when get_forces is first called, then I don't think you have any choice but to dispatch twice. First on the type of atoms, and second on the type of the internal variables, just as you did in your Option 1 code. But I think you can avoid the clunkiness by being a bit more fastidious about defining an interface for AbstractCalculator, AbstractPreConditioner, etc... and use those interface methods instead of just forwarding everything to a more specialized version of get_forces. That is, you might have something like getparam1(::AbstractCalculator) and then get_forces can directly send it a.calc without worrying exactly what type it is. But if the other object implementations are so different that no interface is possible, then yeah, I think you just have to write a different method for each possible combination of types.
