Parametric module or injecting code via class method?

T

Trans

Which approach is better: parametric module or an injecting class
method.

# Generates identity/key methods based on specified attributes.
#
# include EquateOn:)a, :b)
#
# is equivalent to including a module containing:
#
# def ==(other)
# self.a == other.a && self.b == other.b
# end
#
# def eql?(other)
# self.a.eql?(other.a) && self.b.eql?(other.b)
# end
#
# def hash()
# self.a.hash ^ self.b.hash
# end
#

def EquateOn(*fields)
code = ""
code << "def ==(o) " << fields.map {|f| "self.#{f} ==
o.#{f}" }.join(" && ") << " end\n"
code << "def eql?(o) " << fields.map {|f| "self.#{f}.eql?
(o.#{f})" }.join(" && ") << " end\n"
code << "def hash() " << fields.map {|f|
"self.#{f}.hash" }.join(" ^ ") << " end\n"
mod = Module.new
mod.module_eval( code )
mod
end

- Or -

# Generates identity/key methods based on specified attributes.
#
# equate_on :a, :b
#
# _is equivalent to_
#
# def ==(o)
# self.a == o.a && self.b == o.b
# end
#
# def eql?(o)
# self.a.eql?(o.a) && self.b.eql?(o.b)
# end
#
# def hash()
# self.a.hash ^ self.b.hash
# end

def equate_on(*fields)
code = ""
code << "def ==(o) " << fields.map {|f| "self.#{f} ==
o.#{f}" }.join(" && ") << " end\n"
code << "def eql?(o) " << fields.map {|f| "self.#{f}.eql?
(o.#{f})" }.join(" && ") << " end\n"
code << "def hash() " << fields.map {|f|
"self.#{f}.hash" }.join(" ^ ") << " end\n"
class_eval( code )
fields
end

T.
 
R

Robert Klemme

2008/3/13 said:
Which approach is better: parametric module or an injecting class
method.

Are you writing a book on best practices? There seem to be quite a
few of these questions recently. :))
# Generates identity/key methods based on specified attributes.
#
# include EquateOn:)a, :b)
#
# is equivalent to including a module containing:
#
# def ==(other)
# self.a == other.a && self.b == other.b
# end
#
# def eql?(other)
# self.a.eql?(other.a) && self.b.eql?(other.b)
# end
#
# def hash()
# self.a.hash ^ self.b.hash
# end
#

def EquateOn(*fields)
code = ""
code << "def ==(o) " << fields.map {|f| "self.#{f} ==
o.#{f}" }.join(" && ") << " end\n"
code << "def eql?(o) " << fields.map {|f| "self.#{f}.eql?
(o.#{f})" }.join(" && ") << " end\n"
code << "def hash() " << fields.map {|f|
"self.#{f}.hash" }.join(" ^ ") << " end\n"
mod = Module.new
mod.module_eval( code )
mod
end

- Or -

# Generates identity/key methods based on specified attributes.
#
# equate_on :a, :b
#
# _is equivalent to_
#
# def ==(o)
# self.a == o.a && self.b == o.b
# end
#
# def eql?(o)
# self.a.eql?(o.a) && self.b.eql?(o.b)
# end
#
# def hash()
# self.a.hash ^ self.b.hash
# end

def equate_on(*fields)
code = ""
code << "def ==(o) " << fields.map {|f| "self.#{f} ==
o.#{f}" }.join(" && ") << " end\n"
code << "def eql?(o) " << fields.map {|f| "self.#{f}.eql?
(o.#{f})" }.join(" && ") << " end\n"
code << "def hash() " << fields.map {|f|
"self.#{f}.hash" }.join(" ^ ") << " end\n"
class_eval( code )
fields
end

I opt for the second solution because the anonymous module does not
have any reuse effects - unless you cache it based on field names. :)

Kind regards

robert
 
T

Trans

Are you writing a book on best practices? There seem to be quite a
few of these questions recently. :))

Ha... I probably should be! But right now I'm just working through
some old "TODO" questions in Facets.
I opt for the second solution because the anonymous module does not
have any reuse effects - unless you cache it based on field names. :)

:) And if we do cache based on field names?

T.
 
R

Robert Klemme

2008/3/13 said:
Ha... I probably should be! But right now I'm just working through
some old "TODO" questions in Facets.
LOL



:) And if we do cache based on field names?

Aw, com' on. Interesting additional question: should order matter?
I'd say probably yes, but that will reduce reuse of cached entries.

Kind regards

robert


PS: if you run out of tasks (I know, won't happen) - I just discovered
this site:
http://www.virginmedia.com/movies/dvd/gangsterrapgenerator/index.php
 
T

Trans

2008/3/13, Trans <[email protected]>:








Aw, com' on. Interesting additional question: should order matter?
I'd say probably yes, but that will reduce reuse of cached entries.

I explored the cache idea a bit more, and it made me see why a Module
approach appealed to me over the injection method, but generating
parametric modules, even if cached seemed somehow wrong too. I derived
this instead:

module EquateOn

def ==(o)
equate_fields.all?{ |f| send(f) == o.send(f) }
end

def eql?(o)
equate_fields.all?{ |f| send(f).eql?(o.send(f)) }
end

def hash
equate_fields.inject(0){ |memo, f| memo ^ send(f).hash }
end

end

def EquateOn(*fields)
define_method:)equate_fields){ fields }
return EquateOn
end

Much better, but I still wonder if it is enough to be preferable over
the a helper method.

T.
 
A

ara howard

Which approach is better: parametric module or an injecting class
method.

i would say neither and both. it's saner to decouple them and then
recouple - giving the best of both worlds with the same amount of code:


cfp2:~ > cat a.rb
module Equate
module Methods
def equate a, b
module_eval <<-code
def ==(other)
self.#{ a } == other.#{ a } && self.#{ b } == other.#{ b }
end
def eql?(other)
self.#{ a }.eql?(other.#{ a }) && self.#{ b }.eql?
(other.#{ b })
end
def hash()
self.#{ a }.hash ^ self.#{ b }.hash
end
code
end
end

def Equate.included other
other.send :extend, Methods
end

def Equate.on a, b
Module.new{
include Equate
equate a, b
}
end
end

class C < Struct.new:)a, :b)
include Equate.on:)a, :b)
end

