From: shevegen@... Date: 2019-01-03T15:26:41+00:00 Subject: [ruby-core:90873] [Ruby trunk Bug#14541] Class variables have broken semantics, let's fix them Issue #14541 has been updated by shevegen (Robert A. Heiler). > Inheritance of @@class variables is precisely what makes them useful, > since they're truly one single global value. You can always find pros/cons. Some will find a feature useful, others will not. My personal opinion, for example, is that I find @@vars largely unnecessary, so I don't use them in my own code; another smaller reason is that I find the two @ not so elegant. Others may have another opinion and already expressed so, e. g. at a developer meeting some months ago - I think that will very often be the case where people have different opinions. And ultimately matz decides and he already decided and explained here, and elsewhere. :) I can achieve "inheritance" via @instance variables and specifying access to it via custom code too (well, methods), but I think it is not a very strong argument to refer to it as what makes @@class variables that useful to begin with, because ruby is so dynamic that inheritance and access-specifiers aren't a very strict concept. For example, we can obtain and change variables at "runtime" at any moment in ruby as-is, e. g. instance_variable_get/set and so forth. Ruby is ultimately a "tool-box" of code and it has another concept for both OOP but also how to interface with it (from the human side), compared to, say, C++ and Java. > And in addition to what matz points out rubocop has the > Cop::Style::ClassVars warning. Rubocop, and neither the style guide, are not designing ruby though. It's great that rubocop exists; and it is great that it can be of help keeping code bases sane. But that is only one part - the other part is the design of ruby as such in itself. The ruby parser can also be thought of some kind of "style guide", e. g. what it enforces, or what warnings it will show, and so forth. It would be nice if we could customize it a bit more in general when it comes to warnings/notifications, a bit like rubocop - but keeping this simple, too. Rubocop can become a bit complicated if you look at all the different cop-rules that projects can use. I like simplicity too. > As someone who manages a 10 year old, 250,000 line long ruby > codebase, please don't remove them or change the semantics. I have been using ruby for a very, very, very long time but ... how can you manage to write 250.000 lines of ruby code? I assume that must have been written by more than one person. > The class variables we have now in our project have often been > around for 10 years and while in some purist sense they are all > technical debt, they're not doing any harm -- forcing me to have > a flag day to fix them all would be a large waste of my time > (some of them have been fixed as they've been found to be > problematic and/or the code in question simply got cleaned > up as part of a larger refactoring pass). I think you need not worry - matz already said that the incompatibility issue is a real one, so it is super-unlikely that class variables will be changed; most definitely not for ruby 3.0 but I think probably also not at a later time. So a lot of this discussion here is mostly a purely hypothetical one. There have been other ruby users who expressed that they use @@class vars and that they also like them. There is no "wrong" use of code as such per se; people will use features and functionality when it is made available. I think very large code bases are always problematic, not just in regards to ruby alone but in general. Matz also mentioned several times in presentations that he wants to avoid changes such as from ruby 1.8.x to (ultimately) ruby 2.0 as that was a pain point for quite a few people. So I think when discussing it, we should mostly refer to this as a purely theoretical discussion. I would also like to propose to eventually close this particular issue here eventually when eregon is ok with it - ideally before ruby 3.0 is released, simply to keep the amount of issues a bit smaller. (We could always have another discussion in the future after 3.0 but I think the chances for change here are quite low, and about 0% for ruby 3.0 anyway; possibly even 0% at a later time but who knows.) By the way I also agree that it is not something that is hugely important from a practical point of view - people who like class variables can use them; those who don't like them can avoid them. Ultimately project owners can specify what they need/accept in code bases that they maintain. I have a long list of ruby code I would reject - others may probably feel similar in a different way about ruby code they find acceptable and code they don't. :) (I actually found that one of the biggest problem is the lack of documentation and comments - some people never write any comments and barely any documentation, and it is very often that code written by them is either brilliant or totally awful. And in both cases comments/documentation would help OTHER people immensely, if it is up-to-date and of high quality.) ---------------------------------------- Bug #14541: Class variables have broken semantics, let's fix them https://bugs.ruby-lang.org/issues/14541#change-76060 * Author: Eregon (Benoit Daloze) * Status: Open * Priority: Normal * Assignee: * Target version: * ruby -v: ruby 2.6.0dev (2018-01-29 trunk 62091) [x86_64-linux] * Backport: 2.3: UNKNOWN, 2.4: UNKNOWN, 2.5: UNKNOWN ---------------------------------------- Class variables have the weird semantics of being tied to the class hierarchy and being inherited between classes. I think this is counter-intuitive, dangerous and basically nobody expects this behavior. To illustrate that, we can break the tmpdir stdlib by defining a top-level class variable: $ ruby -rtmpdir -e '$SAFE=1; @@systmpdir=42; p Dir.mktmpdir {}' -e:1: warning: class variable access from toplevel Traceback (most recent call last): 3: from -e:1:in `
' 2: from /home/eregon/prefix/ruby-trunk/lib/ruby/2.6.0/tmpdir.rb:86:in `mktmpdir' 1: from /home/eregon/prefix/ruby-trunk/lib/ruby/2.6.0/tmpdir.rb:125:in `create' /home/eregon/prefix/ruby-trunk/lib/ruby/2.6.0/tmpdir.rb:125:in `join': no implicit conversion of Integer into String (TypeError) Or even simpler in RubyGems: $ ruby -e '@@all=42; p Gem.ruby_version' -e:1: warning: class variable access from toplevel Traceback (most recent call last): 3: from -e:1:in `
' 2: from /home/eregon/prefix/ruby-trunk/lib/ruby/2.6.0/rubygems.rb:984:in `ruby_version' 1: from /home/eregon/prefix/ruby-trunk/lib/ruby/2.6.0/rubygems/version.rb:199:in `new' /home/eregon/prefix/ruby-trunk/lib/ruby/2.6.0/rubygems/version.rb:199:in `[]': no implicit conversion of String into Integer (TypeError) So defining a class variable on Object removes class variables in all classes inheriting from Object. Maybe @@systmpdir is not so prone to conflict, but how about @@identifier, @@context, @@locales, @@sequence, @@all, etc which are class variables of the standard library? Moreover, class variables are extremely complex to implement correctly and very difficult to optimize due to the complex semantics. In fact, none of JRuby, TruffleRuby, Rubinius and MRuby implement the "setting a class var on Object removes class vars in subclasses". It seems all implementations but MRI print :foo twice here (instead of :foo :toplevel for MRI): ~~~ ruby class Foo @@cvar = :foo def self.read @@cvar end end p Foo.read @@cvar = :toplevel p Foo.read ~~~ Is there any library actually taking advantage that class variables are inherited between classes? I would guess not or very few. Therefore, I propose to give class variable intuitive semantics: no inheritance, they behave just like variables of that specific class, much like class-level instance variables (but separate for compatibility). Another option is to remove them completely, but that's likely too hard for compatibility. Thoughts? -- https://bugs.ruby-lang.org/ Unsubscribe: