Using #include at the instance level?

I

Intransition

I would like to use #include at an instance level, such that it
behaves just as it does at a class level. After a number of
experiments I thought for sure it would work if I ran the include
through the object's singleton. Alas, submodules remain inaccessible,
eg.

module M
def m1; "m1"; end
module N
def self.n1; "n1"; end
end
end

class X
def initialize(*mods)
(class << self; self; end).class_eval do
include *mods
end
end
def m ; m1 ; end
def n ; N.n1 ; end
end

x = X.new(M)
p x.m
p x.n #=> uninitialized constant X::N (NameError)

Is there any way to achieve this?
 
F

fkocherga

I would like to use #include at an instance level, such that it
behaves just as it does at a class level. After a number of
experiments I thought for sure it would work if I ran the include
through the object's singleton. Alas, submodules remain inaccessible,
eg.
=20
module M
def m1; "m1"; end
module N
def self.n1; "n1"; end
end
end
=20
class X
def initialize(*mods)
(class << self; self; end).class_eval do
include *mods
end
end
def m ; m1 ; end
def n ; N.n1 ; end
end
=20
x =3D X.new(M)
p x.m
p x.n #=3D> uninitialized constant X::N (NameError)
=20
Is there any way to achieve this?
=20
You may write:

class X
def eigenclass
class << self; self; end;
end
=20
def initialize(*mods)
eigenclass.instance_eval do
include *mods=20
end
end
def m ; m1 ; end
def n; eigenclass::N.n1 ; end
end

I guess constants not like methods are not searched within eigenclass, =
and that's the reason why you are getting an error.
 
R

Robert Klemme

2009/12/29 Intransition said:
I would like to use #include at an instance level, such that it
behaves just as it does at a class level. After a number of
experiments I thought for sure it would work if I ran the include
through the object's singleton. Alas, submodules remain inaccessible,
eg.

=A0module M
=A0 =A0def m1; "m1"; end
=A0 =A0module N
=A0 =A0 =A0def self.n1; "n1"; end
=A0 =A0end
=A0end

=A0class X
=A0 =A0def initialize(*mods)
=A0 =A0 =A0(class << self; self; end).class_eval do
=A0 =A0 =A0 =A0include *mods
=A0 =A0 =A0end
=A0 =A0end
=A0 =A0def m ; m1 ; end
=A0 =A0def n ; N.n1 ; end

Hm, this method above won't work as scope resolution rules for
constants are different.
=A0end

=A0x =3D X.new(M)
=A0p x.m
=A0p x.n =A0 =A0#=3D> uninitialized constant X::N (NameError)

Is there any way to achieve this?

Did you consider using #extend?

class X
def initialize(*mods)
extend *mods
end
end

What do you really want to achieve? Do you have a more telling example?

Kind regards

robert

--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 
I

Intransition

2009/12/29 Intransition <[email protected]>:






Hm, this method above won't work as scope resolution rules for
constants are different.




Did you consider using #extend?

=A0class X
=A0 =A0def initialize(*mods)
=A0 =A0 =A0extend *mods
=A0 =A0end
end

What do you really want to achieve? =A0Do you have a more telling example=
?

It's for encapsulating test cases. Eg.

TestCase.new(SomeClass) do
...
end

Within the test case block it would help to handle #include, to make
tests less verbose.

Instead of making an instance of TestCase for each case, at this point
it looks like I'll have to create a new subclass of it.
 
I

Intransition

I guess constants not like methods are not searched within eigenclass, an=
d that's the reason why you are getting an error.

You are right. That appears to be the case. I tried setting a constant
directly in the eigenclass and the instance could not see it either.
The behavior surprises me.
 
R

Robert Klemme

2009/12/29 Intransition said:
e?

It's for encapsulating test cases. Eg.

=A0TestCase.new(SomeClass) do
=A0 =A0...
=A0end

Within the test case block it would help to handle #include, to make
tests less verbose.

I see to be a bit slow today: how do you want to use modules there?
Do you want to include them in the test code? What about:

class TestCase
def new(cl, &bl)
@cl =3D cl
instance_eval(&bl)
end

alias include extend
end

TestCase.new String do
extend Foo
include Bar
end
Instead of making an instance of TestCase for each case, at this point
it looks like I'll have to create a new subclass of it.

Sorry, you lost me here.

Cheers

robert


--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 
I

Intransition

2009/12/29 Intransition <[email protected]>:
I see to be a bit slow today: how do you want to use modules there?
Do you want to include them in the test code? =A0What about:

class TestCase
=A0 def new(cl, &bl)
=A0 =A0 =A0@cl =3D cl
=A0 =A0 =A0instance_eval(&bl)
=A0 end

