Building a Better Functor

J

John W. Long

Hi,

I've been messing around a with functors lately (see [1] and [2]) and have found them really helpful for testing. They make great light-weight MockObjects. Consider the following example:

Pretend I have a method called redirect that I am trying to test:

def redirect(url, sys=Kernel)
#
# code here that writes out an HTTP header
# that causes the redirect to url
#

# my header has been written so kill the script:
sys.exit
end

Given the following definition for a functor I can easily mock out the sys.exit call:

class Functor
def initialize(method, &block)
@method = method
@block = block
end
def method_missing(symbol, *args)
if @method.to_s.intern == symbol
@block.call(*args)
else
super
end
end
end

Here's my test method:

def test_redirect__exit
exited = false
sys = Functor.new do |meth, *args|
exited = true if meth == :exit
end
redirect('test/url.html', sys)
assert(exited)
end

As you can see a functor is a pretty amazing little object. You can define method_missing a couple of different ways [3], but you are probably having your own ideas by now. Also note: because I'm using blocks my functor methods have access to variables that are in scope where the functor is defined (like "exited" in this case).

As I have used functors I have often found that there are times when it would be nice for a functor to support multiple methods--not just one. I've implemented seen this implemented a couple of ways, but haven't been totally satisfied with the results. Today, while talking to a colleague I hit upon an idea: why not allow functors to be added together to allow the construction of larger functor objects.

Consider the following definition for a functor:

class Functor
def initialize(method = nil, &block)
@methods = {}
add_method(method, &block) if block_given?
end

def __add__(f)
n = self.class.new
n.instance_variable_get(
'@methods'
).update(
@methods
).update(
f.instance_variable_get('@methods')
)
n
end

def add_method(method, &block)
@methods[method.to_s.intern] = block
end

def method_missing(symbol, *args)
case
when @methods.has_key?(symbol)
@methods[symbol].call(*args)
when symbol == :+
__add__(*args)
else
super
end
end
end

With an additional top level method:

def functor(method, &block)
Functor.new(method, &block)
end

This will allow you to do the following:

f = functor:)run) { puts 'running...' } +
functor:)jump) { puts 'jumping...' } +
functor:)play) { puts 'playing...' }

f.run #=> "running..."
f.jump #=> "jumping..."
f.play #=> "playing..."

This is a seriously cool concept. I'd like to see a version of this included in the standard lib. Perhaps even added to the core by modifying proc to behave the same manner (see Nowake's proposal[1]). Does anyone else have a better implementation? Can you see ways of improving my own?

I've uploaded my tests and source code on my Web site [4].
 
J

John W. Long

John said:
Here's my test method:

def test_redirect__exit
exited = false
sys = Functor.new do |meth, *args|
exited = true if meth == :exit
end
redirect('test/url.html', sys)
assert(exited)
end

er, sorry, this code should be:

def test_redirect__exit
exited = false
sys = Functor.new:)exit) do
exited = true
end
redirect('test/url.html', sys)
assert(exited)
end
 
R

Robert Klemme

John W. Long said:
Hi,

I've been messing around a with functors lately (see [1] and [2]) and
have found them really helpful for testing. They make great light-weight
MockObjects. Consider the following example:
Pretend I have a method called redirect that I am trying to test:

def redirect(url, sys=Kernel)
#
# code here that writes out an HTTP header
# that causes the redirect to url
#

# my header has been written so kill the script:
sys.exit
end

Given the following definition for a functor I can easily mock out the sys.exit call:

class Functor
def initialize(method, &block)
@method = method
@block = block
end
def method_missing(symbol, *args)
if @method.to_s.intern == symbol
@block.call(*args)
else
super
end
end
end

Just one remark: I'd change that to define the method in the constructor
and not use method_missing as that's more performant.

class Functor
def initialize(method, &block)
class << self;self;end.class_eval do
define_method(method,&block)
end
end
end

This variant does not allow for easy adding of methods via Functor#+
though. But you can easily define a method add(method,&block) that adds a
method and returns self for chaining:

class Functor
def add(method, &block)
class << self;self;end.class_eval do
define_method(method,&block)
end
self
end

alias :initialize :add
end

Functor.new:)x){puts "x"}.
add:)y){puts "y"}.
add:)z){puts "z"}.y


Kind regards

robert
 
J

John W. Long

Jim said:
I'm almost certain you meant to say:
def +(f)

Actually no. I used __add__(f) along this in method_missing:
when symbol == :+
__add__(*args)

Because I wanted people to be able to define their own behavior for plus:

