How about... and please sit down for this one... How about adding an "abstract" keyword for methods and refusing to compile with an abstract method from a parent class not being implemented in a non abstract child class?
Ruby comes from the Alan Kay line of OO thinking, in which method dispatch is late bound via an object’s eigenclass. Aside from those burned into the standard library as native code, methods are defined at runtime. Bit of a chicken/egg problem for any compiler hoping to enforce dispatch invariants.
The C++/Java form of OOP is so conceptually divergent from Smalltalk-style OOP that it may be less confusing if we renamed it.
It's Ruby, there's no compilation. It's also common to not load all files of an application's codebase at startup, in particular this is the default setting for Rails in development mode.
There is. CRuby code gets compiled to ISeq (VM bytecode) upon `load` / `require` / `eval`. JRuby does similar things (IIRC using InvokeDynamic).
The difference (and that does not even need to involve Rails) is that these three - and thus compilation - happen at runtime, anytime, always.
The fact that a typical "main" file follows the "require first, then define modules and classes, then execute" creates a mental illusion of order; the fact that generally dependencies also follow this propagates that illusion across the board.
Therefore at the ruby source level you can only have runtime checks (and failure via exceptions), unless you bring in another static analysis tool such as Sorbet or RBS+Steep, which is an actual solution to enforce interface contracts without risking blowing up an app at runtime. That stays true even if there were a hypothetical "abstract" Ruby-level keyword. The only other solution without such tools is to have perfect (runtime) coverage via tests.
Note that mruby takes a fundamentally different approach here, and there's no require nor load.
Essentially this means that it makes LOAD_PATH lookup faster + intercepts iseq compilation to store to cache on a miss and return a cached version on a hit.
That does not change the load order.
> But that doesn't change anything about OP's suggestion, it's still impossible to know if an interface will ever be implemented.
Again correct, as even with `bootsnap precompile` it would only result in earlier ISeq generation (it is essentially cache priming instead of doing it "lazily" on demand), not the time at which these ISeq get loaded and effective, so even then an `abstract` keyword would be ineffective (or rather, equivalent to raising NotImplementedError, i.e syntactic sugar)
The only way to do it outside of runtime is with type information, ensuring that only certain types respecting a contract reach the point where the expected interface is made use of. I'm using RBS+Steep, not just for that but this is one of the main reasons and I can attest it does work very well in practice: when the types don't respect the contract it blows up as expected, at which stage raising exceptions is either redundant or a defensive coding mechanism (and I've actually had Steep yell at me because some clauses were unreachable due to types ensuring the contract)
This isn’t how Ruby works. Classes can be modified at any point in the program’s runtime. Ie, methods might be added at instantiation or in response to some other event. And even for a boring static implementation, classes may be defined over multiple files. When exactly should you raise the error?