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


#61

On Jun 11, 2006, at 7:42 AM, Daniel S. wrote:

Yukihiro M. wrote:

Redundancy can be removed by some kind of meta programming.

This could do the job:

Here’s another idea:

class Module
def included(receiver)
receiver.extend(const_get(:ClassMethods)) if
const_defined? :ClassMethods

end
end
=> nil

module Test
module ClassMethods
def test
“I’m a class method.”
end
end
def test
“I’m an instance method.”
end
end
=> nil

class MyTest
include Test
end
=> MyTest

MyTest.test
=> “I’m a class method.”

MyTest.new.test
=> “I’m an instance method.”

James Edward G. II


#62

James Edward G. II wrote:

module ClassMethods
include Test
end

I’m not too fond of the nested module approach – I think it’s cleaner
to use a method. But that’s a matter of opinion, of course.

Daniel


#63

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

you help yourself using meta-programming features. I’d happy to add
the standard feature to achieve your goal, if it really is a generic
requirement. I like the solution that Daniel has shown in
[ruby-talk:196730].

The point is I want to make sure which approach is the way to go.

                                                    matz.

IMHO, the best approach in this thread is the access-control style
notation suggested by transfire:

importing" and it is up to the module/class to decide what it provides
–where the responsibility really belongs.

While this certainly needs to be thought about and refined, I think a
good implementation will not only answer the need at hand, but also
contribute to the cohesiveness of the mixin concept, while planting
the locus of control firmly within the mixin module.

I.e. it’s a very elegant way for the mixin module to determine exactly
what functionality gets included in the reciever, whether it is the
default instance methods, a combination of instance methods and class
methods, maybe even just the class methods, or some subset of instance
methods and class methods. Very nice in and of itself, if provided
with a sensible default which would implicitly do the right thing for
common uses.

It also avoids what I see as the major problem with adding #import:
the degradation of the cohesiveness of the inclusion concept. We
understand what it means when a class #includes Enumerable right now.
But what meaning would we ascribe to it #importing Enumerable? How is
#import different from #include? Would we now need to check both
where we before checked only for inclusion? And what if we allow the
reciever to ask only for the class methods? Thus the neat mixin
concept risks sprouting too many special cases so as to become
meaningless. Transfire’s suggestion keeps it cohesive.

Another nice thing: it resembles the access control syntax
(public/protected/private) which is conceptually similar to inclusion
control.


#64

removed_email_address@domain.invalid wrote:

It looks reasonable though I would recommend not calling the method
“class_methods”. That has the effect of freezing, in the language,
the status and purpose of singleton methods on module and class
objects. I know that “class methods” is of course a conventional way
to refer to those methods, but I’d be sorry to see it introduced at
the language level at the expense of the generality of the current
design.

I thought the same when I wrote it, but I couldn’t come up with a better
name. It’s actually hard to pinpoint a reasonable one…

Daniel


#65

On Jun 11, 2006, at 10:40 AM, Daniel S. wrote:

module ClassMethods
include Test
end

I’m not too fond of the nested module approach – I think it’s
cleaner to use a method. But that’s a matter of opinion, of course.

Oh well, to each their own.

I prefer the nested module approach because:

  1. I can envision the threads here where we try to explain when to
    define class methods with and without the method.
  2. It has a been a common Ruby idiom for literally years, so history
    works in our favor.

James Edward G. II


#66

Daniel S. wrote:

removed_email_address@domain.invalid wrote:

BTW, Daniel, have a look as Facets’ classmethods.rb or class_inherit.rb
for a better idea of how to do what you are suggesting.

Actually, that solution seems overly complicated – that may be for
performance reasons, but if we were to disregard those (as we have a
tendency to do,) I like my solution better.

I’m not sure if you understand the important difference though --it’s
not just a differnce in coding, but in actuall functionality. Your
approach has a side effect you might not have considered. You see
you’re evaluating the code directly into the base class/module. In
effect you copy the methods rather than include/extend. What happens is
that if you were to define the same method in your class, you would
over-write it completely. You could not use #super to “call-up” to the
module version. In other words your approach might seem less complicate
and thus more elegant, but it actually thwarts the use of
“inheritance”.

T.


#67

removed_email_address@domain.invalid wrote:

BTW, Daniel, have a look as Facets’ classmethods.rb or class_inherit.rb
for a better idea of how to do what you are suggesting.

Actually, that solution seems overly complicated – that may be for
performance reasons, but if we were to disregard those (as we have a
tendency to do,) I like my solution better.

Daniel


#68

On Mon, 12 Jun 2006, James Edward G. II wrote:

Oh well, to each their own.

I prefer the nested module approach because:

  1. I can envision the threads here where we try to explain when to define
    class methods with and without the method.
  2. It has a been a common Ruby idiom for literally years, so history works
    in our favor.

James Edward G. II

+1

