Subclassing Array

I have a simple observer-like module:
module Notifier
def add_notification(&callback)
@notifications = [] if not defined? @notifications
@notifications << callback
end

def delete_notification( &callback )
@notifications.delete(callback) if @notifications
end

protected
def notify
@notifications.each { |o| o.call } if defined? @notifications
end
end

(If ‘Notifier’ bugs you, then do a semantic search and replace
/notify/observe/)

I want to mix this in to a subclass of Array. The resulting class would
behave exactly like an Array except whenever the array changed notify
would be called.

I could subclass Array and def each of
&, *, +, --, <<, <=>, ==, ===, [], [], []=, assoc, at, clear,
collect, collect!, compact, compact!, concat, delete, delete_at,
delete_if, each, each_index, empty?, eql?, fill, first, flatten,
flatten!, include?, index, indexes, indices, join, last, length,
map!, new, nitems, pack, pop, push, rassoc, reject!, replace,
reverse, reverse!, reverse_each, rindex, shift, size, slice,
slice!, sort, sort!, to_a, to_ary, to_s, uniq, uniq!, unshift, |
that changes the Array, but obviously this isn’t an exciting prospect.
I’m hoping that most of the changing methods of Array are actually
implemented in terms of a couple of fundamental methods, e.g. insert and
delete. So I would do:

class NotifyingArray
include Notifier
def insert(o)
super(o)
notify
end
def delete(o)
super(o)
notify
end
end

Am I on the right track? Does anyone know which methods I would have to
override?

···


Hans Fugal | De gustibus non disputandum est.
http://hans.fugal.net/ | Debian, vim, mutt, ruby, text, gpg
http://gdmxml.fugal.net/ | WindowMaker, gaim, UTF-8, RISC, JS Bach

GnuPG Fingerprint: 6940 87C5 6610 567F 1E95 CB5E FC98 E8CD E0AA D460

A possible alternative may be something like this:

class SimilArray
def initialize(ary)
@ary=ary
end
include Notifier
def method_missing(:method,*args,&blk)
@ary.send(:method,*args,&blk)
end
end

This is a strange kind of “side inherithance”, but it may work.
BTW, do you know that an observer module already exist in the standard
ruby lib ?

···

il 15 Dec 2003 14:50:51 GMT, firstname@lastname.net (Hans Fugal) ha scritto::

I have a simple observer-like module:

I’m hoping that most of the changing methods of Array are actually
implemented in terms of a couple of fundamental methods, e.g. insert and
delete. So I would do:

class NotifyingArray
include Notifier
def insert(o)
super(o)
notify
end
def delete(o)
super(o)
notify
end
end

Am I on the right track? Does anyone know which methods I would have to
override?

Hans Fugal wrote:

Am I on the right track? Does anyone know which methods I would have to
override?

Well, looking at the ruby sources, it appears that Array is completely
implemented in C (which makes sense, given that it’s one of the core
classes). However, this means that each of the operations on Array
don’t call a ruby method, they call the corresponding C function
directly. Thus, it doesn’t appear that you can simply override one
single method and change the behavior of multiple other methods. :frowning:
Unfortunate, but it makes for much faster Array methods, I suppose.

···


Jamis Buck
jgb3@email.byu.edu

ruby -h | ruby -e ‘a=[];readlines.join.scan(/-(.)[e|Kk(\S*)|le.l(…)e|#!(\S*)/) {|r| a << r.compact.first };puts “\n>#{a.join(%q/ /)}<\n\n”’

That’s unfortunately not the case (for performance reasons), but you
could take a look at matju’s Metaruby which IIRC does exactly that.

OTOH, even wrapping all those methods need not be tiresome if you use
some meta-programming or AspectR for instance.

···

On Tue, Dec 16, 2003 at 12:07:08AM +0900, Hans Fugal wrote:

I’m hoping that most of the changing methods of Array are actually
implemented in terms of a couple of fundamental methods, e.g. insert and
delete. So I would do:


_ _

__ __ | | ___ _ __ ___ __ _ _ __
’_ \ / | __/ __| '_ _ \ / ` | ’ \
) | (| | |
__ \ | | | | | (| | | | |
.__/ _,
|_|/| || ||_,|| |_|
Running Debian GNU/Linux Sid (unstable)
batsman dot geo at yahoo dot com

Software is like sex; it’s better when it’s free.
– Linus Torvalds

With a slight modification what you offered would work:

class SimilArray
def initialize(ary)
@ary=ary
end
include Notifier
def method_missing(:method,*args,&blk)
@ary.send(:method,*args,&blk)
notify # this is what’s different
end
end

But it would always notify even on read-only operations, and so it’s
wasteful. But I could check by name for writing operations.

Another approach would be a mixin that provides the Array operations based
on [], []=, and <=> (for example). Is there such a beast? Maybe I should
just use Enumerable and to_a?

BTW, do you know that an observer module already exist in the standard
ruby lib ?
Yeah, but it is callback-based, but I would like mine to be proc-based.
e.g.

Observable way:
class MyObservable
include Observable
attr_reader :foo
def foo=(o)
@foo = o
changed
notify_observers(self)
end
end
class MyObserver
def initialize
MyObservable.new.add_observer(self)
def update(o)
puts o.foo
end
end

Notifier way:
class MyObservable
include Notifier
attr_reader ;foo
def foo=(o)
@foo = o
notify
end
end
class MyObserver
def initialize
o = MyObservable.new
bar = "bar"
o.add_notification { puts “#{o.foo} #{bar}” }
end
end

My Notifier module is based on
http://www.rubygarden.org/ruby?ObserverPattern

···


Hans Fugal | De gustibus non disputandum est.
http://hans.fugal.net/ | Debian, vim, mutt, ruby, text, gpg
http://gdmxml.fugal.net/ | WindowMaker, gaim, UTF-8, RISC, JS Bach

GnuPG Fingerprint: 6940 87C5 6610 567F 1E95 CB5E FC98 E8CD E0AA D460

With a slight modification what you offered would work:

oops forgot that :slight_smile:

BTW, do you know that an observer module already exist in the standard
ruby lib ?
Yeah, but it is callback-based, but I would like mine to be proc-based.
e.g.

I see. Thanks for the answer

···

il 15 Dec 2003 15:52:20 GMT, firstname@lastname.net (Hans Fugal) ha scritto::

With a slight modification what you offered would work:

class SimilArray
def initialize(ary)
@ary=ary
end
include Notifier
def method_missing(:method,*args,&blk)
@ary.send(:method,*args,&blk)
notify # this is what’s different
end
end

But it would always notify even on read-only operations, and so it’s
wasteful. But I could check by name for writing operations.

···

----- Original Message -----
From: “Hans Fugal” firstname@lastname.net

You could also test to see if the array has changed (clone it before sending
the message): is it the same size with the same elements in the same order?
If so, then notify.

Chris Pine

You could also test to see if the array has changed (clone it before
sending
the message): is it the same size with the same elements in the same
order?

···

----- Original Message -----
From: “Chris Pine” cpine@hellotree.com

If so, then notify.


I mean, if NOT then notify, of course!

Chris Pine