looking for an "inversion" pattern

F

Fearless Fool

I'm sure there's a clean way to do this in Ruby, but I haven't figured
it out.

I'd like to create a method +foo+ that transforms:

my_obj.foo.some_method(*args)

to

MyClass.some_method(my_obj, *args)

Maybe I'm overthinking this. I'll keep working on this, but in the
meantime, I'm open to suggestions.

Thanks!

-- ff
 
F

Fearless Fool

Fearless Fool wrote in post #992929:
Maybe I'm overthinking this...

Meh. I s'pose I can substitute an underscore for a period:

my_obj.foo_some_method(*args)

... whereupon this become a simple mixin on my_obj's class:

def foo_some_method(*args) {
MyClass.some_method(self, *args)
}

The only potential disadvantage is that I need to write one of these for
each method. The advantages include likely to run much faster (than
constructing lambdas or invoking method_missing processing) and possibly
easier to understand and maintain than what I was originally trying to
do.
 
K

Kevin Mahler

Fearless Fool wrote in post #992929:
I'd like to create a method +foo+ that transforms:

my_obj.foo.some_method(*args)

to

MyClass.some_method(my_obj, *args)

class MyClass
def self.some_method(obj, *args)
puts "MyClass.some_method:"
puts "obj: #{obj.inspect}"
puts "args: #{args.inspect}"
end
end

def define_foo(obj, clazz, method)
obj.singleton_class.class_eval do
define_method :foo do
Class.new do
define_method method do |*args|
clazz.send(method, obj, *args)
end
end.new
end
end
end

my_obj = "my_obj thing"
args = [1,2,3]
define_foo(my_obj, MyClass, :some_method)
my_obj.foo.some_method(*args)

# =>
# MyClass.some_method:
# obj: "my_obj thing"
# args: [1, 2, 3]

Returning facade or proxy objects like this can be an elegant solution
to certain problems. It's rather high on the abstraction ladder, though,
and without knowing the context I would wonder if more direct solutions
are possible.
 
F

Fearless Fool

@Kevin:

Kevin Mahler wrote in post #992945:
...
def define_foo(obj, clazz, method)
obj.singleton_class.class_eval do
define_method :foo do
Class.new do
define_method method do |*args|
clazz.send(method, obj, *args)
end
end.new
end
end
end

That's some mighty fine code-fu -- back in the day I had no problem with
metaprogramming constructs in scheme, but I'm not yet at ease with the
syntax and class structure of Ruby.
Returning facade or proxy objects like this can be an elegant solution
to certain problems. It's rather high on the abstraction ladder, though,
and without knowing the context I would wonder if more direct solutions
are possible.

Well, yes -- as per my previous post. Since you must call define_foo()
for each object & method you want to cover, I don't see a lot of
advantage to the approach. But it does show off Ruby's function
defining tricks nicely.
 
J

Jesús Gabriel y Galán

I'm sure there's a clean way to do this in Ruby, but I haven't figured
it out.

I'd like to create a method +foo+ that transforms:

=A0 my_obj.foo.some_method(*args)

to

=A0MyClass.some_method(my_obj, *args)

Maybe I'm overthinking this. =A0I'll keep working on this, but in the
meantime, I'm open to suggestions.

I would create a method foo that returns a proxy that remembers which
was the original receiver. This proxy can have a method_missing
implementation that calls MyClass.xxxx passing the original receiver
and all params. For example:

ruby-1.8.7-p334 :003 > class Proxy
ruby-1.8.7-p334 :004?> def initialize target
ruby-1.8.7-p334 :005?> @target =3D target
ruby-1.8.7-p334 :006?> end
ruby-1.8.7-p334 :007?> def method_missing meth, *args, &blk
ruby-1.8.7-p334 :008?> MyClass.send(meth,@target,*args,&blk)
ruby-1.8.7-p334 :009?> end
ruby-1.8.7-p334 :010?> end
=3D> nil
ruby-1.8.7-p334 :011 > class Something
ruby-1.8.7-p334 :012?> def foo
ruby-1.8.7-p334 :013?> Proxy.new self
ruby-1.8.7-p334 :014?> end
ruby-1.8.7-p334 :015?> end
=3D> nil
ruby-1.8.7-p334 :016 > class MyClass
ruby-1.8.7-p334 :017?> def self.test1 obj, *args
ruby-1.8.7-p334 :018?> p [obj,*args]
ruby-1.8.7-p334 :019?> end
ruby-1.8.7-p334 :020?> end
=3D> nil
ruby-1.8.7-p334 :021 > my_obj =3D Something.new
=3D> #<Something:0xb72c8514>
ruby-1.8.7-p334 :022 > my_obj.foo.test1 1,2,3
[#<Something:0xb72c8514>, 1, 2, 3]

I don't know if method foo is part of your class, or maybe you can
build a method and extend the required objects with it.

Jesus.
 
R

Robert Klemme

Fearless Fool wrote in post #992929:

Meh. =A0I s'pose I can substitute an underscore for a period:

=A0my_obj.foo_some_method(*args)

... whereupon this become a simple mixin on my_obj's class:

=A0def foo_some_method(*args) {
=A0 =A0MyClass.some_method(self, *args)
=A0}

The only potential disadvantage is that I need to write one of these for
each method. =A0The advantages include likely to run much faster (than
constructing lambdas or invoking method_missing processing) and possibly
easier to understand and maintain than what I was originally trying to
do.

I am sorry, I still do not understand your motivation to have a class
method which receives an instance as first argument. Isn't this
conceptually exactly what an instance method is for? Why do you need
this?

If you need this as general pattern you could always do

class X
def method_missing(m, *a, &b)
self.class.send(m, self, *a, &b)
end

def self.foo(obj, str)
printf "%p: %s\n", obj, str
end
end

X.new.foo 123

But I would seriously question the wisdom of this. :)