p C[4,2] == C[4,2] #=> true
p C[4,2] == C[4,3] #=> false

class D < Struct.new:)a, :b)
include Equate
equate :a, :b
end

p D[4,2] == D[4,2] #=> true
p D[4,2] == D[4,3] #=> false



cfp2:~ > ruby a.rb
true
false
true
false



a @ http://codeforpeople.com/
 
R

Robert Klemme

Which approach is better: parametric module or an injecting class
method.

i would say neither and both. it's saner to decouple them and then
recouple - giving the best of both worlds with the same amount of code:


cfp2:~ > cat a.rb
module Equate
module Methods
def equate a, b
module_eval <<-code
def ==(other)
self.#{ a } == other.#{ a } && self.#{ b } == other.#{ b }
end
def eql?(other)
self.#{ a }.eql?(other.#{ a }) && self.#{ b }.eql?
(other.#{ b })
end
def hash()
self.#{ a }.hash ^ self.#{ b }.hash
end
code
end
end

def Equate.included other
other.send :extend, Methods
end

def Equate.on a, b
Module.new{
include Equate
equate a, b
}
end
end

class C < Struct.new:)a, :b)
include Equate.on:)a, :b)
end

p C[4,2] == C[4,2] #=> true
p C[4,2] == C[4,3] #=> false

class D < Struct.new:)a, :b)
include Equate
equate :a, :b
end

p D[4,2] == D[4,2] #=> true
p D[4,2] == D[4,3] #=> false



cfp2:~ > ruby a.rb
true
false
true
false

Using a Struct generated class as base class is a bad example IMHO
because those classes do already have the equation properties.

At the moment the only advantage I see in using modules is avoidance of
namespace pollution. Personally I'd just have a method in class Module
equate_on which defines methods as shown.

Btw, while we're at it, I'd also like order_on which defines <=> based
on fields given. :)

Kind regards

robert
 
A

ara howard

Using a Struct generated class as base class is a bad example IMHO
because those classes do already have the equation properties.

