Redefining constants for a given instance only

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
<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

On Sun, Aug 5, 2012 at 7:19 PM, Andrea D.
[email protected] wrote:

end
end
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.

Kind regards

robert

Hi Robert,

thanks for your reply. 

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 :slight_smile:

Andrea

From: “Robert K.” [email protected]
To: [email protected]
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 D.
[email protected]wrote:

Hello,

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.

Ah. The explanation is obviously left as an exercise for the reader. :slight_smile:

– Matma R.

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

ev = Evaluator.new
ev2 = Evaluator.new

file, line = ev.method(:evaluate).source_location
lines = File.readlines(file)
indent = lines[line-1][/^\s*/]
source_lines = lines.drop(line).take_while{|ln| ln=~/#{indent}\s/o }
source = lines[line-1] + source_lines.join("\n") + “end\n”

this could be done better, but anything reasonable involving

singleton_class failed on me
eval "class<<ev; " + source.gsub(‘ClassA’, ‘ClassB’) + “; end”

puts ev.evaluate #=> ClassB
puts ev2.evaluate #=> ClassA

– Matma R.

module Ctx1
class A
def foo; 1; end
end
class B
def bar; 2; end
end
end

module Ctx2
class A
def foo; 4; end
end
class B
def bar; 5; end
end
end

class Evaluator
def run(a)
a ? Ctx1 : Ctx2
end
end

code = “A.new.foo + B.new.bar”
puts Evaluator.new.run(true).module_eval code # 3
puts Evaluator.new.run(false).module_eval code # 9

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)

On Sun, Aug 5, 2012 at 7:48 PM, Andrea D.
[email protected] 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

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.

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 12:48 PM, Andrea D.
[email protected]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:

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

def evaluate(value)
@strategy.call value
end
end

Evaluator.new(MoreThanN.new 3).evaluate 2 # => false
Evaluator.new(MoreThanN.new 3).evaluate 4 # => true
Evaluator.new(IsEven.new).evaluate 3 # => false
Evaluator.new(IsEven.new).evaluate 4 # => true