what is the correct way to extend native methods?

  • Thread starter Maurizio De Santis
  • Start date
M

Maurizio De Santis

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 `<main>'

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"]

?
 
M

Maurizio De Santis

James said:
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

LOL that's what I obtain when I'm still not asleep at 3.00 am :p

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??
 
B

Brian Candler

Maurizio said:

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.
 
D

David A. Black

Hi --

Maurizio said:

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.

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
 
M

Maurizio De Santis

Brian said:
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...
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??? :) I've tried, but with no success...
 
D

David A. Black

Hi --

Brian said:
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...
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??? :) 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
 
M

Maurizio De Santis

David said:
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!
 
I

Intransition

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.
 
D

David A. Black

Hi --

David said:
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!

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
 
M

Maurizio De Santis

David said:
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! :) Can you explain that?
 
R

Rick DeNatale

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:
...

instead, if I execute:

module ExtendedSlicer
=A0def slice(*args)
=A0 =A0arg0 =3D args[0]
=A0 =A0case arg0
=A0 =A0 =A0when Regexp
=A0 =A0 =A0 =A0grep(arg0)
=A0 =A0 =A0else
=A0 =A0 =A0 =A0super
=A0 =A0end
=A0end
end

class Array
=A0include ExtendedSlicer
end

arr =3D Array.new %w{a b c ab}
puts arr.slice(/a/).inspect # =3D> 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! :) 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 # =3D> "FooMaker brand foo."
FooFighter.new.foo # =3D> "I'll do my own foo thank you."
FooInheritor.new.foo # =3D> "FooMaker brand foo."
FooBothInheritAndInclude.new.foo # =3D> "FooModule brand foo."

# The ancestors method shows the order in which classes and modules
are searched for a method:

FooMaker.ancestors # =3D> [FooMaker, Object, Kernel]
FooFighter.ancestors # =3D> [FooFighter, FooModule,
FooMaker, Object, Kernel]
FooInheritor.ancestors # =3D> [FooInheritor, FooMaker,
Object, Kernel]
FooBothInheritAndInclude.ancestors # =3D> [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 =3D FooBothInheritAndInclude.new
monty.foo # =3D> "FooModule brand foo."

def monty.foo
"and now for something completely different"
end

monty.foo # =3D> "and now for something completely different"


--=20
Rick DeNatale

Blog: http://talklikeaduck.denhaven2.com/
Github: http://github.com/rubyredrick
Twitter: @RickDeNatale
WWR: http://www.workingwithrails.com/person/9021-rick-denatale
LinkedIn: http://www.linkedin.com/in/rickdenatale
 
J

Jean-Julien Fleck

Hello,
Why? Maybe it is forbidden to include a Module in a built-in class, for
security reasons, or... boh! :) 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 ! :eek:) is explained in David's book (The
Well-Grounded Rubyist) in a crystal-clear manner around p100 of my
version.

Cheers,

--=20
JJ Fleck
PCSI1 Lyc=E9e Kl=E9ber
 
M

Maurizio De Santis

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

Daniel Berger

David said:
Hi --

Maurizio said:

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.

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. :)

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

Regards,

Dan
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,055
Latest member
SlimSparkKetoACVReview

Latest Threads

Top