Why the lack of mixing-in support for Class methods?


#121

Hi,

In message “Re: Why the lack of mixing-in support for Class methods?”
on Wed, 14 Jun 2006 22:37:24 +0900, removed_email_address@domain.invalid writes:

|If you allow #class_extension (or whatever you want to call it) to also
|take a list of modules in addition to the block, then you’d effectively
|have it both ways. Ex-
|
| module Moo
| module ClassMethods
| def cm
| end
| end
| class_extension ClassMethods
| end

I think class_extension (or whatever) plus and include will do.

module Moo
module ClassMethods
def cm
end
end
class_extension do
include ClassMethods
end
end

|T.
|
|


#122

Yukihiro M. wrote:

I am not thinking about implementation detail yet. What do you think
how it should behave?

It’s not as simple as it seems. After manuevering past the potential
for infinite recursion on #include, I’ve see another issue: the old
Dynamic Module Inclusion problem. This is what I’m playing with:

class Module

def class_extension( *mods, &blk )
  unless @class_extension
    @class_extension = Module.new
    def @class_extension.append_features( base )
      append_features_without_extension( base )
    end
    extend @class_extension
  end
  @class_extension.module_eval(&blk) if blk
  @class_extension
end

alias_method :append_features_without_extension, :append_features

def append_features( base )
  ce = class_extension
  base.class_extension { include ce }
  append_features_without_extension( base )
end

end

Problem is that ‘extend @class_extension’ doesn’t work unless it occurs
after the module_eval(&blk). Which means later changes to the
extensions module will have no effect on modules that have already
included it. Not good. So unless I’ve misanalyzed this, I think keeping
a running list of extension modules, more like my previous
implementation post, is going to be neccessary.

T.


#123

On 6/13/06, Yukihiro M. removed_email_address@domain.invalid wrote:

similar to methods with different behavior e.g. private_methods.
|
|end

How is it more convenient?

I mean your proposal seems more convenient than the common idiom I
showed.

Phil


#124

removed_email_address@domain.invalid wrote:

On Jun 14, 2006, at 10:30 AM, removed_email_address@domain.invalid wrote:

Okay, so I’m wondering, will the annonymous module this creates get a
name of some sort instead of the ususal “#Module:0xb7ca7e1c

I was thinking that the block would just be evaluated in the context of
the targeted singleton class as vs. the context of a new anonymous
module which is then included into the targeted singleton class.

Then inheritance problems will arise – I think we need to use modules.

Daniel


#125

removed_email_address@domain.invalid wrote:

Problem is that ‘extend @class_extension’ doesn’t work unless it occurs
after the module_eval(&blk). Which means later changes to the
extensions module will have no effect on modules that have already
included it. Not good. So unless I’ve misanalyzed this, I think keeping
a running list of extension modules, more like my previous
implementation post, is going to be neccessary.

Why should the order of calling extend and adding methods matter? It
doesn’t seem to in this example, but this is much simpler than
class_extension:

module M
def foo; p “FOO”; end
end

class C
extend M
end

module M
def bar; p “BAR”; end
end

C.foo
C.bar

END

“FOO”
“BAR”


#126

On Thu, 15 Jun 2006, Daniel S. wrote:

Then inheritance problems will arise – I think we need to use modules.
can someone point out to me what’s wrong with this impl? it’s simple,
works
in a recursive way, is self documenting, is already used, and is 34
lines
long. as far as i can tell it already does every required and it very
simple
and robust.

module Mixable
Mixin = lambda do |this, other|
cm = this.const_get ‘ClassMethods’ rescue nil
im = this.const_get ‘InstanceMethods’ rescue nil
other.extend cm if cm
other.module_eval{
include im if im
extend RecursiveMixin
}
end
module RecursiveMixin
def included other
Mixin[self, other]
super
end
end
extend RecursiveMixin
end
class Object
def mixin other
sc =
class << self
self
end
sc.module_eval{ mixin other }
end
end
class Module
def mixin other
other.module_eval{ include Mixable } unless
Mixable > other
include other
end
end