f = functor :)+) { |value| 1 + value }

puts f + 1
 
J

John W. Long

Robert said:
This variant does not allow for easy adding of methods via Functor#+
though. But you can easily define a method add(method,&block) that adds a
method and returns self for chaining:

class Functor
def add(method, &block)
class << self;self;end.class_eval do
define_method(method,&block)
end
self
end

alias :initialize :add
end

Functor.new:)x){puts "x"}.
add:)y){puts "y"}.
add:)z){puts "z"}.y

Mmm, not sure. I really like the ability to use the + method. Seems like there should be a way to use "define_method" and still support the "+" method. Anybody have time to give it a go?
 
G

Glenn Parker

Robert said:
class << self;self;end.class_eval do

This particular trick has shown up a lot on the Ruby list lately. It's
kind of ugly, but clearly useful. I think it deserves a name, or
possibly a new keyword. What would you call it, "virtual_class_eval"?
 
G

Gavin Kistner

This particular trick has shown up a lot on the Ruby list lately.
It's kind of ugly, but clearly useful. I think it deserves a name, or
possibly a new keyword. What would you call it, "virtual_class_eval"?

Apparently I haven't been paying attention. What does the above do, and
how does it differ from:

class Foo
def self.add( meth, &block )
self.class_eval{ define_method( meth, &block ) }
end
end

or

class Foo
def add( meth, &block )
self.class.class_eval{ define_method( meth, &block ) }
end
end

?

Is it to add new methods to the instance only, and not all instances
derived from Foo?
 
R

Robert Klemme

Gavin Kistner said:
Apparently I haven't been paying attention. What does the above do, and
how does it differ from:

class Foo
def self.add( meth, &block )
self.class_eval{ define_method( meth, &block ) }
end
end

or

class Foo
def add( meth, &block )
self.class.class_eval{ define_method( meth, &block ) }
end
end

?

Is it to add new methods to the instance only, and not all instances
derived from Foo?
Exactly.

robert
 
G

Gavin Kistner


Coming from Javascript (where every instance is trivially extensible
directly) that seems somewhat cumbersome.

Do many people write things like:

class Object
def instance_class
class<<self; self; end
end
end

in a common library they re-use frequently? Is there an RCR (or has
there been discussion) about adding a simple method like that to the
language, so you can simply do:

f = Foo.new
f.instance_class.class_eval{ ... }
?

