Switching dynamically between methods (inside modules)

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):

class Person
attr_accessor :current_language

def quit
# Unselect the current language, if any:
@current_language = nil
end
end

Suppose that languages are the following:

module Language
module Japanese
def konnichiwa
“こんにちは! (in #{@current_language} language)”
end

def sayounara
  "さようなら。 (in #{@current_language} language)"
end

end

module French
def bonjour
“Bonjour ! (in #{@current_language} language)”
end

def au_revoir
  "Au revoir. (in #{@current_language} language)"
end

end

module English
def hello
“Hello! (in #{@current_language} language)”
end

def bye
  "Bye. (in #{@current_language} language)"
end

end
end

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:

class Person

wrong code:

include “Language::#{@current_language}” unless @current_language.nil?
end

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

Any comments and messages are welcome. Thank you.

Regards

On Fri, Apr 30, 2010 at 12:50 PM, Paul A. [email protected] wrote:
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.

Oh BTW, traits do exactly what you want, maybe you want to check out
my implementation
http://rubyforge.org/frs/?group_id=4642

might save you some troubles.
R.

2010/4/30 Rick DeNatale [email protected]:

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… :slight_smile:

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ée!

:slight_smile:

Kind regards

robert

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

Here’s a first cut.

module Language

Japanese = {
:konnichiwa => “こんにちは! (in Japanese language)”,
:sayounara => “さようなら。 (in Japanese language)”
}

French = {
:bonjour => “Bonjour ! (in French language)”,
:au_revoir => “Au revoir. (in French language)”
}

English = {
:hello =>“Hello! (in English language)”,
:bye => “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 = language
  @dictionary = 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=nil)
if language
Knowledge.new(language)
else
NewBornKnowledge
end
end

end

class Person
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

person = Person.new

person.current_language # => “Goo Goo Gaa Gaa (in BabyTalk)”
person.hello # => “Goo Goo Gaa Gaa (in BabyTalk)”

person.current_language = :English
person.hello # => “Hello! (in English language)”
person.bonjour # => “that’s not a word in English”
person.quit

person.hello # => “Goo Goo Gaa Gaa (in BabyTalk)”
person.bonjour # => “Goo Goo Gaa Gaa (in BabyTalk)”
person.konnichiwa # => “Goo Goo Gaa Gaa (in BabyTalk)”

person.current_language = :French
person.bonjour # => “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ée!


Rick DeNatale

Blog: http://talklikeaduck.denhaven2.com/
Github: rubyredrick (Rick DeNatale) · GitHub
Twitter: @RickDeNatale
WWR: http://www.workingwithrails.com/person/9021-rick-denatale
LinkedIn: http://www.linkedin.com/in/rickdenatale

Impressive code, thanks Rick Denatale! Thank you too, Robert D.! 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.

On Fri, Apr 30, 2010 at 2:57 PM, Robert K.
[email protected] wrote:

2010/4/30 Rick DeNatale [email protected]:

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

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 :wink:

Cheers
R.

2010/4/30 Paul A. [email protected]:

self.current_language = language
def quit
self.current_language = nil
end
end

…and English hash like this:

module Language
English = {
:hello => “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 .”

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

Maybe only inheritance can do it, no? It’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

2010/4/30 Robert D. [email protected]:

On Fri, Apr 30, 2010 at 2:57 PM, Robert K.
[email protected] wrote:

2010/4/30 Rick DeNatale [email protected]:

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

Absolutely!
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 :wink:

No sweat.

Kind regards

robert