=A0 alias include extend
end

TestCase.new String do
=A0 extend Foo
=A0 include Bar
end

No no. You got it exactly. Problem is the module's constants are not
coming through the #extend (which is effectively the same as the
include in the singleton class). Let say I have a library:

module MyApp
module SomeSpace
class FooClass
...
end
end
end

In the test cases, instead of having to spell out
MyApp::SomeSpace::FooClass everywhere it is needed, it would be nice
to include MyApp::SomeSpace, and then just reference FooClass in the
tests.

Sorry, you lost me here.

Instead of the code you presented I'd have to do something like:

class TestCase
alias :_new, :new
def self.new(&block)
Class.new(self, &block)._new
end
end

Haven't tested it yet, but that should allow #include to work no
problem. Unfortunately it means defining all my dsl methods at the
class level --not even sure the instance level would be of any use in
this case either, in which case the ._new can be dropped -- kind of
stupid, just to get #include to work. But what else can I do?

I almost feel like I'm having a mental block and there is actually an
easy way to do this.
 
R

Robert Klemme

Btw, that method's name should have read "initialize".
No no. You got it exactly. Problem is the module's constants are not
coming through the #extend (which is effectively the same as the
include in the singleton class). Let say I have a library:

=A0module MyApp
=A0 =A0module SomeSpace
=A0 =A0 =A0class FooClass
=A0 =A0 =A0 =A0...
=A0 =A0 =A0 end
=A0 =A0end
=A0end

In the test cases, instead of having to spell out
MyApp::SomeSpace::FooClass everywhere it is needed, it would be nice
to include MyApp::SomeSpace, and then just reference FooClass in the
tests.




Instead of the code you presented I'd have to do something like:

=A0class TestCase
=A0 =A0alias :_new, :new
=A0 =A0def self.new(&block)
=A0 =A0 =A0Class.new(self, &block)._new
=A0 =A0end
=A0end

Then your block needs to contain a class definition. You can use the
block only for _one_ thing - either class / module def or code you
want to execute. Granted, you can have code executed in a class
definition as well but the definition becomes only usable *after* the
definition has been executed in its entirety. Maybe you haven't
decided yet what you want the block for and that is causing your
headaches.
Haven't tested it yet, but that should allow #include to work no
problem. Unfortunately it means defining all my dsl methods at the
class level --not even sure the instance level would be of any use in
this case either, in which case the ._new can be dropped -- kind of
stupid, just to get #include to work. But what else can I do?

I almost feel like I'm having a mental block and there is actually an
easy way to do this.

What stops you from doing the include outside?

include Your::Module::Of::Choice

TestCase.new Foo do
x =3D ChoiceClass.new
x.method_invocation(123)
end

Kind regards

robert


--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 
I

Intransition

Then your block needs to contain a class definition. =A0You can use the
block only for _one_ thing - either class / module def or code you
want to execute. =A0Granted, you can have code executed in a class
definition as well but the definition becomes only usable *after* the
definition has been executed in its entirety.

That's true. But the execution of the block is only for the definition
of things which get run later.
=A0Maybe you haven't
decided yet what you want the block for and that is causing your
headaches.

The block is for defining unit tests. Eg.

testcase SomeClass do
testunit :somemethod =3D> "some concern" do
...
end
end
What stops you from doing the include outside?

include Your::Module::Of::Choice

TestCase.new Foo do
=A0 x =3D ChoiceClass.new
=A0 x.method_invocation(123)
end

Yes, I am able to do that. But I want to avoid polluting the top
level.

You know what really gets me is that even though I get an
"uninitialized constant" error, there is no place I seem to be able to
define a #const_missing method to catch it.
 
I

Intransition

This is really becoming annoying. I can't even do it via a dynamic
class definitions either.

module M
def m1; "m1"; end
module N
def self.n1; "n1"; end
end
end

class X
class << self
alias _new new
def new(&block)
klass = Class.new(self)
klass.class_eval(&block)
klass._new
end
end

def m ; m1 ; end
def n ; N.n1 ; end
end

x = X.new do
include M
end

p x.m
p x.n #=> uninitialized constant X::N (NameError)

Did constant lookup change between 1.8.6 and 1.8.7? Seems to me this
used to be possible, and I find it unacceptable that normal constant
lookup would not apply to dynamic class definitions, let alone the
singleton classes.
 
R

Robert Klemme

2009/12/29 Intransition said:
This is really becoming annoying. I can't even do it via a dynamic
class definitions either.

=A0module M
=A0 =A0def m1; "m1"; end
=A0 =A0module N
=A0 =A0 =A0def self.n1; "n1"; end
=A0 =A0end
=A0end