Other than that I would follow Jesus's approach to make #foo create a
proxy instance. Here's another way to do it which would also allow
method chaining on the proxy:

class Proxy < BasicObject
def initialize(orig, delegate)
@orig =3D orig
@delegate =3D delegate
end

def method_missing(m, *a, &b)
res =3D @delegate.send(m, @orig, *a, &b)
@orig.equal?(res) ? self : res
end
end

class Object
# create proxy for delegation to given
# instance
def proxy(delegate)
Proxy.new(self, delegate)
end
end

class YourClass
def proxy
super(MyClass)
end
end

Kind regards

robert

--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 
K

Kevin Mahler

Fearless Fool wrote in post #992947:
That's some mighty fine code-fu...

Um, no it's not. Inspecting from top to bottom: (a) open the singleton
class, (b) define a method, (c) create an instance of a new class (d)
define a method. This has nothing to do with code-fu.

One of the problems with Ruby is that the syntax changes depending on
the compile-time or run-time context, which is a totally arbitrary
distinction. It's a disservice to programmers because it makes them
say "code-fu" when presented with the run-time counterparts of
familiar, mundane compile-time constructs.

Don't get me started about using eval to "fix" that.
Since you must call define_foo() for each object & method you want to
cover, I don't see a lot of advantage to the approach.

You can automate it as much as you like, including removing the need
for define_foo(). Perhaps you took my code too literally.
 
F

Fearless Fool

Thanks everybody!!

@Jes=C3=BAs:

I like your suggestion. It is simple and does what I'm looking for.

@robert K:
I am sorry, I still do not understand your motivation to have a class
method which receives an instance as first argument.

My motivation is that I was writing a lot of code like this:

module HasXattr
def xattr_reference(key); Xattr.xattr_reference(self, key); end
def xattr_store(key, value); Xattr.xattr_store(self, key, value); =

end
def xattr_has_key?(key); Xattr.xattr_has_key?(self, key); end
def xattr_delete(key); Xattr.xattr_delete(self, key); end
...
end

... and I thought to myself: this is Ruby. There must be a better way. =

I guess I should have shown this concrete example earlier in the thread.

@Kevin:
One of the problems with Ruby is that the syntax changes depending on
the compile-time or run-time context, which is a totally arbitrary
distinction.

As I said, I came from a scheme background where everything was =

syntactically simple and life was simple. Ah, for those days. I guess =

I should have said "that's mighty fine Ruby-fu", since your =

understanding of Ruby far outstrips mine.

Again, thanks all...

- ff

-- =

Posted via http://www.ruby-forum.com/.=
 
R

Robert Klemme

Thanks everybody!!

You're welcome!
My motivation is that I was writing a lot of code like this:

module HasXattr
def xattr_reference(key); Xattr.xattr_reference(self, key); end
def xattr_store(key, value); Xattr.xattr_store(self, key, value);
end
def xattr_has_key?(key); Xattr.xattr_has_key?(self, key); end
def xattr_delete(key); Xattr.xattr_delete(self, key); end
...
end

.. and I thought to myself: this is Ruby. There must be a better way.
I guess I should have shown this concrete example earlier in the thread.

With my code you could do

module HasXattr
def xattr # foo in your lingo
proxy(Xattr)
end
end

or directly use it in your class

module HasXattr
def xattr # foo in your lingo
proxy(Xattr)
end
end

Then you can do

obj.xattr.has_key? "k"

or

obj.xattr.tap |x|
x.store "k", "v" unless x.has_key?
end

Btw, what is Xattr? Where does it come from and what does it do? From
the looks it seems to be some external storage for attributes associated
with your instance which you do not want to store in the instance itself
(for whatever reasons). What do you need that for?

Kind regards

robert
 
7

7stud --

Kevin Mahler wrote in post #992945:
Fearless Fool wrote in post #992929:
I'd like to create a method +foo+ that transforms:

my_obj.foo.some_method(*args)

to

MyClass.some_method(my_obj, *args)

class MyClass
def self.some_method(obj, *args)
puts "MyClass.some_method:"
puts "obj: #{obj.inspect}"
puts "args: #{args.inspect}"
end
end

