Using extend for initialization settings?


T

Trans

It's not uncommon to see initialize method take a hash or a setting
proc and apply that to accessors. Eg.

def initialize( settings )
settings.each{|k,v| send("{#k}=",v)
end

or

def initialize( &settings )
settings.call(self)
end

Today I come up with another potential approach:

class Hash
def to_module(module_function=false)
m = Module.new
each do |k,v|
m.send:)define_method, k){ v }
m.send:)module_function, k) if module_function
end
return m
end
end

M = { :a => 1 }.to_module(true)
p M.a #=> 1


class Foo
def initialize( settings )
extend settings.to_module
end
end

f = Foo.new:)x => 9)
p f.x #=> 9


Thoughts?

T.
 
Ad

Advertisements

G

Gregory Brown

def initialize( settings )
settings.each{|k,v| send("{#k}=",v)
end
def initialize( settings )
extend settings.to_module
end

Side by side, I have to say the former is clearer than the latter, and
doesn't require changing core.
 
R

Robert Klemme

It's not uncommon to see initialize method take a hash or a setting
proc and apply that to accessors. Eg.

def initialize( settings )
settings.each{|k,v| send("{#k}=",v)
end

or

def initialize( &settings )
settings.call(self)
end

Today I come up with another potential approach:

class Hash
def to_module(module_function=false)
m = Module.new
each do |k,v|
m.send:)define_method, k){ v }
m.send:)module_function, k) if module_function
end
return m
end
end

M = { :a => 1 }.to_module(true)
p M.a #=> 1


class Foo
def initialize( settings )
extend settings.to_module
end
end

f = Foo.new:)x => 9)
p f.x #=> 9


Thoughts?


irb(main):001:0> require 'ostruct'
=> true
irb(main):002:0> f = OpenStruct.new:)x => 9)
=> #<OpenStruct x=9>
irb(main):003:0> f.x
=> 9

:)

robert
 
T

Trans

irb(main):001:0> require 'ostruct'
=> true
irb(main):002:0> f = OpenStruct.new:)x => 9)
=> #<OpenStruct x=9>
irb(main):003:0> f.x
=> 9

Hmm... I think maybe my point is being missed. The goal is to
initialize a class, not create a simple data struct.

I think the neat thing about this technique is that it could go beyond
just assigning values, and provide a clean means of dependency
injection.

class Module
def to_module; self; end
end

module Container
def log(msg)
puts msg
end
end

class Foo
def initialize( settings )
extend settings.to_module
end
def report_
log("Ready.")
end
end

f = Foo.new(Container)

f.report

produces

Ready.

T.
 
G

Gregory Brown

Hmm... I think maybe my point is being missed. The goal is to
initialize a class, not create a simple data struct.

I think the neat thing about this technique is that it could go beyond
just assigning values, and provide a clean means of dependency
injection.

class Module
def to_module; self; end
end

module Container
def log(msg)
puts msg
end
end

class Foo
def initialize( settings )
extend settings.to_module
end
def report_
log("Ready.")
end
end

f = Foo.new(Container)

f.report

class Foo; end

f = Foo.new
f.extend(Container)


I don't get what this is getting me over plain ruby?
 
R

Robert Dober

On 6/15/07, Trans <[email protected]> wrote:

class Foo; end

f = Foo.new
f.extend(Container)


I don't get what this is getting me over plain ruby?

Hmm control maybe, you are not forcing anybody to call
f.extend(Container) in your approach.

Tom's approach would allow you to exercise some control about instantiation
e.g.
def initialize(settings)
check about settings.

I however miss the point of #to_module ? What is that good for....
would you mind to explain please.

Cheers
Robert
 
Ad

Advertisements

G

Gregory Brown

Hmm control maybe, you are not forcing anybody to call
f.extend(Container) in your approach.

Then just pass the constant into the constructor :)

It feels like Trans is working on some DI stuff, but I really don't
see the need for it in most practical ruby...
 
R

Robert Klemme

Hmm... I think maybe my point is being missed. The goal is to
initialize a class, not create a simple data struct.

Actually your code is about initializing an instance - not a class.
Basically you just transform a Hash into some other data structure, a
module with constant accessors in your case. At the moment I fail to
see the benefit over something like this:

class Hash
def init(x)
each do |var, val|
x.send("#{var}=", val)
end
end
end

class Foo
def initialize(settings)
settings.init(self)
end
end

I find the approach using a module somewhat convoluted. Also a module
simply does not seem the right vehicle IMHO. For example, what happens
to attr_accessors that you define in that class? Either they override
your anonymous module's methods or the other way round. This does not
seem a good solution to me.
I think the neat thing about this technique is that it could go beyond
just assigning values, and provide a clean means of dependency
injection.

class Module
def to_module; self; end
end

module Container
def log(msg)
puts msg
end
end

class Foo
def initialize( settings )
extend settings.to_module
end
def report_
log("Ready.")
end
end

f = Foo.new(Container)

f.report

produces

Ready.

Basically what you do is you require an instance that implements a
particular method that you introduce and which has to return a module
and you promise to extend the instance with that module returned. But
this means at the same time that there always *has* to be a module. I
am not sure whether that's a good idea because for one every module
comes at a cost and I think it's not the proper means to carry some
initialization info. Also, Module#to_module and Hash#to_module server
two completely different purposes: the former helps extending an
instance with a predefined module (functionality) and the latter
provides key value pairs via a particular interface.

The more general solution for the "pass argument X that implements
method Y" is of course - blocks. So the pattern that you quoted
earlier is more flexible and general IMHO. I mean

def initialize(&b)
instance_eval(&b)
end

Combining that with Hash#init from above you can solve these tasks with

f = Foo.new { {:foo=>"bar"}.init(self) }
f = Foo.new { extend Container }

Kind regards

robert
 
G

Gregory Brown

Huh.. that is exactly what I have suggested, right?
I asked why #to_module .

Right. I was basically saying you don't need to_module because if you
wanted to force your object to use some container module, you could
just pass it in.

The .to_module code is gaining you a few chars at most, and I'm not
sure it's worth it for the lack of clarity it introduces.
 
T

Trans

Right. I was basically saying you don't need to_module because if you
wanted to force your object to use some container module, you could
just pass it in.

The .to_module code is gaining you a few chars at most, and I'm not
sure it's worth it for the lack of clarity it introduces.

#to_module is just a convenience so one can pass in a container module
or a hash (or anything else that responds to #to_module). It's
basically the same as when you accept a string but go ahead and
generalize it to accept anything that responds to #to_s. In other
words, it's a better approach than

def initialize(settings)
case settings
when Module
...
when Hash
...

T.
 
Ad

Advertisements

G

Gregory Brown

#to_module is just a convenience so one can pass in a container module
or a hash (or anything else that responds to #to_module). It's
basically the same as when you accept a string but go ahead and
generalize it to accept anything that responds to #to_s.

I understand what it's for, I just don't think it's very useful.
 
D

Daniel DeLorme

Trans said:
M = { :a => 1 }.to_module(true)
p M.a #=> 1


class Foo
def initialize( settings )
extend settings.to_module
end
end

f = Foo.new:)x => 9)
p f.x #=> 9


Thoughts?

The behavior is substantially different. With hash initialization,
reader and writer accessors are defined in the class. With this
technique it seems there's only a reader accessor, and it is only
defined per object. What methods the object responds to depend on the
initialization values.

In effect this seems to create per-object constants rather than
traditional "attributes". Maybe I can express myself better with code:

class Foo
attr_accessor :a, :b
end
h = Foo.new:)a=>1)
h.a #=> 1
h.a=2 #=> 2
h.b #=> nil
h.c #=> NoMethodError
m = Foo.new:)a=>1) #using to_module
m.a #=> 1
m.a=2 #=> NoMethodError
m.b #=> NoMethodError
m.c #=> NoMethodError
s = OpenStruct.new:)a=>1)
s.a #=> 1
s.a=2 #=> 2
s.b #=> nil
s.c #=> nil

If you can find a use for such behavior then it's fine, but I'm afraid I
can't.

Daniel
 
T

Trans

Actually your code is about initializing an instance - not a class.
Basically you just transform a Hash into some other data structure, a
module with constant accessors in your case. At the moment I fail to
see the benefit over something like this:

class Hash
def init(x)
each do |var, val|
x.send("#{var}=", val)
end
end
end

