1

I am concerned about writing self-modifying code in Ruby. And by self-modifying, I mean being able to write functions that take a code block as an input value, and output another code block based on this. (I am not asking about basics such as redefining methods at runtime.)

What I might want to do is, for example, having the following block,

_x_ = lambda { |a, b, c, d| b + c }

one can notice that arguments a and d are not used in the body at all, so I would like a function eg. #strip to remove them,

x = _x_.strip

which should produce same result as writing:

x = lambda { |b, c| b + c }

Now in Lisp, this would be easy, since Lisp code is easily manipulable data. But I do not know how to manipulate Ruby code. I can parse it eg. by

RubyVM::InstructionSequence.disassemble( x )

But how, based on this, do I write a modified block? Other examples of what I would want to do are are eg.

y = lambda { A + B }
y.deconstantize
# should give block same as saying
lambda { |_A, _B| _A + _B }

So far, in Ruby, I have never encountered a situation where I had to concede that something is not possible. But this time, gut feeling tells me that I might have encountered the fundamental weakness of beautifully structured code vs. code with little syntax to speak about (which would be Lisp). Please enlighten me.

Boris Stitnicky
  • 12,444
  • 5
  • 57
  • 74
  • Do you have some concrete use cases for this? – Nevir Feb 02 '13 at 03:20
  • To detect unused vars, you're going to have to drop down to the AST level; probably via [the melbourne gem](https://github.com/simplabs/melbourne). Chances are, though, there's a different/more convenient approach to take to cover your use case(s) – Nevir Feb 02 '13 at 03:21
  • @Nevir: But surely I do. I am not a tourist, I do not go to places where I have nothing to do :-). Imagine the following code: `ChemicalReaction.new( name: "A_and_B_assembly", rate: proc { A * B * 0.189 } )`. I want to go easy on the user, so I don't want them to write `proc { |_A, _B| _A * _B * 0.189 }`, which is what this would mean (0.189 is the rate constant) ... – Boris Stitnicky Feb 02 '13 at 03:31
  • Why not use bindings, `instance_eval`, and friends instead? You might end up saying `{ @a * @b * 0.189 }` but you could probably get around that with `a` and `b` methods, no? – mu is too short Feb 02 '13 at 03:55
  • Out of stubbornness. Some time ago, I have decided that my entity names would be capitalized, such as `ATP`, `Adenosine`, `DeoxyCytidine`. Then I wrote fake constant magic that allows me to write `ATP = Chemical Species.new`. And now, out of technical interest, or stubbornness, I find it ugly to write `rate: lambda { _ATP * _GDP * NDPK_constant }`. I want to be special by allowing `lambda { ATP * GDP * NDPK_constant }`, or at least `lambda { [ATP] * [GDP] * NDPK_constant }`, like chemists do, whatever it costs. An as you must know, capitalized constants are captured differently in blocks. – Boris Stitnicky Feb 02 '13 at 04:16
  • Boris, try to edit these into the question because its hard to read such large comment blocks because you can't use new lines or tabs. – sunnyrjuneja Feb 02 '13 at 04:20
  • @muistooshort: So you are right, practical solution to my practical problem is actually quite simple. But it's pissing me off that it's not _exactly_ what I want. – Boris Stitnicky Feb 02 '13 at 04:20
  • 1
    @SunnyJuneja: I think I will have to ask a new question. All this discussion helped me to understand that I didn't get it quite right. – Boris Stitnicky Feb 02 '13 at 04:21
  • Boris I can't help still thinking about this problem, as I find it quite interesting :) I found this which I think could be useful for you, and you might be able to cook up a solution using that: https://github.com/ngty/sourcify – Casper Feb 02 '13 at 13:08
  • Also this: https://github.com/seattlerb/ruby_parser – Casper Feb 02 '13 at 13:24
  • @Casper: I find it interesting, too, because block -> block function would be the first thing impossible to do in Ruby that I know of. – Boris Stitnicky Feb 03 '13 at 03:11

2 Answers2

2

Detecting whether a block variable is used or not is a complicated task, and you seem to be saying that you can do that by using RubyVM. So the question seems to be asking how to change the arity of the code.

If you have:

_x_ = ->a, b, c, d{b + c}

and suppose you were able to use RubyVM and come to know that a and d are not used, so you want to create

x = ->b, c{b + c}

out of _x_. Then, that is simple:

