Switching dynamically between methods (inside modules)

P

Paul A.

Hi,

I would like to allow a person object (instanced from a Person class) to
speak a language (which is a collection of public methods stored in
Language module):

Suppose that languages are the following:

Example of use:

person = Person.new

person.current_language # => nil
person.hello # => may raise a nice no method error

person.current_language = :english
person.hello # => "Hello! (from english instance variable)"
person.bonjour # => may also raise a no method error
person.quit

person.current_language = :french
person.bonjour # => "Bonjour ! (from french instance variable)"

As you can see, a language is such as a protocol. So a person can switch
on a specific protocol, but only one at a time.

For modular reasons, storing each language into a module is friendly. So
I think this way is the more logical Ruby way, isn't it.

So I would like to merge and unmerge dynamically module's methods (with
a kind of dynamic include) into the object... and then (for instance)
access them with person.public_send(). Thus, instance variables like
@current_language are still accessible from the new methods.

But, I believe that it is not possible to write something like this:

According to you, what could be the best practice to do so?

Any comments and messages are welcome. Thank you.

Regards
 
R

Robert Dober

A first, elegant approach suffers from the minor defect that it does not work ;(
module A
def a; "A" end
end

module B
def a; "B" end
end

o = Object::new
o.extend A
puts o.a
o.extend B
puts o.a
o.extend A
puts o.a

thus more work is needed
the following code will give you some ideas, I hope

class Module
def apply obj
this_mod = self
methods = instance_methods( false ).map{ | mname | [ mname,
instance_method( mname ) ] }
p methods
class << obj; self end.module_eval do
include this_mod
methods.each do | name, mthd |
define_method name do | *args, &blk |
mthd.bind( self ).call( *args, &blk )
end
end
end
end
end
module A
def a; "A" end
end

module B
def a; "B" end
end

class Object
def force_extend *mods
mods.each do | mod |
mod.apply self
end
end
end
o = Object::new
o.force_extend A
puts o.a
o.force_extend B
puts o.a
o.force_extend A
puts o.a

HTH
R.
 
R

Rick DeNatale

To me, this problem cries out for delegation rather than inheritance
or module inclusion.

Here's a first cut.

module Language

Japanese =3D {
:konnichiwa =3D> "=E3=81=93=E3=82=93=E3=81=AB=E3=81=A1=E3=81=AF=EF=BC=
=81 (in Japanese language)",
:sayounara =3D> "=E3=81=95=E3=82=88=E3=81=86=E3=81=AA=E3=82=89=E3=80=82=
(in Japanese language)"
}

French =3D {
:bonjour =3D> "Bonjour ! (in French language)",
:au_revoir =3D> "Au revoir. (in French language)"
}

English =3D {
:hello =3D>"Hello! (in English language)",
:bye =3D> "Bye. (in English language)"
}

class NewBornKnowledge
def self.method_missing(word, *args)
"Goo Goo Gaa Gaa (in BabyTalk)"
end

def to_s
nil
end
end

class Knowledge

def initialize(language)
@language =3D language
@dictionary =3D Language.const_get(language.to_sym)
end

def to_s
@language
end

def method_missing(word, *args)
@dictionary[word] || "that's not a word in #{@language}"
end

end

def self.knowledge_for(language=3Dnil)
if language
Knowledge.new(language)
else
NewBornKnowledge
end
end

end

class Person
def initialize(language =3D nil)
self.current_language =3D language
end

def method_missing(word, *args)
@language_center.send(word)
end

def current_language=3D(language)
@language_center =3D Language.knowledge_for(language)
end

def quit
self.current_language =3D nil
end
end

person =3D Person.new

person.current_language # =3D> "Goo Goo Gaa Gaa (in BabyTalk)"
person.hello # =3D> "Goo Goo Gaa Gaa (in BabyTalk)"

person.current_language =3D :English
person.hello # =3D> "Hello! (in English language)"
person.bonjour # =3D> "that's not a word in English"
person.quit

person.hello # =3D> "Goo Goo Gaa Gaa (in BabyTalk)"
person.bonjour # =3D> "Goo Goo Gaa Gaa (in BabyTalk)"
person.konnichiwa # =3D> "Goo Goo Gaa Gaa (in BabyTalk)"

person.current_language =3D :French
person.bonjour # =3D> "Bonjour ! (in French language)"


This also has the advantage that it can fairly easily be extended to
support multilingual people. Something even a few of us Americans
think is une bonne id=C3=A9e!

--=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
 
R

Robert Klemme

2010/4/30 Rick DeNatale said:
To me, this problem cries out for delegation rather than inheritance
or module inclusion.
Absolutely!

Here's a first cut.

Reminds me of a bad record by a famous band which had otherwise really
great stuff - back in 60s to 80s... :)
This also has the advantage that it can fairly easily be extended to
support multilingual people. =A0Something even a few of us Americans
think is une bonne id=E9e!