Or (what I'm really looking for) perhaps it might be nice to have a
define_method method that worked directly for instances. Hrm, but what
would the syntax be? Perhaps that's not such a good idea.
 
G

Gavin Kistner

Or (what I'm really looking for) perhaps it might be nice to have a
define_method method that worked directly for instances. Hrm, but what
would the syntax be? Perhaps that's not such a good idea.

Well, no, maybe it would be nice. Something like:

class Object
def add_method( name, &block )
case self
when Class
self.class_eval{
define_method( name, &block )
}
else
class<<self; self; end.class_eval{
define_method( name, &block )
}
end
end
end

class Foo
def whee; puts "#{self} says 'whee'"; end
end

Foo.add_method( 'yow' ){ puts "#{self} says 'yow!'" }

f1 = Foo.new
f2 = Foo.new

f1.whee
#=> #<Foo:0x1cbdec> says 'whee'

f1.yow
#=> #<Foo:0x1cbdec> says 'yow!'

f2.add_method( 'booya!' ){ puts "#{self} says 'booya!'" }
f2.booya!
#=> #<Foo:0x1cbdc4> says 'booya!'

f1.booya!
#=> tmp.rb:33: undefined method `booya!' for #<Foo:0x1cbdec>
(NoMethodError)


I'm certainly adding that to my personal library, but it seems like it
would be a nice (useful, good, less-hacks-needed) built-in.
 
T

Trans

Ruby Facets:

require 'facet/object/special_class'

f = Foo.new
f.special_class
f.special_class_eval { ... }
f.define_special_method( :meth_name ) { ... }

These are also aliased as "singleton" in place of the word "special".
"Singeton" is the more commonly used term, but it sometimes causes
confusion because of Singleton Pattern. So I choose "special" to help
clarify --being more percise.

See http://calibre.rubyforge.org/


T
 
P

Phil Tomson

Coming from Javascript (where every instance is trivially extensible
directly) that seems somewhat cumbersome.

Do many people write things like:

class Object
def instance_class
class<<self; self; end
end
end

in a common library they re-use frequently? Is there an RCR (or has
there been discussion) about adding a simple method like that to the
language, so you can simply do:

f = Foo.new
f.instance_class.class_eval{ ... }
?

Or (what I'm really looking for) perhaps it might be nice to have a
define_method method that worked directly for instances. Hrm, but what
would the syntax be? Perhaps that's not such a good idea.

Isn't this the common way of doing this:

f = Foo.new
def f.somemethod
"in somemethod"
end

f.somemethod #=> "in somemethod"

Phil
 
M

Martin DeMello

Gavin Kistner said:
in a common library they re-use frequently? Is there an RCR (or has
there been discussion) about adding a simple method like that to the
language, so you can simply do:

It's been discussed several times, but no one has come up with a good
name for it yet. (I like proxy_class myself.)

martin
 
G

Gavin Kistner

Heh, right before I got your email, I had just added "instance_class"
and "instance_class_eval" to the previous bit of code I pasted. :)
 
R

Robert Klemme

Martin DeMello said:
It's been discussed several times, but no one has come up with a good
name for it yet. (I like proxy_class myself.)

I don't - I prefere singleton_class or instance_class. :) It seems it's
about time that matz settles this once and forever by choosing a name and
adding a method to Object in the next release.

Kind regards

robert
 
R

Robert Klemme

Gavin Kistner said:
Coming from Javascript (where every instance is trivially extensible
directly) that seems somewhat cumbersome.

JavaScript has no classes as far as I know. Ruby has classes and from
that alone it's obvious that it's a bit more complicated. Usually when
you define instance methods you do

obj = ....
class <<obj
def this_is_only_visible_in_a_single_instance() ... end
end

The construction with class<<self;self;end is only needed in order to be
able to drag local variables into the class_eval block.
Do many people write things like:

class Object
def instance_class
class<<self; self; end
end
end

in a common library they re-use frequently? Is there an RCR (or has
there been discussion)

Discussion - a lot, RCR - maybe.
about adding a simple method like that to the
language, so you can simply do:

f = Foo.new
f.instance_class.class_eval{ ... }

class Object
def instance_class() class<<self;self;end end
end
?

Or (what I'm really looking for) perhaps it might be nice to have a
define_method method that worked directly for instances. Hrm, but what
would the syntax be? Perhaps that's not such a good idea.

IMHO it's sufficient to have the simplified access to the singleton class.
OTOH, it's not too difficult to obtain that, so...

Regards

robert
 
T

Trans

Hi--

Just FYI. I do not think this is what one calls a Functor exactly,
since the object is independent of the method ( please see
http://jakarta.apache.org/commons/sandbox/functor/ ).

What you have is sort of an OpenStruct for methods. While closely
related it's not exactly the same. I think the going name for what you
have defined is MethodProc ( see
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/114197 ).

Of course, by strict definition a Method _is_ a Functor. But that
doesn't get us very far ;-) I take it a step further and consider a
Functor a "MetaMethod". Ruby Carats (yet to be released) includes just
such a Functor class. Here is the trival use case:

require 'carat/functor'

f = Functor.new { |op, x| x.send(op, x) }
f + 1 #=> 2
f + 2 #=> 4
f + 3 #=> 6
f * 1 #=> 1
f * 2 #=> 2
f * 3 #=> 9

Here's the definition:

class Functor
def initialize(&func)
@func = func
end
def method_missing(op, *args)
@func.call(op, *args)
end
end

Hope you find this of interest,
T
 
A

Adriano Ferreira

Robert Klemme said:
JavaScript has no classes as far as I know. Ruby has classes and > from that alone it's obvious that it's a bit more complicated.

Indeed JavaScript supports object orientation based on prototypes
rather than classes. This is an idea borrowed by a Smalltalk-like
language called Self and other sources of inspirations.

Don't underestimate JavaScript though or prototype-based inheritance.
Most things that can be done with classes can be done with prototypes.
Some things are straightforward with prototypes: delegation, addition
of a few methods or properties to one or a few instances.

The obvious thing with JavaScript is that it lacks syntactic sugar to
make prototype construction more legible and distinguishable. Most
JavaScript code out there are lame and don't use object programming.
But the real point is that writing object code in JavaScript can be
pretty complicated and likely much less legible than in Ruby.

Regards,
Adriano.
 
P

Phrogz

While I admit that I was (shockingly, to me) unaware of that simple
technique, I occasionally do things in JS like:
myObj[ varWithMethodName ] = function(){ ... }
which requires a define_method type call to translate the string into
the method name.

But thanks for the note!
 

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,744
Messages
2,569,482
Members
44,900
Latest member
Nell636132

Latest Threads

Top