class Foo
def initialize(settings)
settings.init(self)
end
end

I find the approach using a module somewhat convoluted. Also a module
simply does not seem the right vehicle IMHO. For example, what happens
to attr_accessors that you define in that class? Either they override
your anonymous module's methods or the other way round. This does not
seem a good solution to me.

Attrsibutes would have precedence with how things work now. But that
is an interesting point. Not that we have this in Ruby presently, but
a #prepend instead of #extend could override attrs and would even
allow pre-object AOP. But in any case, if one were using this type of
constructor, one wouldn't be using attributes.
Basically what you do is you require an instance that implements a
particular method that you introduce and which has to return a module
and you promise to extend the instance with that module returned. But
this means at the same time that there always *has* to be a module. I
am not sure whether that's a good idea because for one every module
comes at a cost and I think it's not the proper means to carry some
initialization info.

Yea, I'm not really considering the cost at this point. Certainly this
is not the kind of constructor needed for for every class. I'm
thinking more along the lines of high-level integration classes --
integrating the various dependencies of an application, say. Your are
right that this is an exploration in DI/IoC.
Also, Module#to_module and Hash#to_module server
two completely different purposes: the former helps extending an
instance with a predefined module (functionality) and the latter
provides key value pairs via a particular interface.

That's true. But I think that polymorphism can be useful. Production
code uses a functional module, but the class can be tested with a mock-
up via a simple hash.
The more general solution for the "pass argument X that implements
method Y" is of course - blocks. So the pattern that you quoted
earlier is more flexible and general IMHO. I mean

def initialize(&b)
instance_eval(&b)
end

Combining that with Hash#init from above you can solve these tasks with

f = Foo.new { {:foo=>"bar"}.init(self) }
f = Foo.new { extend Container }

However, this is TOO flexible, because it offers no means of control
over what's being passed in. It basically just opens the up the object
to alteration 100% rather then just filling in "slot requirements".
For example we could do:

class Foo
def initialize( di )
di = di.to_module
raise "Unmet Dependencies" unless di.method_defined?:)x)
extend di
end
end


Maybe a wee bit of a more "realistic" example would help (it not a
real one, but it's much closer to such).

class Archiver
def initialize( copyparams, language=nil, fileutils=nil )
extend copyparams.to_module
extend language.to_module if language
extend (fileutils ? fileutils.to_module : FileUtils)
end

# copyparms

def to
raise "where to?"
end

def from
'./*' # default value
end

# language

def greeting
"copying..."
end

# main

def copy
File.cp_r(from, to)
end
end

So how about a dryrun in Spanish:

module SpanishLanguage
def greeting
"copiado..."
end
end

copyparms = {:from => '/home/trans/pics', :to=>'/backup'}
archiver = Archiver.new( copyparms, SpanishLanguage,
FileUtils::DryRun )
archiver.copy

We can alter the interface:

module EnglishLanguage
def greeting
"copying..."
end
def ask_from_where
"from where?"
end
def ask_to_where
"to where?"
end
end

module CopyParams
def to
puts ask_from_where
gets
end
def from
puts ask_to_where
gets
end
end

archiver = Archiver.new( CopyParams, EnglishLanguage )
archiver.copy


Note, I didn't test this code. So forgive any bugs. I'm sure you get
the idea though.

I'm not sure how great an idea all this really is. That's why I'm
asking about it. But is certainly seems very flexible.

T.
 
T

Trans

Attrsibutes would have precedence with how things work now. But that
is an interesting point. Not that we have this in Ruby presently, but
a #prepend instead of #extend could override attrs and would even
allow pre-object AOP.

Sorry. I was thingk of #include, not #extensd. So scratch that ...
reverse it. The extension overrides the attributes. So one _can_ do
PER-object AOP with this.

T.
 
Ad

Advertisements

R

Robert Dober

#to_module is just a convenience so one can pass in a container module
or a hash (or anything else that responds to #to_module). It's
basically the same as when you accept a string but go ahead and
generalize it to accept anything that responds to #to_s. In other
words, it's a better approach than

def initialize(settings)
case settings
when Module
...
when Hash
...

T.
Thanks for the clarification, I thaught it was part of the concept and
got confused.
It might indeed be very useful as described above.

Robert
 
Ad

Advertisements


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

Top