I suggest you read the translation of the second part of Ruby’s author’s report, Yukihiro Matsumoto, on RubyConf 2010, published in his blog in Japanese. The first part is a brief historical overview of previous RubyConf conferences and is of no interest to the publication.
For assistance in translating from Japanese, many thanks to Vladimir Sadovnikov .Future Ruby: Ruby 2.0
This time I will introduce several new features that will be in Ruby:
- Types ( traits ) 1
- Combinations of methods
- Named Arguments
- Namespaces
I am quite sure that so far this has not been the case. The difference is that:
- These functions are not just ideas that can one day be realized. So it was necessary to do from the very beginning.
- The corresponding code is already committed to the trunk.
1 Comment Lane: for non-trivial technical terms, I will give an English name in brackets and a link to Wikipedia.
Types
Definition of types:
Description is a collection of methods used as “a simple conceptual model for structuring object-oriented programs.”
from wikipedia (eng.)
It looks almost the same as the module. In essence, the new language element - type - is a subspecies of the module.
')
However, in order to present the functionality of a module in a different way, types implement impurities (mixins) using a method other than including (other modules — here and hereinafter in square brackets). trans.]. Since this method is more flexible, the possibility of including modules has been removed completely.
Problems with the inclusion are:
- Name conflicts are not detected.
- Modules start to function incorrectly if their order changes.
- After wrapping a method with another module, you cannot access the original method.
When inclusion is used to simulate multiple inheritance, some difficult to predict situations arise.
One of them is that the inclusion of a module containing a method that is already defined in the current context may have completely different goals and consequences: [intentional] overlapping an already defined method, random coincidence (name conflict), or both are actually required method, and their names must be unique.
Another problem is that the order of inheritance of modules and, accordingly, the sequence of finding methods when matching names can often be counterintuitive.
module American attr_accessor :address end module Japanese attr_accessor :address end class JapaneseAmerican include American include Japanese end JapaneseAmerican.new.address
In this example, the address attribute method is contained in both the American and Japanese modules, overlapping at the request of the developer or accidentally due to their lack of knowledge of the language. As a result, the methods will be called simply in the sequence corresponding to the order of inheritance.
A “well-trained” Ruby programmer [Rubyist], looking at this code, will of course understand that the address method from the Japanese module will be called.
Currently, Ruby [MRI 1.9] uses the paradigm “the module should not be inherited twice, even if the parent class already includes this module” (author's note: it looks like MacRuby did the same. In 1.9 there was an attempt to change this, but for YARV work requires that the module does not appear among the ancestors twice.). So if you include a module in a class whose parent class already includes this module, it will not appear among ancestors where you expect it.
In addition, since the inclusion of modules adds to the ancestors of the current class all the modules included in this module, and after that they can no longer be included in a place specific to this class. In the end, the inclusion of modules makes the order of inheritance much more confusing.
These problems (in part) are solved by the method of mixing (mix).
Why mixing? Because:
- Class methods are defined in the current module / include module.
- When enabled, the module does not become an ancestor.
- The matching of method names throws an exception.
- If the inclusion of a module with matching method names is still necessary, they can be renamed.
- Constants can be included or not included (by default they are not included).
Thus, mixing is implemented as follows:
- Attempting to add a duplicate method will cause an error, because the conflict cannot be automatically resolved.
- Method names can be changed by specifying this explicitly.
- Modules are still included in the inheritance chain in a strange way. (This problem has not been solved, good luck. * Grin *)
For example, in this code there are methods with the same name, and therefore mixing will not work:
module American attr_accessor :address end module Japanese attr_accessor :address end class JapaneseAmerican mix American mix Japanese
In order for the mix to work, you need to explicitly resolve name conflicts:
class JapaneseAmerican mix American, :address => :us_address mix Japanese, :address => :jp_address end
Now the methods that were called the same got different names.
You may ask why, instead of complicating the language, not to add the ability to pass the parameters of the inclusion to the include method? Because the shorter name of the method means that it is preferable to use. (comment re: that is why all introspective methods that can potentially make code difficult to understand have detailed names like “instance_variable_defined?”).
Nakada [probably, Nobuyoshi Nakada, one of the Ruby core developers] has already started developing the types needed to implement the mix method at the time of announcing this feature on RubyKaigi [Japanese Ruby Conference], so the patch is ready.
However, various presentations do not address the following problems, which still need to be resolved:
- [In this place there were two points that I could not translate. Excuse me.]
- There are no ways to resolve conflicts among class member variables. Since the member variables of the child classes are added imperceptibly to the parent, when implementing a class that mixes with the module, you will have to take into account undesirable names for the member variables and avoid them. There is a patch for 1.9, which makes member variables like @_foo or @__foo closed, but in the current situation it is difficult to understand exactly how this should be implemented, or it may be necessary to abandon this function altogether. This problem will have to be given more attention after the introduction of mixing.
Combinations of methods
On RubyKaigi, mixing was part of a story about method combinations, but later it turned out that one of the parts of the story, related to the ability to “wrap” the method with calls to other methods, turned out to be the most non-trivial, and I singled out it separately.
The proposal is to introduce the prepend method. I already hear the question "Do we already have include and mix, what, is another method now needed?", And I believe that it is really necessary.
This method allows you to add another method "before" the current class to add any functions.
module Foo def foo p :before super p :after end end class Bar def foo p :foo end prepend Foo end Bar.new.foo
If the foo method is defined in the Foo module, and it is also defined in the current class (or module), then prepend wraps the foo method call of the current class using the module's foo method.
The idea of the prepend method comes from Yehuda Katz, who is also a Rails developer; he thinks it would be great to replace them with alias_method_chain in Rails, and I agree with him.
There is no concrete implementation of this possibility yet, but the easiest way to add the T_ICLASS object to the inheritance chain. From this it is possible to begin.
Named Arguments
People tend to forget the purpose of the arguments, and especially the optional ones. For example, the public_instance_methods method takes an optional argument, which means “if false is returned, then return the methods and parent class too,” or vice versa, I always forget it. (Actually, true.)
For example,
aClass.public_instance_methods (include_super: false)
So much easier to remember.
In Ruby 1.9, named arguments are simply the last argument with the hash as the default value, and the grammar extension with which this hash is initialized. (comment perev .: few people know that in 1.9 you can make a method like
def a(opts={}); end
and call it as
a(go: true, what: "string")
.
New feature 2.0 is a simple way to specify such an entry when defining a method. For example:
Call:
1.step (by: 2, to: 20) do |i| pi end
Called method:
def step (by: step, to: limit) ... end
After the [community] has accepted the call with the hash, the addition of the named arguments will, in effect, be a slight change.
Namespaces
If you are interested in technical details, you should take a look at
the Shugo Maeda
presentation , which was also on RubyConf.
Ruby classes are public. This means that at any time you can add a method to an existing class. This class change technique is called “monkey patching”.
It seems to me that this term was generated by the chain “guerilla patching” [guerilla - partisan] -> “patching gorilla” -> “monkey patching”. Of course, if in a language like Ruby, changing classes is easy, then I’m sure that this feature should be provided. That is why the DHH report [David Heinemeier Hanson, author of Rails] "The future without monkey patching" I call the "Future without freedom of change." [here was a quote from Mel Gibson, but my brain parser did not master it].
On the one hand, freedom is good, but on the other, it can have too much influence. Of course, you can override the addition for integers so that 1 + 2 is equal to 42, but most of the programs will stop working due to side effects.
The problem is that all such changes act globally, on the entire program. By placing changes in a closed scope, you can get a convenient and secure method of “free changes” [freedom of patching].
Such a “scope” could be the previously proposed Selector Namespace Classbox; This time, the Selector Namespace, implemented by Maeda’s colleague, can be called a “Refinement”.
For example, here is the program:
class Integer def / (other) return quo (other) end end p 1 / 2
Such a redefinition of the division operator (/) in order to return the result in the form of a rational fraction (note: in this case, an object of the Rational class is returned) is part of the standard mathn library. However, code that expects integer division to also result in an integer may fail due to such a change.
In this study, it is proposed to introduce clarifications (the name may change). A grammar might look like this:
module MathN refine Integer do def / (other) return quo (other) end end p 1 / 2
Refinement is used as part of the module. Module. This is a great achievement.
Within the module, existing classes can be refined, and such changes will be visible only within the namespace. Thus, inside the module MathN 1/2 returns Rational (1/2), and outside the module the usual quotient.
The difference between the clarifications and the classbox is that, despite the fact that the lexical scope has already ended, the clarification is still in action; for the same reason, many programming languages use dynamic scope. Of course, the clarifications should work imperatively, not lexically.
The namespace is a module using the using method:
module Rationalize using MathN p 1 / 2
In the Rationalize module, the update defined in MathN is also available.
In addition, if a method is meaningless to define as a class method, then it can be defined inside another method. Within the method, this will work in the same way as the clarification: this way the method can be made completely closed. (Note: In Ruby 1.9, the method defined in this way becomes a member method of the class, that is, nesting has no meaning.)
class Foo def foo def bar ... end bar
This change that Maeda is working on is quite large and complex; however, the changes are quickly integrated into the main trunk [trunk], but for now, you can applaud your Maeda colleague for this great patch.
PS This translation was far from simple, and I know that many phrases will drive any cryptolinguist crazy, but my conscience did not allow me to pull out from the text only those arbitrary parts that were the easiest to translate. I hope that most of the interesting things will be clear from the few surviving comments and the source code. And I promise that the translation of the next part will be much clearer.