0

Say I'm writing a division algorithm script:

def current_trace
  puts "Counter: #{counter}; r: #{r}; q: #{q}"
end

r = a
q = 0
counter = 0

while r >= d
  current_trace
  r = r - d
  q = q + 1
  counter += 1
end
current_trace

I expected that calling current_trace would output the value of counter, r and q. But instead I get:

in current_trace': undefined local variable or methodcounter' for main:Object (NameError)

  1. What's the problem here?
  2. How should I write a method that will output the values of some variables named counter, r, and q, at any given point (preferably without passing arguments to the method)?
sawa
  • 165,429
  • 45
  • 277
  • 381
Jackson
  • 9,188
  • 6
  • 52
  • 77

3 Answers3

2

In this case, your current_trace method is so simple that it does not worth making it a method. But looking at your code, the reason you did so is probably because it appears more than once. That is the bad part of your code that should be improved. By reconsidering the timing of the conditional and puts, you can avoid calling puts in two different locations.

counter, q, r = 0, 0, a
loop do
  puts "Counter: #{counter}; q: #{q}; r: #{r}"
  break if r < d
  counter += 1
  q += 1
  r -= d
end
sawa
  • 165,429
  • 45
  • 277
  • 381
  • You were correct in your assumptions. This is also a good idea. My question became more of a general one about scope, but this solution works well for the script I was writing. – Jackson Sep 28 '13 at 21:55
  • I implemented your suggestion here: https://gist.github.com/jacksonrayhamilton/6747186 – Jackson Sep 28 '13 at 22:06
1

The correct thing is to write your method so it accepts parameters:

def current_trace(c, r, q)
  puts "Counter: #{ c }; r: #{ r }; q: #{ q }"
end

Then call it like:

d = 1          
r = 5          
q = 0          
counter = 0    

while r >= d                      
  current_trace(counter, r, q)    
  r = r - d                       
  q = q + 1                       
  counter += 1                    
end                               

current_trace(counter, r, q) 

Which results in:

Counter: 0; r: 5; q: 0
Counter: 1; r: 4; q: 1
Counter: 2; r: 3; q: 2
Counter: 3; r: 2; q: 3
Counter: 4; r: 1; q: 4
Counter: 5; r: 0; q: 5

(I tweaked your variable names because your code won't work because you don't show where a and d come from, but that's beside the point.)

You can use @instance, $global or CONSTANTs but those are playing games with variable scoping, which can cause problems when you accidentally change them in some method, often because you mistype them or leave off the sigil (@, $) and inadvertently create a local variable that doesn't change the one you want. Having to use @var, or $var at the main level, just to get your code to work, is a pretty good sign you're doing something wrong actually.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
  • I thought this would be kind of redundant seeing as I'd always be passing in the exact same variables. Is this "the Ruby way"? – Jackson Sep 28 '13 at 21:35
  • 1
    It's not a "Ruby way" thing, it's proper coding technique in the languages I've used that have variable scoping and functions/methods/procedures that accept parameters. Your variable names are the same, but the values being passed in change each time through which is fine. Passing the same value in each time would be a different situation. – the Tin Man Sep 28 '13 at 21:38
  • Hm. Do you think it might be better for me to have `counter`, `r` and `q` as instance variables of some object, instead? – Jackson Sep 28 '13 at 21:45
  • It is overkill for a trivial script. I've written Ruby code in utility scripts where I don't define classes or use instance variables, because there was no need. Knowing how and when to do it is important because you can waste a lot of brain-time thrashing around trying to make your code work cleanly if you hit that transition point and don't refactor. But, Ruby is nice that it allows us to decide whether to write our own classes or not. Often I write a method or two, some tests and a couple loops of some sort; Simple stuff. Other times it's thousands of lines in multiple files with classes. – the Tin Man Sep 28 '13 at 21:48
  • Well, since globals are inappropriate in this case (and the same probably goes for instance variables on main), I guess I can't wriggle out of this one. :P I'll use this pattern for similar methods from now on. Thanks for your advice. – Jackson Sep 28 '13 at 21:53
-2

I think I figured it out. counter, r and q were (apparently) only local to main's scope. I changed them to the globals $counter, $r and $q, and now I have access to them in my method.

(Realized this here: https://stackoverflow.com/a/9389532/1468130)

Community
  • 1
  • 1
Jackson
  • 9,188
  • 6
  • 52
  • 77
  • You can make them as instance variables,instead of global variables. – Arup Rakshit Sep 28 '13 at 21:26
  • That would make the variables global. Somewhat less drastic would be using instance variables (replace $ with @). – steenslag Sep 28 '13 at 21:27
  • 1
    Don't use `$globals` for this. That's a sure-fire sign that your code isn't written correctly. – the Tin Man Sep 28 '13 at 21:30
  • My method returned the results I expected, but I guess a global variable would (generally) be the wrong thing to use. This is just meant to be a command-line script, but for the sake of best practice, thanks for the advice. (I changed them to instance variables and that also worked.) – Jackson Sep 28 '13 at 21:33
  • 1
    Command-line script or not, you still want to use proper coding techniques. Ruby is object-oriented from bottom to top, so don't write it like C, Perl, Python, Java, PHP or JavaScript. Learn to write methods, use variables that are scoped correctly, then learn to write classes and create instances of those and it'll all click into place. Your code will be easier to read and maintain; Your brain will thank you at 3:00AM several months down the line. – the Tin Man Sep 28 '13 at 21:43
  • I realize I sounded stubborn in my last comment. You are totally right. I would much rather write good code than bad. – Jackson Sep 28 '13 at 21:58