heh - yeah bad example. i just used struct to avoid defining atts 'a'
and 'b' and an initializer - i think the example stands with that
though.
At the moment the only advantage I see in using modules is avoidance
of namespace pollution. Personally I'd just have a method in class
Module equate_on which defines methods as shown.


well namspace pollution is no small thing! the other huge advantage
is that running rdoc over something like

class C
module ClassMethods
end
end

is about a billion times better than running it over

class C
singleton_class.module_eval ....

not to mention grep et all.


the other huge advantage of using modules is subtle: if you start out
with code such as

module M

def self.included other
add_equate_method_to other
end

end

and, over the course of your project end up with

module M
X = 4
Y = 2
end


then the result of

class C
include M
end

is that X and Y are dumped into C *even though the included block was
purposely setup to define what happens logically for module
inclusion*. by carving out the target of inclusion one can do

module M
module Methods
end

def self.included other
other.extend Methods
end
end

and dump constants into M to your heart's content without
inadvertently dumping them into every class that wanted a little class
method.

this last bit i just recently have been using - but born out from long
debugging session ;-)

cheers.

a @ http://drawohara.com/
 
R

Robert Klemme

heh - yeah bad example. i just used struct to avoid defining atts 'a'
and 'b' and an initializer - i think the example stands with that
though.



well namspace pollution is no small thing! the other huge advantage
is that running rdoc over something like

class C
module ClassMethods
end
end

is about a billion times better than running it over

class C
singleton_class.module_eval ....

not to mention grep et all.

The alternative - at least as far as I see it - is

class Module
def equate_on(fields)
...
end
end

class AnyClass
attr_accessor :foo, :bar
equate_on :foo
end
the other huge advantage of using modules is subtle: if you start out
with code such as

module M

def self.included other
add_equate_method_to other
end

end

and, over the course of your project end up with

module M
X = 4
Y = 2
end


then the result of

class C
include M
end

is that X and Y are dumped into C *even though the included block was
purposely setup to define what happens logically for module
inclusion*. by carving out the target of inclusion one can do

module M
module Methods
end

def self.included other
other.extend Methods
end
end

and dump constants into M to your heart's content without
inadvertently dumping them into every class that wanted a little class
method.

this last bit i just recently have been using - but born out from long
debugging session ;-)

Yeah, but you can achieve the functionality without modules at all - at
the small cost of defining a single instance method in class Module. I
guess you have some other code in mind (at least the one that you had to
debug) but I am still not convinced that using modules is the best
approach for such a basic thing as defining equivalence between
instances of a class. My .02 EUR...

Kind regards

robert
 
S

Sean O'Halpin

Yeah, but you can achieve the functionality without modules at all - at
the small cost of defining a single instance method in class Module.

