What is the correct way to extend native methods?

Hello!

suppose I want to modify Array.slice() (and consequentially, Array[]) in
order to accept a RegExp as argument, and return values matching the
regular expression. I tried to solve this problem in this way:

class Array

def slice_with_regexp(*args)
puts args
return self.select{ |val| val.to_s =~ args[0] } if args.size == 1
and args[0].is_a?(RegExp)
end

alias_method :slice_without_regexp, :slice
alias_method :slice, :slice_with_regexp

end

arr = %w[a b c]
puts arr.slice(/a/)

but executing this script I get this error:

(?-mix:a)
scripts.rb:8:in slice_with_regexp': uninitialized constant RegExp (NameError) from scripts.rb:17:in

Surely I did something wrong, but the very question is: what is the
correct way to get the desired effect (that is:)

arr = %w[a b c]
puts arr[/a/]
=> [“a”]
puts arr.slice(/a/)
=> [“a”]

?

return self.select{ |val| val.to_s =~ args[0] } if args.size == 1
and args[0].is_a?(RegExp)

Try Regexp, not RegExp

http://ruby-doc.org/core/classes/Regexp.html

James H.on wrote:

return self.select{ |val| val.to_s =~ args[0] } if args.size == 1
and args[0].is_a?(RegExp)

Try Regexp, not RegExp

class Regexp - RDoc Documentation

LOL that’s what I obtain when I’m still not asleep at 3.00 am :stuck_out_tongue:

But now it’s morning, and I have still a problem; please consider this:

class Array

def slice_with_regexp(*args)
return self.select{ |val| val =~ args[0] } if args.size == 1 and
args[0].is_a?(Regexp)
slice_without_regexp(*args)
end

alias_method :slice_without_regexp, :slice
alias_method :slice_without_regexp, :[]
alias_method :slice, :slice_with_regexp

end

arr = %w[a b c ab]
puts arr.slice(/a/).inspect # => [“a”, “ab”] slice with Regexp works;
correct
puts arr.slice(1).inspect # => “b” original slice still works; correct
puts arr[/a/].inspect # => stack level too deep ???

why??

Hi –

On Sun, 1 Aug 2010, Brian C. wrote:

If I change it to

alias_method :slice_without_regexp, :slice
alias_method :slice, :slice_with_regexp
alias_method :[], :slice_with_regexp

then that gives a stack too deep error, but on the initial slice call.

I’m afraid I don’t know the solution though. I suspect slice is already
an alias for [], or vice versa.

They are indeed the same method:

 rb_define_method(rb_cArray, "[]", rb_ary_aref, -1);
 rb_define_method(rb_cArray, "slice", rb_ary_aref, -1);

David


David A. Black, Senior Developer, Cyrus Innovation Inc.

The Ruby training with Black/Brown/McAnally
Compleat Philadelphia, PA, October 1-2, 2010
Rubyist http://www.compleatrubyist.com

Maurizio De Santis wrote:

why??

What ruby are you using? With ruby 1.8.7 (2010-01-10 patchlevel 249)
[x86_64-linux] and your posted code, I get the following output:

