0
irb(main):001:0> def some_method
irb(main):002:1>   begin
irb(main):003:2*     xyz = 5
irb(main):004:2>     zyx = 6
irb(main):005:2>   end
irb(main):006:1>   puts xyz
irb(main):007:1>   puts zyx
irb(main):008:1> end
=> :some_method
irb(main):009:0> 
irb(main):010:0* some_method
5
6
=> nil

In this simple example, the begin/end 'block' (really an expression) creates two variables (i.e. xyz and zyx) accessible outside of the begin/end. Of course it does: the begin/end is an expression.

What I want to do is somehow convert the begin/end so that I can break out the code in the begin/end and place it elsewhere but can still create variables in some_method. I suspect doing this would require flattening the scope but I can't figure out how to do it.

I think I want to do something like:

def some_method
  create_variables_here
  puts xyz
  puts zyx
end

define_method :create_variables_here do
  xyz=5
  zyx=6
end

Questions:

  1. Is what I want to do possible at all? I have investigated using bindings but that does not seem to work. At least not for me.
  2. Is it possible to do something like a C #include to lexically place code at a specific point in Ruby code? I don't think load or require can do that.
dedek
  • 7,981
  • 3
  • 38
  • 68
RalphShnelvar
  • 557
  • 1
  • 6
  • 17
  • Nope, I don't think that can be done (with local variables) – Sergio Tulentsev Feb 17 '17 at 16:46
  • 1
    Off the top of my head, best you can do is mass-assign variables. `xyz, zyx = create_variables`. Makes more readable code too :) – Sergio Tulentsev Feb 17 '17 at 16:47
  • 1
    What would be the use case? I mean why not just create a method that returns your xyz, zyx values? The way you want it only works with global variables as far as I know. – haffla Feb 17 '17 at 16:51

1 Answers1

0

You don't want that. What you're asking for is a side-effect, it's a function that does something other than return values and modify its own object and parameters. Worse, it directly affects the caller. Side-effects are the bane of maintainable code. They break encapsulation and make you have to carefully study every function call and consider its side-effects.

This is better solved by returning values and assigning this to variables. Ruby even supports returning multiple values, so you can return it as two values, not an Array of two values.

def create_variables_here
  xyz=5
  zyx=6

  return xyz, zyx
end

def some_method
  xyz, zyx = create_variables_here
  puts xyz
  puts zyx
end

You don't see this much because return values are usually related, and if they're related they should probably be returned as a single object. If they're not related, this might be a good indication that function is doing too many things.

That does everything you wanted, and it lets the caller decide what the variables are called. That's important, because it A) is one less thing for the caller to remember, B) it avoids conflicting with variables in the function, and C) makes it obvious where those variables came from.


As a counter example, let's say you had this.

def some_method
    do_this
    do_that
    do_some_other_thing

    puts xyz
    puts zyx
end

...where did xyz and zyx come from?! Normally the answer would be simple: they're uninitialized. But with your proposed feature, the reader now has to carefully examine each do_blah function call for side-effects that might have set xyz and zyx.

(You might say, "but any function that uses a side effect variable should have a name like create_vars_for_blah." Yes, they should, but you can't count on that. It's better to not have the dangerous feature in the first place than to trust everyone will use it with care.)

Worse, what if they all set xyz and zyx to different values? Now you have a conflict. The caller has to find this conflict, and do something convoluted like:

def some_method
    do_this
    this_xyz = xyz
    this_zyx = zyx

    do_that
    that_xyz = xyz
    that_zyx = zyx

    do_some_other_thing
    some_other_xyz = xyz
    some_other_zyx = zyx

    ...
end

Another problem with the idea is breaking local variables. One of the most important aspects of a local variable is you can, at a glance, see all the code that can affect it. For example.

def some_method
    xyz = 10
    zyx = 20

    do_this
    do_that
    do_some_other_thing

    puts xyz
    puts zyx
end

In normal Ruby, we know for a fact that will print 10 and 20. With this proposed feature, who knows what they'll be?

What you're asking for is global variables that disappear at the end of the function.

Community
  • 1
  • 1
Schwern
  • 153,029
  • 25
  • 195
  • 336
  • I thank you all for your responses. Yes, you are quite correct, I want nothing but side-effects in this particular case. – RalphShnelvar Feb 17 '17 at 20:31
  • I thank you all for your responses. Yes, you are quite correct, I want nothing but side-effects in this particular case. In one sense, it's good to know that what I want to do can't be done. In another, it does frustrate me. @haffla asked what the use case I have in mind is. In my code there is a certain pattern in controller after controller: Validate params and while validating, simultaneously build other data structures. I could, I guess, duplicate the code but, then, of course, the code wouldn't be DRY. – RalphShnelvar Feb 17 '17 at 20:45
  • @RalphShnelvar If you have a problem, and you try to solve it by injecting code into the middle of another function, you now have two problems. Instead, you probably need to rearchitect that function. – Schwern Feb 17 '17 at 20:45
  • @RalphShnelvar [You have an XY Problem](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem); rather than ask about the real problem, you've asked about your solution, and it's not a good solution. You should post a question about the real problem you're trying to solve, we can probably offer better solutions. – Schwern Feb 17 '17 at 20:48