:)

Kind regards

robert

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

Robert Dober

Absolutely!
Ok can you kinda stop of spoiling the fun ;).

I really did not analyze the use case. Delegation is often forgotten
and often could be used instead of inheritance with beneficial
effects.
However, traits are behavior based on, well traits and *not* inheritance.
I believe that traits are not really used anywhere in the spirit of
the "original". If I am mistaken that would be a joyful surprise. And
it would allow us to better study traits vs. x. In our case x being
delegation.

The question I would like to see answered is: Is it really the
responsibility of a delegate to do the work? Look at where the data
lives. In our case delegation is wonderful as there is an almost
perfect decoupling of the language data. When the delegate needs many
features of the delegator(sp?) however, traits become a really good
alternative.
And sorry for the advertising ;)Cheers
R.
 
P

Paul A.

Impressive code, thanks Rick Denatale! Thank you too, Robert Dober! I
am currently reading your solutions.

Anyway, I don't know if delegation is the best option for my needs.
Actually, if for instance we update Person class such as:

class Person
attr_accessor :name

def initialize(language = nil)
self.current_language = language
end

def method_missing(word, *args)
@language_center.send(word)
end

def current_language=(language)
@language_center = Language.knowledge_for(language)
end

def quit
self.current_language = nil
end
end

...and English hash like this:

module Language
English = {
:hello => "Hello! My name is #{@name}.",
:bye => "Bye. (in English language)"
}
# ...
}

...then it's hard to access instance variables (and instance methods)
from person object:

person = Person.new
person.name = "Paul"

person.current_language = :English
person.hello # => "Hello! My name is ."

Maybe only inheritance can do it, no? It's tricky :s

Best regards.
P.
 
R

Robert Klemme

2010/4/30 Paul A. said:
Impressive code, thanks Rick Denatale! Thank you too, Robert Dober! =A0I
am currently reading your solutions.

Anyway, I don't know if delegation is the best option for my needs.
Actually, if for instance we update Person class such as:

class Person
=A0attr_accessor :name

=A0def initialize(language =3D nil)
=A0 =A0self.current_language =3D language
=A0end

=A0def method_missing(word, *args)
=A0 =A0@language_center.send(word)
=A0end

=A0def current_language=3D(language)
=A0 =A0@language_center =3D Language.knowledge_for(language)
=A0end

=A0def quit
=A0 =A0self.current_language =3D nil
=A0end
end

...and English hash like this:

module Language
=A0English =3D {
=A0 =A0:hello =3D> "Hello! My name is #{@name}.",

This does not work as the #{} is expanded right here, i.e. if you look
at the map you'll likely find this:
"Hello! My name is ."
=A0 =A0:bye =3D> "Bye. (in English language)"
=A0}
=A0# ...
}

...then it's hard to access instance variables (and instance methods)
from person object:

person =3D Person.new
person.name =3D "Paul"

person.current_language =3D :English
person.hello =A0 =A0# =3D> "Hello! My name is ."

Maybe only inheritance can do it, no? =A0It's tricky :s

No, you just need to change the contract between Person and the
delegate. You can for example pass on self (i.e. the Person instance)
and the delegate extracts instance variable values via
instance_variable_get. You can also do it the other way round and
make delegate methods expect an object which replies to particular
methods (like a callback interface in other languages).

Inheritance is often abused IMHO. People seem to tend to lump too
much disparate functionality together in a single inheritance
hierarchy. At least that's my observation.

Kind regards

robert

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

Robert Klemme

2010/4/30 Robert Dober said:
Ok can you kinda stop of spoiling the fun ;).

How can I stop you from having fun? It's a free world and you can do
whatever pleases you.
I really did not analyze the use case. Delegation is often forgotten
and often could be used instead of inheritance with beneficial
effects.

Exactly Rick's and my point.
However, traits are behavior based on, well traits and *not* inheritance.
I believe that traits are not really used anywhere in the spirit of
the "original". If I am mistaken that would be a joyful surprise. And
it would allow us to better study traits vs. x. In our case x being
delegation.

I would look a bit further into this if I had enough time right now.
I have to admit I don't really have a firm grip on traits so I would
have to do some research upfront to be able to properly comment on
that matter.
The question I would like to see answered is: Is it really the
responsibility of a delegate to do the work? Look at where the data
lives. In our case delegation is wonderful as there is an almost
perfect decoupling of the language data. When the delegate needs many
features of the delegator(sp?) however, traits become a really good
alternative.
And sorry for the advertising ;)

No sweat.

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

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,007
Latest member
obedient dusk

Latest Threads

Top