[“a”, “ab”]
“b”
ert.rb:17:in `[]’: can’t convert Regexp into Integer (TypeError)
from ert.rb:17

If I change it to

alias_method :slice_without_regexp, :slice
alias_method :slice, :slice_with_regexp
alias_method :[], :slice_with_regexp

then that gives a stack too deep error, but on the initial slice call.

I’m afraid I don’t know the solution though. I suspect slice is already
an alias for [], or vice versa.

Brian C. wrote:

What ruby are you using?

I’m using ruby 1.9.2dev (2010-07-11 revision 28613) [x86_64-linux] ,
maybe this is the reason…

David A. Black wrote:

They are indeed the same method:

 rb_define_method(rb_cArray, "[]", rb_ary_aref, -1);
 rb_define_method(rb_cArray, "slice", rb_ary_aref, -1);

uhm… but if they are the same method, why aliasing the one, the other
does not become aliased? do I have to alias both methods?? and, if yes,
how??? :slight_smile: I’ve tried, but with no success…

David A. Black wrote:

It’s one underlying method (C function) with two Ruby names. If you
redefine one of the Ruby names you break the binding between that name
and the underlying method, and if you add an alias then there’s a third
name. Each binding is separate; they don’t automatically change
together.

uhhh I think I’ve understood: In order to do what I want to do, I should
implement something like this:

class Array

def slice_with_regexp(*args)
return self.select{ |val| val =~ args[0] } if args.size == 1 and
args[0].is_a?(Regexp)
# code that executes C function called by [] and slice
end

alias_method :slice_with_regexp, :slice
alias_method :slice_with_regexp, :[]

end

that is stupid 'cause breaks Array.slice performances.

Right?

Thanks a lot David!

On Jul 31, 9:19 pm, Maurizio De Santis [email protected]
wrote:

Hello!

suppose I want to modify Array.slice() (and consequentially, Array[]) in
order to accept a RegExp as argument, and return values matching the
regular expression. I tried to solve this problem in this way:

DON’T!

It’s almost always a bad idea to override a built-in core method.
There are likely to be unintended effects that will break other code.
The best approach is to define a new method and use that.

Hi –

On Sun, 1 Aug 2010, Maurizio De Santis wrote:

implement something like this:
alias_method :slice_with_regexp, :slice
alias_method :slice_with_regexp, :[]

end

that is stupid 'cause breaks Array.slice performances.

Right?

Thanks a lot David!

I should add that my full advice is to avoid overriding slice and/or []
globally. A better way to alter core functionality is on a per-object
basis:

module ExtendedSlicer
def slice(*args)
arg0 = args[0]
case arg0
when Regexp
grep(arg0)
else
super
end
end
end

a = %w{ one two three four }.extend(ExtendedSlicer)
a.slice(0,2) # [“one”, “two”]
a.slice(/o/) # [“one”, “two”, “four”]

The main advantage here is that it’s a better fit: you need an object to
behave a certain way, and you teach that object to behave that way.
Another advantage is that it makes you work a little harder, and
therefore helps you evaluate more carefully whether or not you need to
perform the modification at all.

David


David A. Black, Senior Developer, Cyrus Innovation Inc.

The Ruby training with Black/Brown/McAnally
Compleat Philadelphia, PA, October 1-2, 2010
Rubyist http://www.compleatrubyist.com

Hi –

On Sun, 1 Aug 2010, Maurizio De Santis wrote:

 rb_define_method(rb_cArray, "slice", rb_ary_aref, -1);

uhm… but if they are the same method, why aliasing the one, the other
does not become aliased? do I have to alias both methods?? and, if yes,
how??? :slight_smile: I’ve tried, but with no success…

It’s one underlying method (C function) with two Ruby names. If you
redefine one of the Ruby names you break the binding between that name
and the underlying method, and if you add an alias then there’s a third
name. Each binding is separate; they don’t automatically change
together.

David


David A. Black, Senior Developer, Cyrus Innovation Inc.

The Ruby training with Black/Brown/McAnally
Compleat Philadelphia, PA, October 1-2, 2010
Rubyist http://www.compleatrubyist.com

David A. Black wrote:

module ExtendedSlicer
def slice(*args)
arg0 = args[0]
case arg0
when Regexp
grep(arg0)
else
super
end
end
end

a = %w{ one two three four }.extend(ExtendedSlicer)
a.slice(0,2) # [“one”, “two”]
a.slice(/o/) # [“one”, “two”, “four”]

The main advantage here is that it’s a better fit: you need an object to
behave a certain way, and you teach that object to behave that way.
Another advantage is that it makes you work a little harder, and
therefore helps you evaluate more carefully whether or not you need to
perform the modification at all.

Ok, I’ve learned more from these posts than in two months of
experiments.

Said that “is a bad idea to override a built-in method”, I’m trying to
think in this way now: “what happens if I…”, so I tried this:

class MyArray < Array; end

module ExtendedSlicer
def slice(*args)
arg0 = args[0]
case arg0
when Regexp
grep(arg0)
else
super
end
end
end

class MyArray
include ExtendedSlicer
end

arr = MyArray.new %w{a b c ab}
puts arr.slice(/a/).inspect => it writes [“a”, “ab”] ; correct

instead, if I execute:

module ExtendedSlicer
def slice(*args)
arg0 = args[0]
case arg0
when Regexp
grep(arg0)
else
super
end
end
end

class Array
include ExtendedSlicer
end

arr = Array.new %w{a b c ab}
puts arr.slice(/a/).inspect # => can’t convert Regexp into Integer
(TypeError)

Why? Maybe it is forbidden to include a Module in a built-in class, for
security reasons, or… boh! :slight_smile: Can you explain that?

Hello,

Why? Maybe it is forbidden to include a Module in a built-in class, for
security reasons, or… boh! :slight_smile: Can you explain that?

I think it has to do with inheritance when looking for methods.

First, ruby look in the class itself to see if there is a method named
‘slice’. When you try to extend Array, it find Array’s original
‘slice’ and so does not need to look at the module (and never find
your redefinition).

If no method of that name is found in the class (like MyArray), it
looks in the modules (if any) included in your class (and find one
‘slice’ in ExtendedSlicer) before looking at the ancestor class (which
is Array).

If I’m not mistaken, it should explain the observed behavior.

All that (and more ! :o) is explained in David’s book (The
Well-Grounded Rubyist) in a crystal-clear manner around p100 of my
version.

Cheers,

Clear as spring wat - better do not mention Spring! - bright as a
flawless Ruby! :smiley: Thanks to everyone! And forgive me for the joke :stuck_out_tongue:

On Sun, Aug 1, 2010 at 12:07 PM, Maurizio De Santis
[email protected] wrote:

Said that “is a bad idea to override a built-in method”, I’m trying to
think in this way now: “what happens if I…”, so I tried this:

end

Why? Maybe it is forbidden to include a Module in a built-in class, for
security reasons, or… boh! :slight_smile: Can you explain that?

It’s because included modules are inserted after the class in the
method lookup chain. They act more like a superclass in that regard,
methods defined in a module don’t override methods defined in classes
which include the module.

consider:

module FooModule
def foo
“FooModule brand foo.”
end
end

class FooMaker
def foo
“FooMaker brand foo.”
end
end

class FooFighter < FooMaker
include FooModule
def foo
“I’ll do my own foo thank you.”
end
end

class FooInheritor < FooMaker
end

class FooIncluder
include FooModule
end

class FooBothInheritAndInclude < FooMaker
include FooModule
end

FooMaker.new.foo # => “FooMaker brand foo.”
FooFighter.new.foo # => “I’ll do my own foo thank you.”
FooInheritor.new.foo # => “FooMaker brand foo.”
FooBothInheritAndInclude.new.foo # => “FooModule brand foo.”

The ancestors method shows the order in which classes and modules

are searched for a method:

FooMaker.ancestors # => [FooMaker, Object, Kernel]
FooFighter.ancestors # => [FooFighter, FooModule,
FooMaker, Object, Kernel]
FooInheritor.ancestors # => [FooInheritor, FooMaker,
Object, Kernel]
FooBothInheritAndInclude.ancestors # => [FooBothInheritAndInclude,
FooModule, FooMaker, Object, Kernel]

Note that in the last case the leaf class doesn’t define foo, and

the method in FooModule overrides the one in FooMaker

And of course singleton methods trump everything:

monty = FooBothInheritAndInclude.new
monty.foo # => “FooModule brand foo.”

def monty.foo
“and now for something completely different”
end

monty.foo # => “and now for something completely different”


Rick DeNatale

Blog: http://talklikeaduck.denhaven2.com/
Github: rubyredrick (Rick DeNatale) · GitHub
Twitter: @RickDeNatale
WWR: http://www.workingwithrails.com/person/9021-rick-denatale
LinkedIn: http://www.linkedin.com/in/rickdenatale

David A. Black wrote:

[“a”, “ab”]
then that gives a stack too deep error, but on the initial slice call.

I’m afraid I don’t know the solution though. I suspect slice is already
an alias for [], or vice versa.

They are indeed the same method:

rb_define_method(rb_cArray, "[]", rb_ary_aref, -1);
rb_define_method(rb_cArray, "slice", rb_ary_aref, -1);

Though, technically they’re synonyms, not aliases. :slight_smile:

http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/13301

Regards,

Dan