=A0class X
=A0 =A0class << self
=A0 =A0 =A0alias _new new
=A0 =A0 =A0def new(&block)
=A0 =A0 =A0 =A0klass =3D Class.new(self)
=A0 =A0 =A0 =A0klass.class_eval(&block)
=A0 =A0 =A0 =A0klass._new
=A0 =A0 =A0end
=A0 =A0end

=A0 =A0def m ; m1 ; end
=A0 =A0def n ; N.n1 ; end
=A0end

=A0x =3D X.new do
=A0 =A0include M
=A0end

=A0p x.m
=A0p x.n =A0 #=3D> uninitialized constant X::N (NameError)

Did constant lookup change between 1.8.6 and 1.8.7? Seems to me this
used to be possible, and I find it unacceptable that normal constant
lookup would not apply to dynamic class definitions, let alone the
singleton classes.

AFAIK it hasn't changed and your code could never work in any version
of Ruby because the const lookup in method #n is done _statically_.
Apart from that you are defining method X#n and not <subclass of X>#n
so the lookup could never work if you would instantiate X.

Also, on a more abstract level: it is at least a bit odd to define
class X which can only ever work if you make sure you include "N"
(whichever way).

I believe you haven't yet fully analyzed the problem you want to
solve. I suggest to put it to sleep for a while and then get back
later to it. I can't help you any more because to me it is not clear
what you are trying to achieve. So far we only went through technical
issues but the problem you are trying to solve isn't cleat to me yet.

Kind regards

robert


--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 
I

Intransition

2009/12/29 Intransition <[email protected]>:










AFAIK it hasn't changed and your code could never work in any version
of Ruby because the const lookup in method #n is done _statically_.
Apart from that you are defining method X#n and not <subclass of X>#n
so the lookup could never work if you would instantiate X.

Also, on a more abstract level: it is at least a bit odd to define
class X which can only ever work if you make sure you include "N"
(whichever way).

I believe you haven't yet fully analyzed the problem you want to
solve. =A0I suggest to put it to sleep for a while and then get back
later to it. =A0I can't help you any more because to me it is not clear
what you are trying to achieve. =A0So far we only went through technical
issues but the problem you are trying to solve isn't cleat to me yet.

All I am trying to do is emulate test/unit but using a DSL.
Essentially:

class MyTest < Test::Unit::TestCase

include M

def test_N_n1
assert_equal(N.n1, "n1")
end

end

Becomes:

testcase :MyTest do

include M

testunit :N_n1 do
assert_equal(N.n1, "n1")
end

end

That's it. But I can't do it exactly b/c I can't make the include
work.
 
F

fkocherga

=20
AFAIK it hasn't changed and your code could never work in any version
of Ruby because the const lookup in method #n is done _statically_.

But it should not, consider:

module M
module N
end
end

#Class.new do <---- This does not work in 1.8.7!
class B < Object=20
include M
N
end

When 'class B < Object' replaced with 'Class.new do' the Ruby 1.8.7 =
interpreter complains about uninitialized constant N. What is the big =
reason for dynamically defined class to behave so differently? It would =
be very non obvious and unexpected behavior. Actually both cases work as =
expected in Ruby 1.9.1 and this is correct behavior according to the =
Ruby Draft.=20
 
F

fkocherga

All I am trying to do is emulate test/unit but using a DSL.
Essentially:
=20
class MyTest < Test::Unit::TestCase
=20
include M
=20
def test_N_n1
assert_equal(N.n1, "n1")
end
=20
end
=20
Becomes:
=20
testcase :MyTest do
=20
include M
=20
testunit :N_n1 do
assert_equal(N.n1, "n1")
end
=20
end
=20

Not sure why you need a DSL for, but technically it is doable (in Ruby =
1.9.1):
http://gist.github.com/265864
 
R

Robert Klemme

2009/12/29 Intransition said:
All I am trying to do is emulate test/unit but using a DSL.
Essentially:

=A0class MyTest < Test::Unit::TestCase

=A0 =A0include M

=A0 =A0def test_N_n1
=A0 =A0 =A0 assert_equal(N.n1, "n1")
=A0 =A0 end

=A0end

Becomes:

=A0testcase :MyTest do

=A0 =A0include M

=A0 =A0testunit :N_n1 do
=A0 =A0 =A0 assert_equal(N.n1, "n1")
=A0 =A0 end

=A0end

That's it. But I can't do it exactly b/c I can't make the include
work.