I suspect that Trans is looking for a general principle to follow and
is using equality as an example (but I'm open to be corrected of
course :)
While adding a single method to Module may seem like a small cost if
you're only using it in a single program,
it becomes unsustainable when you're managing a large public library
like Facets which is trying to play nice with frameworks like Rails
(which doesn't go out of its way to be polite).

With the unbridled rise in Ruby 'metaprogramming', a zillion monkeys
are leaving their cr**p all over Object, Module and Class.
I thoroughly endorse any effort to avoid namespace pollution.

Also, Ara's point about unintended constant propagation is a very good
one. I recently got stung by an otherwise innocent VERSION constant
which screwed up all manner of unrelated things.

So my tuppence worth goes towards something like Ara's version of the
module inclusion method.

Kind regards,
Sean
 
R

Robert Klemme

I suspect that Trans is looking for a general principle to follow and
is using equality as an example (but I'm open to be corrected of
course :)

Maybe. IIRC he did not state this.
While adding a single method to Module may seem like a small cost if
you're only using it in a single program,
it becomes unsustainable when you're managing a large public library
like Facets which is trying to play nice with frameworks like Rails
(which doesn't go out of its way to be polite).

Agreed. But that's a different story - I reacted to a very concrete
question. Maybe it was my fault to not read it as a more general
question - OTOH the original question was rather short and you can read
it both ways IMHO.
With the unbridled rise in Ruby 'metaprogramming', a zillion monkeys
are leaving their cr**p all over Object, Module and Class.
I thoroughly endorse any effort to avoid namespace pollution.
Agreed.

Also, Ara's point about unintended constant propagation is a very good
one. I recently got stung by an otherwise innocent VERSION constant
which screwed up all manner of unrelated things.

Yeah, but happens only if you include modules. My point here was that
the same behavior can be achieved by a single method in class Module.
And in this particular case (defining equivalence) it is so general that
it should even be part of the std lib. No module inclusion, no constant
propagation.
So my tuppence worth goes towards something like Ara's version of the
module inclusion method.

"tuppence" LOL - was this a typo or is it a common idiom I haven't seen
before? (Sounds a bit like these plastic boxes...)

Kind regards

robert
 
S

Sean O'Halpin

Maybe. IIRC he did not state this.

Indeed. Perhaps Trans can enlighten us as to the real application?
(Are you listening Tom?)
Agreed. But that's a different story - I reacted to a very concrete
question. Maybe it was my fault to not read it as a more general
question - OTOH the original question was rather short and you can read
it both ways IMHO.

True enough. As I said, I'm only surmising what Trans is after. I'm
not criticising your response to the concrete example (after all,
it is both pragmatic and to the point). Rather, I'm taking the
question Trans posed as an opportunity to discuss what I consider best
practice in the more general case.
Yeah, but happens only if you include modules. My point here was that
the same behavior can be achieved by a single method in class Module.
And in this particular case (defining equivalence) it is so general that
it should even be part of the std lib.

Agreed. (Lots of other things I'd like to see there too, and some I'd
rather see not there - but that's a different story :)
No module inclusion, no constant
propagation.
True.


"tuppence" LOL - was this a typo or is it a common idiom I haven't seen
before? (Sounds a bit like these plastic boxes...)

Just showing my age & nationality (old, British :) Tuppence = 2 pence,
3 pence was 'thruppence', the coin 'a thruppenny bit' and we used to
call the six pence coin 'a tanner'.)
Kind regards

robert
All the best,
Sean
 
T

Trans

Indeed. Perhaps Trans can enlighten us as to the real application?
(Are you listening Tom?)

There is the specific application which applies to equate_on,
compare_on (aliased also as sort_on) and possibly initialize_on.
However, seeing that's already three cases, a general principle does
come into consideration.
True enough. As I said, I'm only surmising what Trans is after. I'm
not criticising your response to the concrete example (after all,
it is both pragmatic and to the point). Rather, I'm taking the
question Trans posed as an opportunity to discuss what I consider best
practice in the more general case.

In the above cases, with the exception of initialize_on (I may just
dump that one), I went with the capitlaize method approach, but used a
little bit of Ara's concept. Eg.

module Equatable

def self.identify(base, *accessors)
base.send:)define_method, :identity){ accessors }
self
end

def ==(o)
identity.all?{ |a| send(a) == o.send(a) }
end

def eql?(o)
identity.all?{ |a| send(a).eql?(o.send(a)) }
end

def hash
identity.inject(0){ |memo, a| memo ^ send(a).hash }
end

end

class Module
def Equatable(*fields)
Equatable.identify(self, *accessors)
end
end

I did something similar with Comparable, albeit a little different b/c
Comparable already exists in the standard library. I figure this is a
good way to go b/c the method doesn't really take up any new name
space because the module already exists.
Agreed. (Lots of other things I'd like to see there too, and some I'd
rather see not there - but that's a different story :)

I'd like to dig into these questions a bit more as well --except for
the fact that I am sick of them. To be straight forward about it, Ruby
itself is torn. It makes method extensions a snap, which we all know
has proven itself as a distinct advantage of Ruby. But in turn it's
inheritance system does nothing to make it easy to do it in a robust
way. In short, I've come to the (long and drawn out) conclusion that
Ruby's mixin system is crap.

T.
 
T

Trans

True enough. As I said, I'm only surmising what Trans is after. I'm
not criticising your response to the concrete example (after all,
it is both pragmatic and to the point). Rather, I'm taking the
question Trans posed as an opportunity to discuss what I consider best
practice in the more general case.

I'd like to present another example --a simpler one in a sense, of
direct extension vs. using modules. I will leave out most the code for
brevity sakes but consider, but here is an example where I took code
that was straight core extensions and "modulized" it:

# -- random.rb

# Can't modulize Kernel b/c of double module inclusion problem.

module Kernel

def maybe(chance = 0.5, &block)
if block then
yield if rand < chance
else
rand < chance
end
end

...
end

module Random

def self.append_features(mod)
if mod == ::Kernel # not needed
mod.send:)include, Random::Kernel)
elsif mod == ::Array
mod.send:)include, Random::Array)
elsif mod == ::Hash
mod.send:)include, Random::Hash)
elsif mod == ::String
mod.send:)include, Random::String)
else
raise TypeError
end
end