def define_foo(obj, clazz, method)
obj.singleton_class.class_eval do
define_method :foo do
Class.new do
define_method method do |*args|
clazz.send(method, obj, *args)
end
end.new
end
end
end

my_obj = "my_obj thing"
args = [1,2,3]
define_foo(my_obj, MyClass, :some_method)
my_obj.foo.some_method(*args)

# =>
# MyClass.some_method:
# obj: "my_obj thing"
# args: [1, 2, 3]

Returning facade or proxy objects like this can be an elegant solution
to certain problems. It's rather high on the abstraction ladder, though,
and without knowing the context I would wonder if more direct solutions
are possible.

I don't know what's conceptually easier to understand, but in ruby there
is no limit to how high you can stack singleton methods, e.g.:

obj.foo
obj.foo.some_method

If obj.foo() returns obj's singleton class, then some_method() is being
called as a class method of the singleton class. That means some_method
is a method inside the singleton class's singleton class:

obj's class
^
|
|
singleton2: some_method()
^
|
|
singleton1: foo() -> returns singleton1
^
|
|
obj


obj.foo.some_method

And you can add as many singleton classes to the method lookup path as
you want. For instance, if you have this call:

obj.foo.some_method.do_stuff

and obj.foo.some_method() returns singleton2, then do_stuff() is being
called as a class method of singleton2, i.e. do_stuff() is a method in a
parent class, singleton3, above singleton2.

So in the original example at the top of the post, if the
creating-a-new-anonymous-class-with-a-method-named-some_method-and-returning-an-instance-of-that-class
is too hard to understand, you could do this:

class MyClass
def self.some_method(obj, *args)
puts "MyClass.some_method:"
puts "obj: #{obj.inspect}"
puts "args: #{args.inspect}"
end
end

def define_foo(obj, clazz, method)
obj.singleton_class.class_eval do
singleton = self

define_method :foo do
#Class.new do
#define_method method do |*args|
#clazz.send(method, obj, *args)
#end
#end.new


#In here, self is equal to the obj that
#will eventually call foo()--not obj.singleton_class.
#Hence, the need to do singleton = self above.

singleton.singleton_class.class_eval do
define_method method do |*args|
clazz.send(method, obj, *args)
end
end

singleton #return the singleton class
end

end

end

my_obj = "my_obj thing"
args = [1,2,3]
define_foo(my_obj, MyClass, :some_method)
my_obj.foo.some_method(*args)

--output:--
MyClass.some_method:
obj: "my_obj thing"
args: [1, 2, 3]
 
7

7stud --

7stud -- wrote in post #993237:
I guess since obj *is* visible in all the nested blocks, its not
strictly necessary to do the singleton = self trick--you can always get
the singleton class from obj using obj.singleton_class:

class MyClass
def self.some_method(obj, *args)
puts "MyClass.some_method:"
puts "obj: #{obj.inspect}"
puts "args: #{args.inspect}"
end
end

def define_foo(obj, clazz, method)

obj.singleton_class.class_eval do
define_method :foo do
obj.singleton_class.singleton_class.class_eval do
define_method method do |*args|
clazz.send(method, obj, *args)
end
end

obj.singleton_class
end

end

end
 
B

Brian Candler

Fearless Fool wrote in post #992929:
I'm sure there's a clean way to do this in Ruby, but I haven't figured
it out.

I'd like to create a method +foo+ that transforms:

my_obj.foo.some_method(*args)

to

MyClass.some_method(my_obj, *args)

Not sure, but would bound or unbound methods help you?

bound_method = my_obj.method:)foo)
bound_method.call(*args)

unbound_method = my_obj.class.instance_method:)foo)
unbound_method.bind(my_obj).call(*args)
 
R

Robert Klemme

Fearless Fool wrote in post #992929:

Not sure, but would bound or unbound methods help you?

bound_method = my_obj.method:)foo)
bound_method.call(*args)

unbound_method = my_obj.class.instance_method:)foo)
unbound_method.bind(my_obj).call(*args)

You can only bind to instances of the original type. You cannot use
#unbind and #bind to let a method be called on a different type. But
this would be necessary for the OP's requirement to be fulfilled.

irb(main):001:0> class Foo
irb(main):002:1> def self.bar;123;end
irb(main):003:1> end
=> nil
irb(main):004:0> m=Foo.method:)bar).unbind
=> #<UnboundMethod: #<Class:Foo>#bar>
irb(main):005:0> f=Foo.new
=> #<Foo:0x105a24a4>
irb(main):006:0> m.bind(f)
TypeError: singleton method called for a different object
from (irb):6:in `bind'
from (irb):6
from /usr/local/bin/irb19:12:in `<main>'
irb(main):007:0> m.bind(Foo)
=> #<Method: Foo.bar>
irb(main):008:0> m.bind(Foo).call
=> 123

Kind regards

robert
 

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

Forum statistics

Threads
473,811
Messages
2,569,693
Members
45,477
Latest member
IsidroSeli

Latest Threads

Top