-
-
Notifications
You must be signed in to change notification settings - Fork 55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[fixes #22] Introduce FastArray, a frozen Array with fast inclusion lookup #29
Conversation
b6fc172
to
a1af995
Compare
lib/rubocop/ast/tuple.rb
Outdated
module AST | ||
# A frozen Array/Set | ||
# | ||
class Tuple < ::Array |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Subclassing core classes is not recommended.
https://words.steveklabnik.com/beware-subclassing-ruby-core-classes has the quote
If you don’t know exactly how these classes operate at the C level, you’re gonna have a bad time.
And I remember Rails running into problems when making ActionController::Parameters
no longer inherit from Hash
: rails/rails#14384 and rails/rails#20868
Could we use composition instead, and still get the same benefits?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd claim I know almost exactly how these classes operate at the C level 😅
It's true it can be tricky to inherit from base classes. Composition also can be tricky (as your rails PR show). Here though, we're talking about a completely immutable object that acts like an array in almost all aspects, just with an optimized include?
and :===
that's an alias. The idea is to use it instead of frozen arrays of strings/symbols.
FWIW, HashWithIndifferentAccess
was an abomination 😆, and ActiveSupport::SafeBuffer < String
and that's quite a bit trickier as it's meant to interact with other String
s and keeping track of safety is really important. Here we typically don't really care if if make an operation and we get back an Array
or a Tuple
, because we don't really make operations on them (or we wrap them in a Tuple
anyways).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've seen the concept of Tuple in Rinda, but I'm not familiar with it 😅
https://ruby-doc.org/stdlib-2.7.1/libdoc/rinda/rdoc/Rinda/Tuple.html
Array, Hash and Set do not inherit from each other. I'm wondering if Tuple is a concept related to is_a
of Array or not. The following is an example of Rinda:
% ruby -rrinda/rinda -e 'p Rinda::Tuple.ancestors'
[Rinda::Tuple, Object, Kernel, BasicObject]
Tuple may be a different concept than Array.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How come we cannot use a Set
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@koic To be honest, I'm not completely satisfied with Tuple
, a more descriptive name would be more like DeeplyFrozenIndexedArray
. And that is_a? Array
...
@bquorning That was the first thing I tried, but Set
are sorely lacking and the first two points created annoying issues. The choice was to have potential incompatibilities and have to worry about which "lists" were Sets (most) or Arrays (some that didn't need indexing and used join
, say).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. Refinements are a fine (no pun intended) feature that might fit well here. Also have never tested the performance implications of them (but use them actively at work.) 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, FastArray
, ConstArray
, SetArray
(as in "fixed" and as "set-like" :-) ),IndexedArray
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, is there a naming preference? Tuple
/ FastArray
/ IndexedArray
/ ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, arrays are supposed to be indexed by default, so I don't like IndexedArray
. Seems to me that FastArray
is the best out of the proposed names.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replaced with FastArray
& rebased
e5117a8
to
9fbfca5
Compare
53dcf85
to
b03f186
Compare
lib/rubocop/ast/fast_array.rb
Outdated
end | ||
end | ||
|
||
def FastArray(list) # rubocop:disable Naming/MethodName |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not too crazy about us defining constants outside of the RuboCop
namespace. Is it necessary? As far as I can see, we only use FastArray
inside the RuboCop::AST
namespace.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still think it's going to be better to define some refinement for use within RuboCop's code base and just use a conversion method like .to_fa
everywhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The constant FastArray
is defined within RuboCop
, but I also added a global method FastArray
, but it isn't strictly necessary, I could use FastArray[ary]
. I can try with refinements too. Is to_fast_array
ok? I fear to_fa
would be quite cryptic
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my personal opinion, if we could get away with defining the FastArray
method inside RuboCop
scope, that would be optimal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my personal opinion, if we could get away with defining the
FastArray
method insideRuboCop
scope, that would be optimal.
We could, but we'll need an explicit extend FastArray::Method
or similar (not needed when inheriting from a class that already extend it, like Cop::Base
). Let me try that first.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Force pushed without a global function, and on the rubocop side too.
I'm fine with this approach, but if the others have concerns I won't push for it. We can always revisit this down the road. |
Cool. @bquorning did you have a chance to look at the approach without global function? |
Sorry, I forgot to check before now. I really like the explicit |
I have the following @bquorning opinion again.
rubocop/rubocop#8133 (comment) Performance improvements are welcome, however I love the ease and readability of Ruby itself. |
I just want to mention that one issue is that these constants are public. Most are arrays, some are sets, which is confusing and error prone. It's less of an issue to change them now (pre 1.0) than after. |
I'm not an avid contributor but I'd like to give an idea here: Instead of enforcing the FastArray inline, we can do it via metaprogramming just patching the node_classes = RuboCop::AST.constants.grep(/Node$/).map(&RuboCop::AST.method(:const_get))
node_classes.each do |clazz|
clazz.constants.each do |const|
value = clazz.const_get(const)
next unless value.grep(Symbol) != value # all should be symbols
clazz.remove_const(const)
clazz.const_set(const, FastArray(value))
end
end We can keep the implementation and add some good documentation in the to patch it but we don't need to have the code explicit in all the places. |
I understood your point. Very nice idea on #57, I checked now :) @marcandre Have you shared the issue with the ruby-core team? because as far as I can see here, this looks like bring benefit to any ruby array lookup right? maybe someone has some ideas about how to make it better for the array in general or introduce the FastArray in the core library as we have BasicObject that is lighter than Object. |
Not really, no. Arrays may have duplicated elements, are not meant for lookups, but are good for fast creation / insertions / accessing |
Creates an Array-like class with
===
andinclude?
from Set.