if FILE == $0

example 1 - simple usage

module M
include Mixable
module ClassMethods
def foo() 42 end
end
end
class C
include M
end
p C.foo #=> 42

example 2 - note the how it works recursively

module A
include Mixable
module ClassMethods
def bar() ‘forty-two’ end
end
module InstanceMethods
def foobar() 42.0 end
end
end
module B
include A
end
class K
include B # and recurively add ClassMethods from A!
end
p K.bar #=> ‘forty=two’
p K.new.foobar #=> 42.0

example 3 - we needn’t both with Mixable if the ‘includee’ specifies

that it

wants to to a mixin

module M
module ClassMethods
def bar() 42.0 end
end
def foo() 42 end
end
class C
mixin M
end
p C.bar #=> 42.0
p C.new.foo #=> 42

and, of course, it works with objects

mixin M
p foo #=> 42
class << self
p bar #=> 42.0
end

end

-a


#127

removed_email_address@domain.invalid wrote:

Joel VanderWerf wrote:

Why should the order of calling extend and adding methods matter? It
doesn’t seem to in this example, but this is much simpler than
class_extension:

It’s a little more subtle than that, because it has to do with modules
included in modules.

Yes, I see your point. Two questions:

  1. Are chained inclusions of modules a good way to propagate class
    methods? (Do we really need this feature, for good design?)

  2. Do we really want the modules to respond to the class methods that
    they provide to classes? (I think that’s the way your class_extension
    works, but in my own test implementation I avoided that.)


#128

Joel VanderWerf wrote:

Yes, I see your point. Two questions:

  1. Are chained inclusions of modules a good way to propagate class
    methods? (Do we really need this feature, for good design?)

Yes, b/c if you don’t you won’t be able to propogate data/control
through the hierarcy. i.e. calling #super.

  1. Do we really want the modules to respond to the class methods that
    they provide to classes? (I think that’s the way your class_extension
    works, but in my own test implementation I avoided that.)

In some cases one will, so it important to have the ability. In
contrast, I could not think of any benefit of not having the modules
respond, nor any adverse effect from having them always respond. So I’d
say yes here as well.

Make sense?

T.


#129

Joel VanderWerf wrote:

Why should the order of calling extend and adding methods matter? It
doesn’t seem to in this example, but this is much simpler than
class_extension:

It’s a little more subtle than that, because it has to do with modules
included in modules.

