canonical Array#append to hang hook

R

Rassat Nicolas

I have a class which inherit from Array. I want to perform some task
each time something is append to an instance of this class. The problem
is: there are so many way to append something to an array. For example:

class Narray < Array
alias_method :eek:ld_push, :push
def push(*obj)
p "pushing #{obj}"
old_push(*obj)
end
alias_method :eek:ld_append, :<<
def <<(obj)
p "<<ing #{obj}"
old_append(obj)
end
alias_method :eek:ld_bracket, :[]=
def []=(i,l=0)
p "[]=ing #{i}"
old_bracket(i,l)
end
alias_method :eek:ld_add, :+
def +(i)
p "+ing #{i}"
old_add(i)
end
end

a = Narray.new
a.push(1)
a << 2
a[a.length] = 3
a = a + [4]
p a

I forgot Array#insert, Array#fill and probably other...

Is there any Array#append function that can hooked to perform task every
time something is append to a?

Any solution is welcome.

LarsTico

PS: sorry for my poor frenchy english, but I hope you understand me.
 
R

Robert Klemme

2007/9/30 said:
I have a class which inherit from Array. I want to perform some task
each time something is append to an instance of this class. The problem
is: there are so many way to append something to an array. For example:

class Narray < Array
alias_method :eek:ld_push, :push
def push(*obj)
p "pushing #{obj}"
old_push(*obj)
end
alias_method :eek:ld_append, :<<
def <<(obj)
p "<<ing #{obj}"
old_append(obj)
end
alias_method :eek:ld_bracket, :[]=
def []=(i,l=0)
p "[]=ing #{i}"
old_bracket(i,l)
end
alias_method :eek:ld_add, :+
def +(i)
p "+ing #{i}"
old_add(i)
end
end

a = Narray.new
a.push(1)
a << 2
a[a.length] = 3
a = a + [4]
p a

I forgot Array#insert, Array#fill and probably other...

Is there any Array#append function that can hooked to perform task every
time something is append to a?

Any solution is welcome.

I'd probably consider not using inheritance but wrapping your Array in
some other class. That way you can expose the interface you want to
expose and esp. limit the way to modify the array.

Kind regards

robert
 
J

Joel VanderWerf

Rassat said:
I have a class which inherit from Array. I want to perform some task
each time something is append to an instance of this class. The problem
is: there are so many way to append something to an array. For example:

class Narray < Array ...
alias_method :eek:ld_add, :+
def +(i)
p "+ing #{i}"
old_add(i)
end
end

It's not clear to me (though maybe it is clear to you) whether + should
be considered as a kind of append operation. Note that + doesn't modify
the array, but returns a new array. Furthermore, + doesn't return an
instance of NArray, but just an Array:

irb(main):001:0> class A < Array
irb(main):002:1> def +(x); super; end
irb(main):003:1> end
=> nil
irb(main):004:0> a = A.new
=> []
irb(main):005:0> (a + []).class
=> Array

However:

irb(main):008:0> (a.concat []).class
=> A
 
L

Logan Capaldo

I have a class which inherit from Array. I want to perform some task
each time something is append to an instance of this class. The problem
is: there are so many way to append something to an array. For example:

class Narray
def initialize(a)
@array = a.dup.freeze
end

def method_missing(name, *args, &block)
@array.send(name, *args, &block)
rescue NoMethodError
super
rescue TypeError => e
if e.message =~ /can't modify frozen array/
a = @array.dup
p "#{name}ing"
r = a.send(name, *args, &block)
@array = a.freeze
r
else
raise e
end
end

def inspect
@array.inspect
end

def to_s
@array.to_s
end
end

narray = Narray.new [1,2,3]
narray.append 4
p narray
narray << 3
p narray
narray.fill(2)
p narray

Totally didn't test this
 
L

Lars Ticot

Answer to the three responses:


To Robert Klemme:

Sure I can do this but this way I lose every benefit of inheritance! And
I'll have to rewrite "accesor" function to access function to the
instance variable array. That is not very DRY.


To Joel VanderWerf:

Your remark about Array#+ is completly right. I just put it here to test
if Array#push or Array#<< were implementing using the Array#+ function.


To Logan Capaldo:

I got this error:
narray.rb:9:in `method_missing': undefined method `append' for [1,
2,3]:Narray (NoMethodError)
from narray.rb:32

But this is not the point. This way I'll catch every func, not just func
that insert something on the array. I would have to do something like my
example. Again, this is not really DRY.


Does anyone knows how insertion is implementing in the ruby core code or
should I dig the code by myself? This time is about inserting into an
array, but it can be over action; would it not be great to have some
hook function to solve easily this kind of problem?

To all you three, thanks for your answer
 
A

Austin Ziegler

To Robert Klemme:
Sure I can do this but this way I lose every benefit of inheritance! And
I'll have to rewrite "accesor" function to access function to the
instance variable array. That is not very DRY.

No you don't. #method_missing and #respond_to? are your friends. Or
you can use Delegator.