x = ->b, c{_x_.call(nil, b, c, nil)}
sawa
  • 165,429
  • 45
  • 277
  • 381
  • O.K., I must say that my example that involves arity change is not the best one, since it can sometimes be solved using `#curry` method, and that's not what I'm after. I'm after honest, Lisp like self-modification. – Boris Stitnicky Feb 02 '13 at 03:30
  • Wait, now that I read your answer... You know, I do not often use all capital when talking online, but OF COURSE YOU CAN MODIFY CODE BY HARD CODING A DIFFERENT CODE. WHAT I AM ASKING ABOUT IS SELF-MODIFYING CODE. THAT "SELF" IS NOT THE PROGRAMMER, BUT THE PROGRAM "ITSELF". NOW I FEEL LIKE TRURL FROM STAN LEM'S NOVEL TRYING TO EXPLAIN TO THE AUTOMATON HE JUST MADE THAT 2 + 2 IS NOT 5, BUT 4. – Boris Stitnicky Feb 02 '13 at 03:42
  • 1
    Boris, I find your question interesting and useful but please refrain from typing in all caps as it doesn't help the conversation in anyway. – sunnyrjuneja Feb 02 '13 at 03:49
  • @BorisStitnicky I don't understand your comment. – sawa Feb 02 '13 at 03:51
  • @SunnyJuneja Besides its content, I also didn't understand the intent of Boris's comment being typed in caps. I waited ten minutes for Boris's reply, but no hope so far. Is this expressing anger or something? – sawa Feb 02 '13 at 04:02
  • 1
    I think he is implying that you are writing out that code instead of using metaprogramming to do it but I'm not sure since I thought you answered the question correctly (but I maybe misunderstanding the question). Thanks for all your contributions none the less, Sawa! Always a pleasure when you answer a Q. – sunnyrjuneja Feb 02 '13 at 04:08
  • @SunnyJuneja: Yes, that's exactly what I was implying :-) – Boris Stitnicky Feb 02 '13 at 04:18
  • @sawa: Plus 1 anyway, thanks for reading and trying to help. Casper pointed me in some interesting directions later. – Boris Stitnicky Feb 03 '13 at 03:33
1

Boris do you necessarily have to rely on Ruby to begin with here?

Why not just create your own situation-specific language that the chemists can use just for the purpose to express their formulas in the most convenient way. Then you create a simple parser and compiler for this "chemical expression language".

What I mean is this parser and compiler will parse and compile the expressions the chemists write in their Ruby code. Then you could have:

ChemicalReaction.new(..., "[ATP] * [GDP] * NDPK_constant")

Voila: ultimate flexibility.

That's the approach I would take if usability is your main concern. Already writing out "lambda" seems like an unnecessarily cumbersome thing to me here, if all you want to do is express some domain-specific formula in the most compact way possible.

Casper
  • 33,403
  • 4
  • 84
  • 79
  • 1
    I highly appreciate your answer, it demonstrates what the phrase mongers call thinking outside the box. But trust me, Casper, I have seen enough to be sure that Ruby DSL is what I want. Dan Bernstain's saying is, do not parse. I don't want to parse. I want to take use of Ruby parser. As you can see, I already have the imperfect solution: Use variable names predictably related to constant names of chem. species outside the block, and `instance_eval` the block in the right environment. But this is not totally perfect. So I hope to learn more by asking on almighty SO. – Boris Stitnicky Feb 02 '13 at 05:12
  • @BorisStitnicky Ok that's cool. Thanks for the comment. I admire your tenacity :) I love writing parsers for situations like this, but if you can make it work beautifully in Ruby, well then I admit it's even better. – Casper Feb 02 '13 at 05:15
  • Reading your answer again after edit, if nobody gives a better one, I'll accept yours. You are right about the strings there. I just somehow... I think I'm suffering from a feature envy towards Lisp :-) – Boris Stitnicky Feb 02 '13 at 05:15
  • 1
    @BorisStitnicky - Yep, I was suspecting you might be doing that too :-) But if you CAN make it work. It's still pretty cool, but not if you need to jump through too many hoops, then it's not that cool any more. – Casper Feb 02 '13 at 05:18
  • Thanks for helping me thinking. It seems I'm gonna settle for the second best, if no magician appears and opens our eyes. – Boris Stitnicky Feb 02 '13 at 05:21