Problems creating a method to define a class with its method

Dear list,

I want to create a method that creates certain kinds of classes for me.
The following doesn't work:

### start pseudo code
module My_Module
class Bigclass
  attr_accessor :test
  def Bigclass.run(para)
    @test = para
    puts "your parameter is " + @test
  end
  def Bigclass.check
    puts "I'm here"
  end
end

def My_Module.subclass(name, &code)
  eval <<-EOS
    class #{name} < Bigclass
      def #{name}.run(p)
        super(p)
       #{code.call}
      end
    end
  EOS
end
end

My_Module::subclass("Newclass"){
  puts "doesn't work" + @test.to_s
  check()
}

My_Module::Newclass.run("my parameter")
### end pseudo code

the problem is that the #{code.call} is executed before
the class is defined, so @test isn't filled yet (super(p)
isn't executed at this time.

how can I change the code, so that »code« is part of the definition
of the class (i.e. part of its method »run«)?
(the code can be very long, so putting it in a string is
not very comfortable regarding quoting etc. (I also want
the code to be highlighted in an editor).

benny

def My_Module.subclass(name, &code)
  eval <<-EOS
    class #{name} < Bigclass
      def #{name}.run(p)
        super(p)
       #{code.call}
      end
    end
  EOS
end
end

[...]

the problem is that the #{code.call} is executed before
the class is defined, so @test isn't filled yet (super(p)
isn't executed at this time.

how can I change the code, so that »code« is part of the definition
of the class (i.e. part of its method »run«)

batsman@tux-chan:/tmp$ cat ghfdgh54yiyh.rb
module My_Module
    class Bigclass
        attr_accessor :test
        def Bigclass.run(para)
            @test = para
            puts "your parameter is " + @test
        end
        def Bigclass.check
            puts "I'm here"
        end
    end

    def My_Module.subclass(name, &code)
        k = Class.new(Bigclass)
        class << k; self end.send(:define_method, "run") do |p|
            super(p)
            instance_eval(&code)
        end
        My_Module.const_set(name, k)
        k
    end
end

My_Module.subclass("Newclass") do
    puts "does work work now: " + @test.to_s
    check
end

My_Module::Newclass.run "blah"
batsman@tux-chan:/tmp$ ruby ghfdgh54yiyh.rb
your parameter is blah
does work work now: blah
I'm here

···

On Tue, Jul 13, 2004 at 07:22:27AM +0900, Benny wrote:

--
Running Debian GNU/Linux Sid (unstable)
batsman dot geo at yahoo dot com

"You, sir, are nothing but a pathetically lame salesdroid!
I fart in your general direction!"
  -- Randseed on #Linux

Mauricio Fernández wrote:

your parameter is blah
does work work now: blah
I'm here

whow!! thank you. its seems to be a bit more complicated
than I thought. I would never have found the solution on my own!!

now I know for what reason we've got "send", "define_method",
"instance_eval" and "const_set". can you explain me why "eval" was a
thought in the wrong direction?

(btw: the construct with k = Class.new... const_set(name, k) seems less
elegant than eval("class #{name} < Bigclass..") at first sight)

benny

Mauricio Fernández wrote:

> your parameter is blah
> does work work now: blah
> I'm here
whow!! thank you. its seems to be a bit more complicated
than I thought. I would never have found the solution on my own!!

now I know for what reason we've got "send", "define_method",
"instance_eval" and "const_set". can you explain me why "eval" was a
thought in the wrong direction?

Your code was

def My_Module.subclass(name, &code)
  eval <<-EOS
    class #{name} < Bigclass
      def #{name}.run(p)
        super(p)
       #{code.call}
      end
    end
  EOS
end

the basic problem is that you're opening a new scope when you do class X;
... end or def foo; ... end, so you cannot access the 'code' variable
directly; if it were possible to turn it into a literal (that is, if
it were a number, string, array, etc) you could use #{code.inspect}
to interpolate the value, but it's not possible in this case since it's
a Proc. In order to be able to access code from the method definition,
you have to use a closure, i.e. define_method.

(btw: the construct with k = Class.new... const_set(name, k) seems less
elegant than eval("class #{name} < Bigclass..") at first sight)

I tend to avoid eval <<-EOF for a number of reasons:
* syntax errors aren't caught at load time
* the editor doesn't indent/highlight the code
* (when defining classes/methods with class/def) no closures
* interpolating values in the code feels cheap (and will only work for
   some classes)
* it's eval :slight_smile: using it is a bit like recognizing the language wasn't
   powerful enough to express what I wanted 'statically'

To me, eval'ing a text with lots of #{whatever} seems less elegant, for
the reasons stated above. I prefer one of the block forms (instance_eval{}
or module_eval{ }) or define_method when it makes sense.

···

On Tue, Jul 13, 2004 at 02:32:22PM +0900, Benny wrote:

--
Running Debian GNU/Linux Sid (unstable)
batsman dot geo at yahoo dot com

Why are there always boycotts? Shouldn't there be girlcotts too?
  -- argon on #Linux