Forum: Ruby Using extend for initialization settings?

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
45196398e9685000d195ec626d477f0e?d=identicon&s=25 Trans (Guest)
on 2007-06-15 16:46
(Received via mailing list)
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.
31e038e4e9330f6c75ccfd1fca8010ee?d=identicon&s=25 Gregory Brown (Guest)
on 2007-06-15 17:20
(Received via mailing list)
On 6/15/07, Trans <transfire@gmail.com> 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.
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (Guest)
on 2007-06-15 19:07
(Received via mailing list)
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)
=> #<OpenStruct x=9>
irb(main):003:0> f.x
=> 9

:-)

  robert
45196398e9685000d195ec626d477f0e?d=identicon&s=25 Trans (Guest)
on 2007-06-15 21:13
(Received via mailing list)
On Jun 15, 1:05 pm, Robert Klemme <shortcut...@googlemail.com> wrote:
>
> >       m = Module.new
>
>
> 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.
31e038e4e9330f6c75ccfd1fca8010ee?d=identicon&s=25 Gregory Brown (Guest)
on 2007-06-15 21:18
(Received via mailing list)
On 6/15/07, Trans <transfire@gmail.com> 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?
703fbc991fd63e0e1db54dca9ea31b53?d=identicon&s=25 Robert Dober (Guest)
on 2007-06-15 22:07
(Received via mailing list)
On 6/15/07, Gregory Brown <gregory.t.brown@gmail.com> wrote:
> On 6/15/07, Trans <transfire@gmail.com> wrote:
<snip>
>
> 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
31e038e4e9330f6c75ccfd1fca8010ee?d=identicon&s=25 Gregory Brown (Guest)
on 2007-06-15 22:13
(Received via mailing list)
On 6/15/07, Robert Dober <robert.dober@gmail.com> 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 :)

It feels like Trans is working on some DI stuff, but I really don't
see the need for it in most practical ruby...
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (Guest)
on 2007-06-15 22:36
(Received via mailing list)
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
703fbc991fd63e0e1db54dca9ea31b53?d=identicon&s=25 Robert Dober (Guest)
on 2007-06-15 22:47
(Received via mailing list)
On 6/15/07, Gregory Brown <gregory.t.brown@gmail.com> wrote:
> On 6/15/07, Robert Dober <robert.dober@gmail.com> 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 :)
Huh.. that is exactly what I have suggested, right?
I asked why #to_module .
>
Robert
31e038e4e9330f6c75ccfd1fca8010ee?d=identicon&s=25 Gregory Brown (Guest)
on 2007-06-16 03:20
(Received via mailing list)
On 6/15/07, Robert Dober <robert.dober@gmail.com> wrote:
> On 6/15/07, Gregory Brown <gregory.t.brown@gmail.com> wrote:
> > On 6/15/07, Robert Dober <robert.dober@gmail.com> 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 :)
>
> 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.
45196398e9685000d195ec626d477f0e?d=identicon&s=25 Trans (Guest)
on 2007-06-16 04:45
(Received via mailing list)
On Jun 15, 9:19 pm, "Gregory Brown" <gregory.t.br...@gmail.com> 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.
31e038e4e9330f6c75ccfd1fca8010ee?d=identicon&s=25 Gregory Brown (Guest)
on 2007-06-16 05:04
(Received via mailing list)
On 6/15/07, Trans <transfire@gmail.com> 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.
E0526a6bf302e77598ef142d91bdd31c?d=identicon&s=25 Daniel DeLorme (Guest)
on 2007-06-16 05:20
(Received via mailing list)
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
45196398e9685000d195ec626d477f0e?d=identicon&s=25 Trans (Guest)
on 2007-06-16 05:46
(Received via mailing list)
On Jun 15, 4:35 pm, Robert Klemme <shortcut...@googlemail.com> 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.
45196398e9685000d195ec626d477f0e?d=identicon&s=25 Trans (Guest)
on 2007-06-16 06:40
(Received via mailing list)
On Jun 15, 11:46 pm, Trans <transf...@gmail.com> 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.
703fbc991fd63e0e1db54dca9ea31b53?d=identicon&s=25 Robert Dober (Guest)
on 2007-06-16 10:46
(Received via mailing list)
On 6/16/07, Trans <transfire@gmail.com> 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
This topic is locked and can not be replied to.