Ruby doesn't have interfaces, but how tell other programmers what need to include current module in class, like instance variables, methods, constants, etc?
3 Answers
There's no way of formally defining what's required, it's up to you to document it clearly. The reason for this is Ruby is very dynamic by design so static tests won't work, the problems they detect might be rectified by the time the code is actually executed. Likewise, something that might seem correct could be broken later on by some other code.
C++, Java, and even Objective-C and Swift can do compile-time checking to enforce these things. Once a class is defined it cannot be undefined. Once a method is created it can't be removed. This is not the case in a Smalltalk-derived language like Ruby.
Ruby has no ability to test these things up-front since what the program actually does can change radically from the time the code is loaded and parsed, and when it's actually executed.
If you have a particularly complicated footprint you might want to write a method for testing it that can be exercised to verify that everything's working correctly. That can be called by the programmer whenever they think they're ready.
The only way to verify that your Ruby code is running correctly is to run it. No amount of static analysis will ever come close to that.
3 Comments
There are some existing mixins, classes, and methods in the Ruby core library that have the exact same problem, e.g. Enumerable, Comparable, Range, Hash, Array#uniq: they require certain behavior from other objects in order to work. Some examples are:
-
The class must provide a method
each, which yields successive members of the collection. IfEnumerable#max,#min, or#sortis used, the objects in the collection must also implement a meaningful<=>operator […] -
The class must define the
<=>operator, which compares the receiver against another object, returning -1, 0, or +1 depending on whether the receiver is less than, equal to, or greater than the other object. If the other object is not comparable then the<=>operator should return nil. -
Ranges can be constructed using any objects that can be compared using the
<=>operator. Methods that treat the range as a sequence (#eachand methods inherited fromEnumerable) expect the begin object to implement asuccmethod to return the next object in sequence. Thestepandinclude?methods require the begin object to implementsuccor to be numeric. Hash:A user-defined class may be used as a hash key if the
hashandeql?methods are overridden to provide meaningful behavior.And in order to define what "meaningful behavior" means, the documentation of
Hashfurther links to the documentation ofObject#hashandObject#eql?:-
[…] This function must have the property that
a.eql?(b)impliesa.hash == b.hash. […] -
[…] The
eql?method returns true if obj and other refer to the same hash key. […]
So, as you can see, your question is a quite common one, and the answer is: documentation.
Comments
This is a subjective question, so with that in mind here is my opinion.
Maybe you could create a base class that your objects inherit from.
for example:
class BaseA
def say(msg)
raise NotImplementedError
end
end
class A < BaseA
def say(msg)
puts "saying #{msg}"
end
end
Even though this isn't a real interface you can "pretend" it's one and have all the classes that need BaseA's methods and then override them with the real behavior. Then I suppose developers could just look at the base class to see what methods need to implemented.