i use this already, it’s very flexible and does ‘the right thing’:

 harp:~ > cat inheritable.rb
 module Inheritable
   Inherit = 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 RecursiveInherit
     }
   end

   module RecursiveInherit
     def included other
       Inherit[self, other]
       super
     end
   end

   extend RecursiveInherit
 end


 if $0 == __FILE__
   module M
     include Inheritable
     module ClassMethods
       def foo() 42 end
     end
   end
   class C
     include M
   end
   p C.foo



   module A
     include Inheritable
     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
   end
   p K.bar
   p K.new.foobar
 end


 harp:~ > ruby inheritable.rb
 42
 "forty-two"
 42.0

perhaps a simple RCR for inheritable.rb?

-a


#69

Hi –

On Mon, 12 Jun 2006, Daniel S. wrote:

name. It’s actually hard to pinpoint a reasonable one…
Which might be telling us something… :slight_smile:

David


David A. Black (removed_email_address@domain.invalid)
Ruby Power and Light, LLC (http://www.rubypowerandlight.com)

See what the readers are saying about “Ruby for Rails”!
http://www.rubypowerandlight.com/quotes


#70

removed_email_address@domain.invalid wrote:

I’m not sure if you understand the important difference though --it’s
not just a differnce in coding, but in actuall functionality. Your
approach has a side effect you might not have considered. You see
you’re evaluating the code directly into the base class/module. In
effect you copy the methods rather than include/extend. What happens is
that if you were to define the same method in your class, you would
over-write it completely. You could not use #super to “call-up” to the
module version. In other words your approach might seem less complicate
and thus more elegant, but it actually thwarts the use of
“inheritance”.

What I like about my solution is the interface – the implementation was
something I just jotted down. This would of course be better (feel free
to point out any errors):

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

 def included(mod)
   mod.extend(@class_methods)
 end

end

That way you don’t need to have a constant (which clutters the
namespace.)

Cheers,
Daniel


#71

On Mon, Jun 12, 2006 at 03:48:14AM +0900, Daniel S. wrote:
[…]
} What I like about my solution is the interface – the implementation
was
} something I just jotted down. This would of course be better (feel
free
} to point out any errors):
}
} class Module
} def class_methods(&block)
} @class_methods ||= Module.new
} @class_methods.module_eval(&block)
} end
}
} def included(mod)
} mod.extend(@class_methods)
} end
} end
}
} That way you don’t need to have a constant (which clutters the
namespace.)

That has two problems. First off, if you don’t call class_methods before
you first include the module (e.g. you want to reopen the module later
to
add class methods), the included method will fail since @class_methods
is
nil. Also, including the module in another module will not behave as
expected.

See http://redcorundum.blogspot.com/2006/06/mixing-in-class-methods.html
for code and an explanation. It’s similar to what you have, except:

  • a little more bulletproof
  • manages inclusion in modules gracefully
  • requires an explicit directive to provide the
    class method mixin functionality
  • provides a hook to put code in included()

} Cheers,
} Daniel
–Greg


#72

Gregory S. wrote:

… if you don’t call class_methods before
you first include the module (e.g. you want to reopen the module later to
add class methods), the included method will fail since @class_methods is
nil.

Wouldn’t this fix it?

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

 def included(mod)
   mod.extend(@class_methods ||= Module.new)
 end

end

I’m not saying this implementation is perfect; I only wrote it for this
thread.

Daniel


#73

On Mon, Jun 12, 2006 at 05:14:20AM +0900, Daniel S. wrote:
} Gregory S. wrote:
} >… if you don’t call class_methods before
} >you first include the module (e.g. you want to reopen the module
later to
} >add class methods), the included method will fail since
@class_methods is
} >nil.
}
} Wouldn’t this fix it?
}
} class Module
} def class_methods(&block)
} @class_methods ||= Module.new
} @class_methods.module_eval(&block)
} end
}
} def included(mod)
} mod.extend(@class_methods ||= Module.new)
} end
} end
}
} I’m not saying this implementation is perfect; I only wrote it for
this
} thread.

Sure, I understand. And yes, that would fix it. Take a look at the
complete
solution I posted at
http://redcorundum.blogspot.com/2006/06/mixing-in-class-methods.html

} Daniel
–Greg


#74

Yukihiro M. wrote:

It may make sense from some aspect, but not from others. Language
design is the very complicated task. :wink:

I concur with that. It is certainly not a simple thing, and I can only
admire the work you have done in the field. That’s for sure.

In this case, it introduces full multiple inheritance, which I avoided for years.

Does it really introduce multiple inheritance? It’s still a linear
heirarchy. I always thought of MI as a means to route calls to
ancestors in arbitrarily defined ways, more like a tree graph. So I’m
not sure how the modularization of a module’s “class methods” is
multiple inheritance other than the fact that a class is gaining new
behaviors from the module, making it something more than it was before.
But we can do that already with #extend.

I guess what I don’t understand is this: If I can manully separate the
“class-level” into it’s own module and resuse that, why can’t I just
reusue the class-level to begin with?

T.


#75

In message “Re: Why the lack of mixing-in support for Class methods?”
on Sun, 11 Jun 2006 23:59:17 +0900, removed_email_address@domain.invalid writes:

|> I like the solution that Daniel has shown in [ruby-talk:196730].
|
|He he. That’s ironic. Yes, Daniels solution is the right direction for
|the best meta-programming approach, but it still has some problems (as
|I pointed out in my last post).