Ah, now I see. I believe your problem comes from the fact that you
are mixing two styles: DSL and "regular" Ruby. If you integrate
defining those modules into your DSL (i.e. not via "module ... end")
then integrating lookups might be easier as well. I don't have a
clear idea yet how to do that but maybe it's worth exploring.

Kind regards

robert

--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 
R

Rick DeNatale

To sum it up: Ruby 1.9 changed lookup rules from lexical (aka static)
to dynamic.

That change may not be permanent.

There was a long thread on ruby-core a month or two ago, prompted by a
question raised by Yehuda Katz about the problems the change was
causing for existing DSLs like ActiveRecord and RSpec. After various
proposals to modify the 1.9 behavior to make it more compatible,
Maeda-san posted this:

http://osdir.com/ml/ruby-core/2009-12/msg00001.html

Matz seems to be in favor of returning to the 1.8 behavior.

--
Rick DeNatale

Blog: http://talklikeaduck.denhaven2.com/
Twitter: http://twitter.com/RickDeNatale
WWR: http://www.workingwithrails.com/person/9021-rick-denatale
LinkedIn: http://www.linkedin.com/in/rickdenatale
 
I

Intransition

But it should not, consider:

module M
=A0 module N
=A0 end
end

#Class.new do <---- This does not work in 1.8.7!
class B < Object
=A0 include M
=A0 N
end

When 'class B < Object' replaced with 'Class.new do' the Ruby 1.8.7 inter=
preter complains about uninitialized constant N. What is the big reason for=
dynamically defined class to behave so differently? It would be very non o=
bvious and unexpected behavior. Actually both cases work as expected in Rub=
y 1.9.1 and this is correct behavior according to the Ruby Draft.

Nice clarification.

Are there two issues here? This issue and the resolution of constants
via instance_eval? Or are these two aspects of the same issue?

In either case, there is no doubt in my mind that 'class B' and 'B =3D
Class.new do' should work the same.
 
I

Intransition

Thanks for the test case! =A0It never occurred to me to do a const
lookup in a class defined with a class body so I never stumbled across
this. =A0I did a bit of research and this is what I found:

Statement from Matz about the change: "In 1.8, constant uses lexical
look-up, even within the block given to instance_eval(). =A0We changed
this behavior in 1.9 to simplify things."http://blade.nagaokaut.ac.jp/cgi= -bin/scat.rb/ruby/ruby-talk/181646

Found viahttp://eigenclass.org/hiki/withdrawn-experimental-ruby-features
- I am not so sure about the "experimental" status of this though -
maybe this is due to the last update date of the page in 2007...

Adam Gardner also made an interesting observation about the difference
between const_get and direct constant lookup:http://blade.nagaokaut.ac.jp= /cgi-bin/scat.rb/ruby/ruby-talk/333677
(I have changed the test output to easier interpret it. See attachment.)

There's also this discussionhttp://blade.nagaokaut.ac.jp/cgi-bin/vframe.r= b/ruby/ruby-core/25865?2...

And thishttp://blade.nagaokaut.ac.jp/cgi-bin/vframe.rb/ruby/ruby-talk/334= 016?...

To sum it up: Ruby 1.9 changed lookup rules from lexical (aka static)
to dynamic. =A0IMHO this explains the observed different behavior.
Thanks again for poking.

Very helpful. Thanks for going to all the trouble, Robert. I now see
the issue I am facing laser clear.
 
I

Intransition

That change may not be permanent.

There was a long thread on ruby-core a month or two ago, prompted by a
question raised by Yehuda Katz about the problems the change was
causing for existing DSLs like ActiveRecord and RSpec. =A0 After various
proposals to modify the 1.9 behavior to make it more compatible,
Maeda-san posted this:

http://osdir.com/ml/ruby-core/2009-12/msg00001.html

Matz seems to be in favor of returning to the 1.8 behavior.

What a tangle.

Seems to me there are two desired behaviors: 1) domain behavior, where
by lookup follows from the point of evaluation, and 2) closure
behavior, whereby lookup follows from the point of definition. Both
are perfectly reasonably and depend solely on the need of the
developer's usecase. There is also the compromise position, first try
domain behavior, failing that try closure behavior.

So if I understand correctly, 1.8 and older used closure behavior, 1.9
switched to domain behavior. There were issues with this change. Some
people suggested the compromise position, but Matz rejected that and
has decided to return to the original closure behavior. Is that a
correct summary?

Short of the compromise position, I would think the only complete
solution would be to allow for both approaches via different methods,
eg. instance_eval vs. instance_domain_eval, or perhaps an option,
instance_eval(&b, :closure=3D>false).
 

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,968
Messages
2,570,154
Members
46,702
Latest member
LukasConde

Latest Threads

Top