Remix.run Logo
inopinatus 3 hours ago

Any type system for Ruby objects that isn’t based on message/method response signature (i.e eigenclass), but instead relies on module ancestors (or worse, class), is fundamentally misaligned with the architecture of the language.

A remarkably high proportion of folks that self-identify as Ruby aficionados will make this error.

I’m not even talking about respond_to? / method_missing tricks. If an object prepends a module to its singleton to become a proxy for something else, or a library offers refinements (which are lexical) so its clients may declaratively align method expectations, or (bad style, looking at you Rails, but nevertheless) just evals whatever method definitions it likes after messing with the three implicit contexts, then it should still pass.

Leaning on class and mixin is just one of the ways in which Ruby object anatomy evolves, and although that’s a familiar default to many, there are other styles in common use, especially in framework/library code. Any app relying on such a framework may either not pass, or may silently bypass, such type checking. And I foresee a myriad of edge cases if one slings around closures as a habit (why, yes I do).

Symbolic message passing is the basis of object collaboration in Smalltalkish OO, and in Ruby class/mixin is merely one of the ways to get there. The conceptual gap means that what you get from oversimplification isn’t just a half-baked type system, it also becomes an incomplete straitjacket for style.

Edit to add: after reviewing the internals of this library, note that for a dash of irony, it is indeed prepending modules to class singletons to redefine methods with proxy wrappers. That is to say, it could not type-check itself.