then we have a third one, which has a method that references the first
class:
class Evaluator
def evaluate
return ClassA
end
end
What I'd like to do is to be able to redefine what ClassA is for a given
instance of Evaluator, like so:
ev = Evaluator.new
<redefine ClassA to ClassB in some way, for ev only>
ev.evaluate #ClassB
I found out how to do this, but only class-wide:
ev = Evaluator.new
ev.evaluate #ClassA
class Evaluator
ClassA = ClassB
end
ev.evaluate #ClassB
Ideas?
Andrea
Constants are only storage, that method is returning a class object. The
normal implementation would be
def evaluate
if some condition
ClassA
else
ClassB
end
end
because the caller gets a class object, the constant (or variable, or
method call, or whatever) #evaluate happened to take the class object from
is irrelevant to the caller.
Now, I guess from your question that approach is not good for your problem,
but in that case I'd like to understand why to suggest another one.
···
On Sun, Aug 5, 2012 at 7:19 PM, Andrea Dallera <andrea@andreadallera.com>wrote:
That can lead to confusion with regard to the class names.
I know, but that shouldn't cause me problems in this particular case.
Unfortunately, I'd like to avoid storing the class in a variable and then referring to it because then the code inside what I called Evaluator would have to be aware of the mechanics. The method Evaluator#evaluate might also refer to more than one constant and, ideally, shouldn't be at all aware that one of the constant it's using is not what it originally was defined:
class A; end
class B; end
class C; end
class Evaluator
def evaluate #complex expression using one or more among A,B,C, like for example
A.method1 == :test && @myvar.is_a?(B) && C.method2 == :other_test
end
end
mind that the swap should happen only for a given instance of Evaluator, not at the class level, in a way such as
ev1 = Evaluator.new
ev2 = Evaluator.new
ev1.set(A).to(B) #something like this, or worse named ev1.set(A,B) for simplicity
ev1.evaluate #here, A.is_a?(B)#true.
ev2.evaluate #here, not so
I'm not even sure if that's possible to do in ruby at this point. Opening the singleton class (class << ev1) and defining the constant there works (in the sense that the singleton class has the constant correctly changed) but the methods defined on the class correctly ignore it.
In any case, thanks for your time
Andrea
···
--
From: "Robert Klemme" shortcutter@googlemail.com
To: ruby-talk@ruby-lang.org
Cc:
Date: Mon, 6 Aug 2012 02:28:09 +0900
Subject: Re: Redefining constants for a given instance only
On Sun, Aug 5, 2012 at 7:19 PM, Andrea Dallera wrote:
> Hello,
>
> let's say we have two empty classes:
>
> class ClassA
>
> end
>
> class ClassB
>
> end
>
> then we have a third one, which has a method that references the first
> class:
>
> class Evaluator
>
> def evaluate
> return ClassA
> end
>
> end
>
> What I'd like to do is to be able to redefine what ClassA is for a given
> instance of Evaluator, like so:
>
> ev = Evaluator.new
>
> ev.evaluate #ClassB
>
> I found out how to do this, but only class-wide:
>
> ev = Evaluator.new
> ev.evaluate #ClassA
> class Evaluator
> ClassA = ClassB
> end
> ev.evaluate #ClassB
That can lead to confusion with regard to the class names.
> Ideas?
Well, the simplest is to just store it in a member.
irb(main):001:0> Evaluator = Struct.new :other_class do
irb(main):002:1* def evaluate; other_class; end
irb(main):003:1> end
=> Evaluator
irb(main):004:0> class A;end
=> nil
irb(main):005:0> class B;end
=> nil
irb(main):006:0> e1 = Evaluator.new A
=> #
irb(main):007:0> e1.evaluate
=> A
irb(main):008:0> e2 = Evaluator.new B
=> #
irb(main):009:0> e2.evaluate
=> B
Note: you do not necessarily need the Struct, I just picked it because
that saved me a bit of typing.
Sooo... how badly do you need this? Because if the answer is "very
badly", then you could do this:
(If someone asks, I didn't recommend this. The code that follows is
braindead, wrong on multiple levels, and *will* fail in unpredictable
ways in production. You have been warned.)
class ClassA
end
class ClassB
end
class Evaluator
def evaluate
# to make the source more interesting
if true
return ClassA
else
return nil
end
end
end
# this could be done better, but anything reasonable involving
singleton_class failed on me
eval "class<<ev; " + source.gsub('ClassA', 'ClassB') + "; end"
Unfortunately, I'd like to avoid storing the class in a variable and then
referring to it because then the code inside what I called Evaluator would
have to be aware of the mechanics.
The method Evaluator#evaluate might also
refer to more than one constant
From the rest of your posting it's clear that you mean "classes" and
not "constants".
and, ideally, shouldn't be at all aware that
one of the constant it's using is not what it originally was defined:
I think you should step back a bit and rethink. Then, what is the
logic you actually want to implement?
mind that the swap should happen only for a given instance of Evaluator, not
at the class level, in a way such as
Why then are you insisting on constants? Constants are definitively
the wrong mechanism in this case.
ev1 = Evaluator.new
ev2 = Evaluator.new
ev1.set(A).to(B) #something like this, or worse named ev1.set(A,B) for
simplicity
ev1.evaluate #here, A.is_a?(B)#true.
ev2.evaluate #here, not so
I'm not even sure if that's possible to do in ruby at this point. Opening
the singleton class (class << ev1) and defining the constant there works (in
the sense that the singleton class has the constant correctly changed) but
the methods defined on the class correctly ignore it.
If you want something constantish then I suggest you use a Hash with
Symbols as keys in Evaluator like
class Evaluator
def initialize @classes = {
:a => A,
:b => B,
}
end
def evaluate
a_instance = @classes[:a].new
...
end
def set_class(label, class_object)
raise "Wrong arguments" unless label.class == Symbol &&
class_object.class == Class @classes[label] = class_object
end
end
Cheers
robert
···
On Sun, Aug 5, 2012 at 7:48 PM, Andrea Dallera <andrea@andreadallera.com> wrote:
I don't really understand why you can't pass it in when you initialize. Are
you saying that the implementation of #evaluate isn't known? Can't you just
scroll down a bit and see it? Or are you overriding it in subclasses or
something?
If so, it sounds like you need to pull out an object that wraps the
evaluate method, then you can initialize the evaluator (which would perhaps
be misnamed if you do this) with that object, and your evaluate method
could just delegate to it:
class MoreThanN
def initialize(n) @n = n
end
def call(value)
value > @n
end
end
class IsEven
def call(value)
value.even?
end
end
# in this contrived example, this class is irrelevant,
# but presumably in your more complex domain, it does something useful
class Evaluator
def initialize(strategy) @strategy = strategy
end
On Sun, Aug 5, 2012 at 12:48 PM, Andrea Dallera <andrea@andreadallera.com>wrote:
Unfortunately, I'd like to avoid storing the class in a variable and then
referring to it because then the code inside what I called Evaluator would
have to be aware of the mechanics. The method Evaluator#evaluate might also
refer to more than one constant and, ideally, shouldn't be at all aware
that one of the constant it's using is not what it originally was defined:
Here, Evaluator#run returns a module, and that sets the namespace for
the constant resolution in the eval of code.
However, as Robert points out, classes are just regular objects in Ruby,
and so can be passed around and used as such. It's perfectly normal and
reasonable to return classes in variables, so you don't have to mess
around with eval (which is often a security risk anyway)
class Evaluator
def run(a)
a ? [Ctx1::A,Ctx1::B] : [Ctx2::A,Ctx2::B]
end
end
a, b = Evaluator.new.run(true)
puts a.new.foo + b.new.bar
a, b = Evaluator.new.run(false)
puts a.new.foo + b.new.bar
That is, the variables a and b contain classes, and you use them
directly, independent of the fact that they happen to be bound to
constants like Ctx1::A.
In fact, you can also have completely anonymous classes (using
Class.new)