irb(main):001:0> module M
irb(main):002:1> def foo; p “FOO”; end
irb(main):003:1> end
=> nil
irb(main):004:0> module N
irb(main):005:1> def bar; p “BAR”; end
irb(main):006:1> end
=> nil
irb(main):007:0> module O
irb(main):008:1> def baz; p “BAZ”; end
irb(main):009:1> end
=> nil
irb(main):010:0> module M
irb(main):011:1> include N
irb(main):012:1> end
=> M
irb(main):013:0> class C
irb(main):014:1> extend M
irb(main):015:1> end
=> C
irb(main):016:0> C.foo
“FOO”
=> nil
irb(main):017:0> C.bar
“BAR”
=> nil
irb(main):018:0> module M
irb(main):019:1> include O
irb(main):020:1> end
=> M
irb(main):021:0> C.baz
NoMethodError: undefined method `baz’ for C:Class
from (irb):21
from :0

T.


#130

removed_email_address@domain.invalid wrote:

can someone point out to me what’s wrong with this impl? it’s simple,
works
in a recursive way, is self documenting, is already used, and is 34 lines
long. as far as i can tell it already does every required and it very
simple
and robust.

I can of course only speak for myself, but I’m not fond of the idea of
reserving constant names when it’s really not needed – “class
extension”, as it seemingly has come to be named, should be handled
internally, within the module that defines such an extension. Even if
the ClassMethods and InstanceMethods modules are private, they would
still clutter the namespace. By using anonymous modules in instance
variables you avoid collisions (not completely of course.)

Furthermore, I feel that it’s redundant to have an entire “child” module
for the instance methods.

Not that the implementation is bad at all; I just don’t think it’s
streamlined enough to make it to the core, although that obviously isn’t
my call to make.

Cheers,
Daniel


#131

On Thu, 15 Jun 2006, Daniel S. wrote:

hi daniel-

I can of course only speak for myself, but I’m not fond of the idea of
reserving constant names when it’s really not needed – “class extension”,
as it seemingly has come to be named, should be handled internally, within
the module that defines such an extension. Even if the ClassMethods and
InstanceMethods modules are private, they would still clutter the namespace.
By using anonymous modules in instance variables you avoid collisions (not
completely of course.)

Furthermore, I feel that it’s redundant to have an entire “child” module for
the instance methods.

indeed. read the code though, it’s not needed - it’s just for symmtry.

Not that the implementation is bad at all; I just don’t think it’s
streamlined enough to make it to the core, although that obviously isn’t my
call to make.

here’s my complaint against any non-module based solution: it wildly
violates
POLS due to the change in scoping. i’m not saying it can’t be done, but
read
over these tests/demos carefully and you’ll see it’s not quite as
straightforward as you’re suggesting - espcially if you want ‘normal’
class
method definition semantics.

i think you may be able to work around some of these issues, but some
are part
of ruby.

harp:~ > ruby a.rb


recursive inclusion : meta_module


a.rb:140:in included': stack level too deep (SystemStackError) from a.rb:140:inincluded’
from a.rb:9
from a.rb:197:in show' from a.rb:194:inshow’
from a.rb:4


recursive inclusion : mixable


success


double inclusion : meta_module


a.rb:140:in append_features': cyclic include detected (ArgumentError) from a.rb:140:inincluded’
from a.rb:37
from a.rb:197:in show' from a.rb:194:inshow’
from a.rb:23


double inclusion : mixable


success


namespace pollution : meta_module


a.rb:73: N polluted! (RuntimeError)
from a.rb:197:in show' from a.rb:194:inshow’
from a.rb:60


namespace pollution : mixable


success


constant scoping : meta_module


a.rb:104:in const_get': uninitialized constant #<Module:0xb75ccc50>::C (NameError) from a.rb:104 from a.rb:131:inmeta_module’
from a.rb:102
from a.rb:197:in show' from a.rb:194:inshow’
from a.rb:100


constant scoping : mixable


success

harp:~ > cat a.rb

recursive inclusion

show(‘recursive inclusion’, :meta_module){
module M
meta_module{
def foo() :foo end
}
include M
end
}
show(‘recursive inclusion’, :mixable){
module M
module ClassMethods
def foo() :foo end
end
mixin M
end
}

double inclusion

show(‘double inclusion’, :meta_module){
module M
meta_module{
def foo() :foo end
}
end
module N
meta_module{
def bar() :bar end
}
include M
end
class C
include M
include N
end
}
show(‘double inclusion’, :mixable){
module M
module ClassMethods
def foo() :foo end
end
end
module N
module ClassMethods
def bar() :bar end
end
mixin M
end
class C
mixin M
mixin N
end
}

namespace pollution

show(‘namespace pollution’, :meta_module){
module N
meta_module{
def foo() :foo end
}
end
module M
include N
meta_module{
def bar() :foo end # defined in N!
}
end
if N.respond_to? ‘bar’
raise ‘N polluted!’
else
true
end
}
show(‘namespace pollution’, :mixable){
module N
module ClassMethods
def foo() :foo end
end
end
module M
mixin N
module ClassMethods
def bar() :foo end
end
end
if N.respond_to? ‘bar’
raise ‘N polluted!’
else
true
end
}

constant scoping

show(‘constant scoping’, :meta_module){
module N
meta_module{
C = true
const_get :C
}
end
true
}
show(‘constant scoping’, :mixable){
module N
module ClassMethods
C = true
const_get :C
end
end
true
}

BEGIN {

define two impls of class method mixin

META_MODULE_IMPL = lambda {
class Module
def meta_module(&block)
@meta_module ||= Module.new
@meta_module.module_eval(&block)
extend(@meta_module)
@meta_module
end
def included(mod)
mod.extend(@meta_module ||= Module.new)
if mod.kind_of? Module
if mod.instance_variables.include? “@meta_module
other_meta_module =
mod.instance_variable_get(:@meta_module)
other_meta_module.send(:include, @meta_module)
else
mod.instance_variable_set(:@meta_module, @meta_module)
end
end
end
end
}

MIXABLE_IMPL = lambda {
module Mixable
Mixin = lambda do |this, other|
cm = this.const_get ‘ClassMethods’ rescue nil
im = this.const_get ‘InstanceMethods’ rescue nil
other.extend cm if cm
other.module_eval{
include im if im
extend RecursiveMixin
}
end
module RecursiveMixin
def included other
Mixin[self, other]
super
end
end
extend RecursiveMixin
end
class Object
def mixin other
sc =
class << self
self
end
sc.module_eval{ mixin other }
end
end
class Module
def mixin other
other.module_eval{ include Mixable } unless
Mixable > other
include other
end
end
}

demonstrate implications of two implimentations of class method mixin

def show label, which, &code
div = ‘_’ * 79
puts div
puts “#{ label } : #{ which }”
puts div
impl = Object.const_get(which.to_s.upcase << ‘_IMPL’)
fork {
STDOUT.sync = STDERR.sync = true
impl.call
ret = code.call
puts(ret ? ‘success’ : ‘failed’)
}
Process.wait
ensure
2.times{ puts }
end

}

regards.

-a


#132

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

here’s my complaint against any non-module based solution: it wildly violates
POLS due to the change in scoping. i’m not saying it can’t be done, but read
over these tests/demos carefully and you’ll see it’s not quite as
straightforward as you’re suggesting - espcially if you want ‘normal’ class
method definition semantics.

i think you may be able to work around some of these issues, but some are part
of ruby.

[snip persuasive code examples]

You ~could~ get round the constant scoping problem with:

module N
  meta_module{
    self::C = true
    const_get :C
  }
end

which is similar to the way we deal with locals shadowing attributes
(and just as likely to catch us out). Also, using:

mod.instance_variable_set(:@meta_module, @meta_module.clone)

seems to avoid the double inclusion and namespace pollution problems.
I can’t see a way to avoid the ‘stack level too deep’ in recursive
inclusion though.

However, as I said earlier, I think your implementation is the most
straightforward (though I also think that having the InstanceMethods
module in there is obscuring your argument in the present discussion).

Regards,
Sean


#133

removed_email_address@domain.invalid wrote:

here’s my complaint against any non-module based solution: it wildly
violates
POLS due to the change in scoping. i’m not saying it can’t be done, but
read
over these tests/demos carefully and you’ll see it’s not quite as
straightforward as you’re suggesting - espcially if you want ‘normal’ class
method definition semantics.

I’m not saying it’ll be easy, but I do think it’s possible to use
anonymous modules. I’ll see if I can find time today to further develop
the existing implementation. There’ll be flaws at first, of course, but
if we could avoid the whole “in your face” mentality I think has sprung
up a bit in this thread, I’m sure we’ll prevail, as George junior
would’ve said :slight_smile:

Daniel


#134

Hi,

In message “Re: Why the lack of mixing-in support for Class methods?”
on Thu, 15 Jun 2006 04:59:18 +0900, removed_email_address@domain.invalid writes:

|> 2. Do we really want the modules to respond to the class methods that
|> they provide to classes? (I think that’s the way your class_extension
|> works, but in my own test implementation I avoided that.)
|
|In some cases one will, so it important to have the ability. In
|contrast, I could not think of any benefit of not having the modules
|respond, nor any adverse effect from having them always respond. So I’d
|say yes here as well.

I consider a module as a place holder of methods to be injected into
classes. From that view, a module does not have to have same methods
that classes will have; it is not a class. If you consider a module
as a ripped off class, you need to go for multiple inheritance.

						matz.

#135

Yukihiro M. wrote:

|implementation post, is going to be neccessary.
| append_features_without_extension( base )
| end
| extend @class_extension
| end
| @class_extension.module_eval(&blk) if blk
| @class_extension
| end

I am not going to call “extend @class_extension” for the target
module. That’s not necessary, even bad from my eyes. I think this is
where our opinions differ.

You mean to say you are going to eval the code directly into the target
module instead?

T.


#136

Hi,

In message “Re: Why the lack of mixing-in support for Class methods?”
on Thu, 15 Jun 2006 02:25:14 +0900, removed_email_address@domain.invalid writes:

|Problem is that ‘extend @class_extension’ doesn’t work unless it occurs
|after the module_eval(&blk). Which means later changes to the
|extensions module will have no effect on modules that have already
|included it. Not good. So unless I’ve misanalyzed this, I think keeping
|a running list of extension modules, more like my previous
|implementation post, is going to be neccessary.

It’s how modules work, at least under the current implementation.
If you want/have to fix it, you should fix it all.

| class Module
|
| def class_extension( *mods, &blk )
| unless @class_extension
| @class_extension = Module.new
| def @class_extension.append_features( base )
| append_features_without_extension( base )
| end
| extend @class_extension
| end
| @class_extension.module_eval(&blk) if blk
| @class_extension
| end

I am not going to call “extend @class_extension” for the target
module. That’s not necessary, even bad from my eyes. I think this is
where our opinions differ.

						matz.

#137

Update:

class Module
def class_extension(&block)
@class_extension ||= Module.new
@class_extension.module_eval(&block)
end

 def included(mod)
   if not @class_extension.nil?
     mod.extend(@class_extension)
     if mod.instance_of? Module # better way?
       unless mod.instance_variables.include? "@class_extension"
         mod.instance_variable_set(:@class_extension, Module.new)
       end
       other = mod.instance_variable_get(:@class_extension)
       other.send(:include, @class_extension)
     end
   end
 end

end

Cheers,
Daniel


#138

Okay, I’ve sorted out a few of the problems, and I hope I haven’t
created new ones.

1 class Module
2 def class_extension(&block)
3 @class_extension ||= Module.new
4 @class_extension.module_eval(&block)
5 end
6
7 def included(mod)
8 if not @class_extension.nil?
9 case mod
10 when Class
11 mod.extend(@class_extension)
12 when Module
13 unless mod.instance_variables.include? “@class_extension
14 mod.instance_variable_set(:@class_extension, Module.new)
15 end
16 other = mod.instance_variable_get(:@class_extension)
17 other.send(:include, @class_extension)
18 end
19 end
20 end
21 end

Please report any errors here, sans bashing.

Daniel


#139

This should fix the recursion issue:

class Module
def class_extension(&block)
@class_extension ||= Module.new do
def self.included(mod); end
end
@class_extension.module_eval(&block) if block_given?
@class_extension
end

 def included(mod)
   mod.extend(class_extension)
   if mod.instance_of? Module
     unless mod.instance_variables.include? "@class_extension"
       mod.send(:class_extension)
     end
     other = mod.instance_variable_get(:@class_extension)
     other.send(:include, class_extension)
   end
 end

end

I’m sure there are more issues, but I’ll fix them as they come.

Cheers,
Daniel


#140

Simplification. Tell me if I should stop spamming.

class Module
def class_extension(&block)
@class_extension ||= Module.new do
def self.included(mod); end
end
@class_extension.module_eval(&block) if block_given?
@class_extension
end

 def included(mod)
   mod.extend(class_extension)
   if mod.instance_of? Module and mod.respond_to? :class_extension
     mod.send(:class_extension).send(:include, class_extension)
   end
 end

end

The code might need a change if we decide to make #class_extension
private.

Daniel