class NRArray
def initialize(*args, &block)
@nrarray = if args.empty? and block.nil?
[]
elsif args.empty?
Array.new &block
else
Array.new(*args, &block)
end
end

def respond_to?(sym)
ok = super
ok = @nrarray.respond_to?(sym) unless ok
ok
end

def method_missing(sym, *args, &block)
puts "#{sym}ing #{args.inspect}"
@nrarray.send(sym, *args, &block)
end
end

(Note that NArray is a well-known numerical array extension, so Narray
is likely to be confusing if you need it.)

-austin
 
L

Lars Ticot

Austin said:
No you don't. #method_missing and #respond_to? are your friends. Or
you can use Delegator.

yes but:

a = NRArray.new
a.push(2) # => "pushing [2]"
a.pop # => "poping []"

I don't want to catch the poping!

The delegator is an answer, like having an Array instance var. I lose
the inheritance benefit.

It would have been great if, Array#push, Array#<< and Array#insert,
would used the same Array#internal_insert in their implementation. So
hooking this one would give me a hook on the other one. Basicaly they
are all three doing insertion!
(Note that NArray is a well-known numerical array extension, so Narray
is likely to be confusing if you need it.)

Right! It was just the first name that came to me (N(ew)Array) when I
start thinking at this problem ;-)
 
R

Robert Klemme

2007/10/2 said:
To Robert Klemme:

Sure I can do this but this way I lose every benefit of inheritance! And
I'll have to rewrite "accesor" function to access function to the
instance variable array. That is not very DRY.

Actually the only real difference is object identity IMHO. As has been
demonstrated there are easy to use tools that make delegation of
method invocations to the real Array really simple.

If you provide more insight into your use case then maybe even more /
better / easier solutions will come up. I am convinced that it is a
bad idea to inherit Array most of the time similarly to it being a bad
idea to inherit java.util.HashMap.

Kind regards

robert
 
L

Logan Capaldo

To Logan Capaldo:

I got this error:
narray.rb:9:in `method_missing': undefined method `append' for [1,
2,3]:Narray (NoMethodError)
from narray.rb:32

Heh I'm silly. There actually is no Array#append method so that's what
should have happend. Delete the narray.append line and then try it
out, I think you'll find it's a pleasantly evil hack.
 
L

Lars Ticot

Robert said:
Actually the only real difference is object identity IMHO.

Right for me
As has been
demonstrated there are easy to use tools that make delegation of
method invocations to the real Array really simple.

Right too. But they induced over cost (during coding time and execution
time)
If you provide more insight into your use case then maybe even more /
better / easier solutions will come up.

It's a work at really pre alpha stage (actually I am just starting to
think about how to do things). It's a kind of spreadsheet that works by
column. That should approximately look like

class Cell
# represent the content of a cell
# define some useful functions: format, is_number?,....
end

class Column
# represent the column as an array of Cell.
end
I am convinced that it is a
bad idea to inherit Array most of the time similarly to it being a bad
idea to inherit java.util.HashMap.

To continue on my example, I want to check if cells that I add on my
column are number (just an example). Two possibilities:
- if Column inherit from Array I'll have to do something like in my
first example.
- if Column does not inherit from Array it will have to delegate Array's
function that I need (almost all function in Array)and the delegation
has a cost (see example at the end). Moreover, what if I want to use a
lib that don't rely only on duck typing and use some crapy things like
this?
def do_something(obj)
if obj.class == Array then
...
else
...
end
So the identity of object *is* important in some (bad?) situation. As an
example, Gtk lib (and probably others) does such things (I'll post
something on this and multiple inheritance in some time).

My column *is* an array. So I think it's pretty clear that it should be
derived from Array.

Thanks

Here is a quick and dirty test I made to see cost of delegating.
Suprisingly, delegating and forwarding are really slow. As slow as using
the method_missing (without error test). I would expect them to be as
quick as the "indirect" (class C) method.


require 'delegate'
require 'forwardable'
require 'benchmark'
include Benchmark

class A
def hello
end
end

class B < A
end

class C
def initialize
@a = A.new
end
def hello
@a.hello
end
end

class D < DelegateClass(A)
def initialize
a = A.new
super(a)
end
end

class E
extend Forwardable
def_delegator:)@a, :hello, :hello)
def initialize
@a=A.new
end
end

class F
def initialize
@a=A.new
end
def method_missing(name, *args, &block)
@a.send(name, *args, &block)
end
end

a = A.new
b = B.new
c = C.new
d = D.new
e = E.new
f = F.new

m=100000 # My computer is an old k6-400 so you probably have to increase
this!

bm(10) do |x|
x.report("direct ") { m.times{a.hello } }
x.report("inherit ") { m.times{b.hello } }
x.report("indirect") { m.times{c.hello } }
x.report("delegate") { m.times{d.hello } }
x.report("forward ") { m.times{e.hello } }
x.report("missing ") { m.times{f.hello } }
end
 

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
474,266
Messages
2,571,342
Members
48,018
Latest member
DelilahDen

Latest Threads

Top