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

···

--


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

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
=> #<struct Evaluator other_class=A>
irb(main):007:0> e1.evaluate
=> A
irb(main):008:0> e2 = Evaluator.new B
=> #<struct Evaluator other_class=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

···

On Sun, Aug 5, 2012 at 7:19 PM, Andrea Dallera <andrea@andreadallera.com> wrote:

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

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

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:

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

Kind regards

robert

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

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 Rex

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:

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

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

···

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:

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

-- Matma Rex

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)

···

--
Posted via http://www.ruby-forum.com/.