recursion in scripts -- global variables required?

E

Eric Armstrong

Is it me, or is it pretty much impossible
to write a recursive script in ruby unless
you use global variables?

I have a simple countdown timer. I
set up the number of seconds to sleep,
sleep for a while, and then beep.

A single run is easily done in a script
(code below). The problems start when
I define a run method and recurse.

Unless I use global variables, there
doesn't seem to be any way for the
variables to get the data.

Is that about it?


Initial Script
--------------
#!/usr/bin/env ruby

# Beeps and character I/O
require 'curses'
include Curses

@seconds = 10
@minutes = 0
@hours = 0

#def run
timeremaining = (3600 * @hours)
+ (60 * @minutes)
+ @seconds
puts "sleeping for " + time_remaining.to_s
timeremaining.downto(0) do |i|
sleep(1)
print i.to_s + ".."
end
puts
beep; beep; beep
sleep 1
beep; beep; beep
sleep 1
beep; beep; beep

# run
#end

#run
 
M

Matthew Smillie

Is it me, or is it pretty much impossible
to write a recursive script in ruby unless
you use global variables?

I have a simple countdown timer. I
set up the number of seconds to sleep,
sleep for a while, and then beep.

A single run is easily done in a script
(code below). The problems start when
I define a run method and recurse.

Unless I use global variables, there
doesn't seem to be any way for the
variables to get the data.

Is that about it?

Well, in this case, if you're using global variables, there's not
much of a point to making the method recursive (which stores all of
its local variables on the stack). The typical way to do recursive
method is to pass the relevant information as parameters, e.g.,

def countdown(seconds)
sleep(1)
puts "tick"
sleep(1)
puts "tock"
countdown(seconds - 2)
end

I'll also mention that a deep recursion like this is a horribly
inefficient way to do a countdown timer.

matthew smillie
 
E

Eric Armstrong

There is no inefficency here. Recursion isn't
being used to count down. That's in a loop.

Recursion is only being used to restart the
timer after it expires. No stack space is
required. Any sufficiently optimizing compiler
will take the stack out of the equation.
(Whether ruby does so is another matter.)

The question is really about the nature of
instance variables in a script, I guess.

It seems they can only be initialized in a
method. (Something I keep forgetting. I'm
/so/ used to intializing instance variables
in Java.)

Defining initialize() didn't work, because
the Object object the variables belong to
in a script is already created when that
definition is processed. So it never runs.

The obvious solution (which occurs to me only
now) is to create an init() method, and
invoke it before the first call to run().

As the mathematician said...

"It is now obvious that...wait a minute...
let me check that...(works furiously)...
Yes! It /is/ obvious..."
 
E

Eric Armstrong

Doh! "while true" makes a lot more sense than
recursion, doesn't it?

Anybody have a sledgehammer? I see a fly...
 
J

Justin Collins

Eric said:
There is no inefficency here. Recursion isn't
being used to count down. That's in a loop.

Recursion is only being used to restart the
timer after it expires. No stack space is
required. Any sufficiently optimizing compiler
will take the stack out of the equation.
(Whether ruby does so is another matter.)

It doesn't, sadly. Soon, hopefully.
The question is really about the nature of
instance variables in a script, I guess.

It seems they can only be initialized in a
method. (Something I keep forgetting. I'm
/so/ used to intializing instance variables
in Java.)

Defining initialize() didn't work, because
the Object object the variables belong to
in a script is already created when that
definition is processed. So it never runs.

The obvious solution (which occurs to me only
now) is to create an init() method, and
invoke it before the first call to run().

I would think, given Ruby's nature, that the solution would be to create
a timer object. ;)

class MyTimer

def initialize(hours, minutes, seconds)
@hours = hours
@minutes = minutes
@seconds = seconds
end

#Rather than recursion, which in Ruby
#will eventually run out of stack space
def start
loop do
run
end
end

#Using nearly your exact same method
def run
#Won't see count down until the end without this
$stdout.sync = true

#Your line breaks meant that time_remaining was always zero
time_remaining = (3600 * @hours) + (60 * @minutes) +
@seconds
puts "sleeping for " + time_remaining.to_s
time_remaining.downto(0) do |i|
sleep(1)
print i.to_s + ".."
end
puts
beep; beep; beep
sleep 1
beep; beep; beep
sleep 1
beep; beep; beep
end
end

timer = MyTimer.new(0,0,15)
timer.start


Maybe that helps...

-Justin
 
S

Sean O'Halpin

Recursion is only being used to restart the
timer after it expires. No stack space is
required. Any sufficiently optimizing compiler
will take the stack out of the equation.
(Whether ruby does so is another matter.)

It doesn't - you'd quickly run out of stack space.
The question is really about the nature of
instance variables in a script, I guess.

It seems they can only be initialized in a
method.

Not so. Try this:

@foo = 1
def bar
p @foo
end

bar
#=> 1

Regards,
Sean
 
E

Eric Armstrong

Sean said:
Not so. Try this:

@foo = 1
def bar
p @foo
end

bar
#=> 1
I must be going out of my mind. I can't
tell you how many times that has seemed
to fail... At the moment, of course, it's
working fine. So either something is
failing in some strange intermittent way
(unlikely) or something is confusing the
heck out of me (very likely).
 
G

gwtmp01

I must be going out of my mind. I can't
tell you how many times that has seemed
to fail... At the moment, of course, it's
working fine. So either something is
failing in some strange intermittent way
(unlikely) or something is confusing the
heck out of me (very likely).

Be careful here. The top-level scope in Ruby is not the same thing
as class level scope:

p self # 1) top_level object
def foo
p self # 2) top_level object
end
foo

class A
p self # 3) the class object, A
def a_foo
p self # 4) an instance of A
end
end
A.new.a_foo

So instance variables in scopes 1 and 2 are actually associated with
the same object (the top_level object) while instance variables in
3 and 4 are associated with different objects (the class A and an
instance of the class A).

Gary Wright
 
E

Eric Armstrong

Justin said:
It doesn't, sadly. Soon, hopefully.
Good to know. Thanks for the info.

Nice O-O solution below, too. (Too much scripting
in my blood.)
 
E

Eric Armstrong

That's IT. That's precisely what confuses me. The same
surface syntax works completely differently in the two
different settings. It only makes sense when you
understand the deeper model -- and I CONTINUALLY forget
that initializing a variable in a class definition only
applies to the class objects, rather than instance
objects.

The same thing came just last week in another way,
in fact. It only took a week to forget again!
Thanks for the reminder.

(But I wonder if there is a way to make things less
surprising.)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,733
Messages
2,569,439
Members
44,829
Latest member
PIXThurman

Latest Threads

Top