#

module Array

def at_rand
self.at( rand( size ) )
end

...
end

#

module Hash

def rand_key
keys.at( rand(keys.size) )
end

...
end

#

module String

def self.included(base)
base.extend(Self)
end

module Self

def random(max_length = 8, char_re = /[\w\d]/)
# gmosx: this is a nice example of input parameter checking.
# this is NOT a real time called method so we can add this
# check. Congrats to the author.
raise ArgumentError.new('char_re must be a regular
expression!') unless char_re.is_a?(Regexp)
string = ""
while string.length < max_length
ch = rand(255).chr
string << ch if ch =~ char_re
end
return string
end

def rand_letter
(rand(26) + (rand(2) == 0 ? 65 : 97) ).chr
end

end

def at_rand( separator=// )
#separator = self.class.patterns( separator )
self.split(separator,-1).at_rand
end

...

end

end

class Array
include Random
end

class Hash
include Random
end

class String
include Random
end

Other Ruby coder's might look at this and think, "clever". I look at
it and think, "obfuscated". First off there is Kernel, b/c of the
double module inclusion problem it can't participate in the whole
"clever" affair. Then there is Self and the included callback hack to
get the class-level methods in there. And on top of it all, there is
the simple fact that I had to create and include a bunch of new
modules and all the overhead that comes with them -- when all I want
to do add a few methods to the core classes/modules. To throw in a
little irony as well, the main reason I even bothered to do this, was
not to help avoid method clash (a fairly unavoidable problem really),
but to get better results for RDoc --and that fact really makes me
sick.

T.
 
T

Trans

I'd like to dig into these questions a bit more as well --except for
the fact that I am sick of them. To be straight forward about it, Ruby
itself is torn. It makes method extensions a snap, which we all know
has proven itself as a distinct advantage of Ruby. But in turn it's
inheritance system does nothing to make it easy to do it in a robust
way. In short, I've come to the (long and drawn out) conclusion that
Ruby's mixin system is crap.

Okay, that's overly harsh. (Can you tell I had a bad day yesterday?)
"Weak" is a better description.

T.
 
R

Robert Dober

Okay, that's overly harsh. (Can you tell I had a bad day yesterday?)
"Weak" is a better description.
Not really ;)
But it is not Ruby's fault it is the way Mixins are, that's why I am
so fond of traits.
 
R

Robert Klemme

2008/3/18 said:
I'd like to present another example --a simpler one in a sense, of
direct extension vs. using modules. I will leave out most the code for
brevity sakes but consider, but here is an example where I took code
that was straight core extensions and "modulized" it:

# -- random.rb

<snip>100+ lines</snip>

Did you say "brevity"? LOL
Other Ruby coder's might look at this and think, "clever". I look at
it and think, "obfuscated". First off there is Kernel, b/c of the
double module inclusion problem it can't participate in the whole
"clever" affair. Then there is Self and the included callback hack to
get the class-level methods in there. And on top of it all, there is
the simple fact that I had to create and include a bunch of new
modules and all the overhead that comes with them -- when all I want
to do add a few methods to the core classes/modules. To throw in a
little irony as well, the main reason I even bothered to do this, was
not to help avoid method clash (a fairly unavoidable problem really),
but to get better results for RDoc --and that fact really makes me
sick.

Indeed. It sounds rather RDoc should be fixed instead of code tweaked
to get desired results.

Kind regards

robert
 

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

Members online

Forum statistics

Threads
473,776
Messages
2,569,603
Members
45,190
Latest member
ClayE7480

Latest Threads

Top