Using extend for initialization settings?


#1

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.


#2

On 6/15/07, Trans removed_email_address@domain.invalid wrote:

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.


#3

On 15.06.2007 16:44, Trans wrote:

settings.call(self)
  end
  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)
=> #
irb(main):003:0> f.x
=> 9

:slight_smile:

robert


#4

On Jun 15, 1:05 pm, Robert K. removed_email_address@domain.invalid wrote:

  m = Module.new

irb(main):001:0> require ‘ostruct’
=> true
irb(main):002:0> f = OpenStruct.new(: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.


#5

On 6/15/07, Trans removed_email_address@domain.invalid wrote:

def initialize( settings )

p f.x #=> 9
Hmm… I think maybe my point is being missed. The goal is to
module Container
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?


#6

On 6/15/07, Robert D. removed_email_address@domain.invalid wrote:

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

Then just pass the constant into the constructor :slight_smile:

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


#7

On 6/15/07, Gregory B. removed_email_address@domain.invalid wrote:

On 6/15/07, Trans removed_email_address@domain.invalid wrote:

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


#8

On 15.06.2007 21:13, Trans wrote:

end
m.send(:module_function, k) if module_function
end
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.

  puts msg

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


#9

On 6/15/07, Robert D. removed_email_address@domain.invalid wrote:

On 6/15/07, Gregory B. removed_email_address@domain.invalid wrote:

On 6/15/07, Robert D. removed_email_address@domain.invalid wrote:

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

Then just pass the constant into the constructor :slight_smile:

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.


#10

On Jun 15, 9:19 pm, “Gregory B.” removed_email_address@domain.invalid wrote:

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.

#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.


#11

On 6/15/07, Trans removed_email_address@domain.invalid wrote:

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.

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


#12

Trans wrote:

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


#13

On 6/15/07, Gregory B. removed_email_address@domain.invalid wrote:

On 6/15/07, Robert D. removed_email_address@domain.invalid wrote:

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

Then just pass the constant into the constructor :slight_smile:
Huh… that is exactly what I have suggested, right?
I asked why #to_module .

Robert


#14

On Jun 15, 4:35 pm, Robert K. removed_email_address@domain.invalid wrote:

def init(x)
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.

  puts msg

end
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.

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.


#15

On 6/16/07, Trans removed_email_address@domain.invalid wrote:

  ...
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


#16

On Jun 15, 11:46 pm, Trans removed_email_address@domain.invalid wrote:

end
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.

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.