Which is your “last post”? Is it [ruby-talk:196751]?
If so, I agree with Daniel’s reply in [ruby-talk:196770].

I have to mention I am not a big fan of the name “class_methods”
though.

|On the other hand, I can’t help but wonder if a module’s “class-level”
|were instead a “module-level”, --a module instead of a class. Or at
|least if those methods were delegated to an extending module but
|transaprently, underthehood. Then this might actually make sense:

It may make sense from some aspect, but not from others. Language
design is the very complicated task. :wink: In this case, it introduces
full multiple inheritance, which I avoided for years.

						matz.

#76

Yukihiro M. wrote:

It may make sense from some aspect, but not from others. Language
… In this case, it introduces
full multiple inheritance, which I avoided for years.

I’m wondering why it’s not possible to #include a class into a
module/class – making the only difference between classes and modules
the ability to instantiate (#allocate). That way, you could do something
like this:

class Module
def included(mod)
mod.extend(class << self; self; end)
end
end

i.e. extend the receiver with the methods and constants of the singleton
class. It sure would make this whole problem a lot easier to solve, and
avoid those pesky hacks we’re all but fond of.

Cheers,
Daniel


#77

Gregory S. wrote:

Sure, I understand. And yes, that would fix it. Take a look at the complete
solution I posted at
http://redcorundum.blogspot.com/2006/06/mixing-in-class-methods.html

Still think it’s rather complicated. Here’s what I’ve got so far, using
a slightly different approach than you:

class Module
def meta_module(&block)
@meta_module ||= Module.new
@meta_module.module_eval(&block)
extend(@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

There may still be flaws, but I don’t think it’s too bad. Notice the use
of meta_module' -- thought it was better thanclass_methods’, though
I’m not sure it’s perfect, either.

Cheers,
Daniel


#78

On Mon, Jun 12, 2006 at 07:19:10PM +0900, Daniel S. wrote:
} Gregory S. wrote:
} >Sure, I understand. And yes, that would fix it. Take a look at the
complete
} >solution I posted at
} >http://redcorundum.blogspot.com/2006/06/mixing-in-class-methods.html
}
} Still think it’s rather complicated. Here’s what I’ve got so far,
using
} a slightly different approach than you:
}
} class Module
} def meta_module(&block)
} @meta_module ||= Module.new
} @meta_module.module_eval(&block)
} extend(@meta_module)

Why should the module extend the meta_module?

} end
}
} def included(mod)
} mod.extend(@meta_module ||= Module.new)

Again, if it’s a module why should it extend the meta_module?

} if mod.kind_of? Module

This definitely won’t work. You need to check for Class, not Module.
Check
it out:

% irb
irb(main):001:0> Object.kind_of? Module
=> true

} if mod.instance_variables.include? “@meta_module
} other_meta_module = mod.instance_variable_get(:@meta_module)
} other_meta_module.send(:include, @meta_module)

Pretty much what I have.

} else
} mod.instance_variable_set(:@meta_module, @meta_module)
} end
} end
} end
} end

That last line is dangerous. Consider the following usage:

module M
meta_module {
def bar
“class method bar() was defined in M”
end
}
end

class Foo
include M
end

module N
include M

meta_module {
def bar
“pwn3d!”
end
}
end

puts Foo.bar

By setting the including module’s instance variable to be the same as
the
included module’s instance variable you allow the including module to
overwrite methods unexpectedly. That’s why I give the including module
its
own new module and have it include the included module’s module. It
preserves the inclusion semantics.

} There may still be flaws, but I don’t think it’s too bad. Notice the
use
} of meta_module' -- thought it was better thanclass_methods’, though
} I’m not sure it’s perfect, either.

I don’t see anything particularly meta about it, but that may just be
me.

} Cheers,
} Daniel
–Greg


#79

On Mon, 12 Jun 2006, Yukihiro M. wrote:

not inherit class/module-level methods, since there are cases like
Math module. So we need to prepare two different method for module
inclusion instead of one, if we reuse class-level.

what did you think about my solution matz? it’s solves the issue
cleanly and
is leverages existing practices. here it is again:

 harp:~ > cat inheritable.rb
 module Inheritable
   Inherit = 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 RecursiveInherit
     }
   end

   module RecursiveInherit
     def included other
       Inherit[self, other]
       super
     end
   end

   extend RecursiveInherit
 end


 #
 # example
 #
 if $0 == __FILE__
   module M
     include Inheritable
     module ClassMethods
       def foo() 42 end
     end
   end
   class C
     include M
   end
   p C.foo



   module A
     include Inheritable
     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
   end
   p K.bar
   p K.new.foobar
 end



 harp:~ > ruby inheritable.rb
 42
 "forty-two"
 42.0

this way the module itself exports specific class methods in a
deliberate
fashion and no new keyword need be introduced to support it.

regards.

-a


#80

On Jun 12, 2006, at 5:09 AM, Daniel S. wrote:

i.e. extend the receiver with the methods and constants of the
singleton class. It sure would make this whole problem a lot easier
to solve, and avoid those pesky hacks we’re all but fond of.

Then you have multiple inheritance.