I need to make few scripts for tasks I do often manually, but last time I programmed something was almost 20 years ago in high school, we used some BASIC dialect. Since no one uses BASIC nowdays, I decided to give a try to Ruby (I also tried Python, but it's strict indentation is driving me insane). I watched Ruby Essentials Video from Lynda.com (twice), and I pretty much understand almost all of it, but still, I'm having trouble to adapt how to structure my programs (without using of GOTO) Here's a simple example written in FreeBASIC (it works) --------------------------------------------------------------------- Dim As Integer strKeyPress menu_00: CLS PRINT "what do you desire:" PRINT PRINT "1 - open menu_01" PRINT "2 - open menu_02" PRINT "3 - open menu_03" PRINT "4 - open menu_04" PRINT "q - Quit" REM is comment line, same as ' ... code for Q is 81, q = 113 Do strKeyPress = GetKey Loop Until strKeyPress <> 0 '49 represents number 1 on a keybord, and so on if strKeyPress = 49 THEN goto menu_01 elseif strKeyPress = 50 THEN goto menu_02 elseif strKeyPress = 51 THEN goto menu_03 elseif strKeyPress = 52 THEN goto menu_04 elseif strKeyPress = 81 or strKeyPress = 113 THEN goto programendshere else print print "Wrong Choice, Try Again!" sleep 1000 menu_00_endif: end if goto menu_00 menu_01: cls print print "menu 01" sleep 1000 goto menu_00_endif menu_02: cls print print "menu 02" sleep 1000 goto menu_00_endif menu_03: cls print print "menu 03" sleep 1000 goto menu_00_endif menu_04: cls print print "menu 04" sleep 1000 goto menu_00_endif programendshere: End 0 ------------------------------------------------------------------ So, what would be the best practice to make this work in modern languages, like Ruby ? Here are some ideas I have, would be nice to know if it's good thinking or wrong: 1- make all menues part of a same Class, so I can use instance variables (I will need to do some calculations) 2- define every menu as separate method, so you travel from root menu_00 (method) --> menu_01 (which is separate method) --> ... etc go back and forth ... #could this be memory demanding if there is too many sub-menu branching ??? 3- define every menu as separate method, but you DO NOT start new method from INSIDE of other method. It goes something like this: menu_00 returns value to a loop, that value is name of sub-menu (menu_01, separate method) ... I'd then have to use --> method(name_of_returned_value).call() This looks better than option #2, but my attempts to make it work so far failed ... not sure if it is even possible to achieve at all 4- every menu is separate method, but they only have puts "enter choice" lines, no other code, and a $global variable which I will use later in IF loops to detect inside which menu (method) I am: if $global == "00" and userInputChoice == "1" #if you are currently inside root menu 00, and user input is "1" menu_01() $global = "01" ... etc I think this could easily work, though, might not look nice ................................................................. So basically, I'd like to know which way should I go for, 2,3 or 4, or there is maybe even something else? And, also is idea #1 ok ? I hope this wasn't too long to read for my first question here, starting something new is always hardest part for me
on 2013-02-15 17:31
on 2013-02-15 17:37
Stu P. D'naim wrote in post #1097111: > I need to make few scripts for tasks I do often manually, but last time > I programmed something was almost 20 years ago in high school, we used > some BASIC dialect. Since no one uses BASIC nowdays, I decided to give a > try to Ruby (I also tried Python, but it's strict indentation is driving > me insane). > > I watched Ruby Essentials Video from Lynda.com (twice), and I pretty > much understand almost all of it, but still, I'm having trouble to adapt > how to structure my programs (without using of GOTO) > > > Here's a simple example written in FreeBASIC (it works) > --------------------------------------------------------------------- > Dim As Integer strKeyPres Yes, It has. You can try "catch - throw" in Ruby's world. Thanks
on 2013-02-15 17:47
Love U Ruby: I'm really not sure what you meant by that response, but it's irrelevant and inaccurate (there is no catch in Ruby, it's rescue)... To answer the OP's question, you'd probably want to first pick up a good book on Ruby (or one of the many tutorials online). You're probably going to want to look into first creating methods where you normally had headers in your BASIC code and replacing the GOTO declarations with just the method name. I'd also take a look at the "case...when" syntax to replace your if statements. Switching to an object oriented language from BASIC is tough but not impossible, especially if you're just replacing some utility scripts. I'm sure there's a "menu" gem out there too somewhere. Hopefully that cleared up at least something, Ryan Victory
on 2013-02-15 17:56
Ryan Victory wrote in post #1097114: > Love U Ruby: I'm really not sure what you meant by that response, but > it's irrelevant and inaccurate (there is no catch in Ruby, it's > rescue)... > before going to challenge other you should "GOOGLE" - here I did for you. Please read it : http://www.devalot.com/articles/2012/03/ruby-goto
on 2013-02-15 18:03
I did consider jokingly showing the "enable jokes" compiler flag that turned on the __goto__ method...I'm actually slightly horrified that Ruby has anything resembling goto for hopping around in code. I'll concede that I did not know about this, however I'll stand by the idea that it's terrible coding practice (except for in very particular circumstances where goto is the most elegant solution). Either way, your original suggestion is just bad advice. I'll stand by that. -Ryan
on 2013-02-15 18:10
Ryan Victory wrote in post #1097118: > I did consider jokingly showing the "enable jokes" compiler flag that > turned on the __goto__ method...I'm actually slightly horrified that > Ruby has anything resembling goto for hopping around in code. I'll > concede that I did not know about this, however I'll stand by the idea > that it's terrible coding practice (except for in very particular > circumstances where goto is the most elegant solution). > > Either way, your original suggestion is just bad advice. I'll stand by > that. > > -Ryan OP asked is it there? I have given the reference to him- yes Ruby has. i didn't advice him to use that. So please don't need to stand by. You didn't aware what Ruby has, so now you got it. That's good. chill...
on 2013-02-15 18:29
Be careful saying "no one uses BASIC nowdays". I have no doubt there is a subset of people out there still using BASIC. =)
on 2013-02-15 18:43
There is no one way to replace GOTO. GOTO is at the heart of many
structured control structures. What you need to do is think about what
you are trying to do and where you are trying to go when you are done.
In your code, you have: "goto menu_01". After the sequence of
statements starting at the menu_01 label, you have: "goto
menu_00_endif". If you returned from your sequence to the goto, you
would have a subroutine. I'm not familiar with FreeBASIC, but in
Commodore BASIC, you could replace your GOTO with GOSUB and the GOTO at
the end of the sequence with RETURN. That would eliminate the need for
the "menu_00_endif" label. This is a subroutine and can be replaced by
a method in Ruby. I think in FreeBASIC, your sub may be defined as
follows:
' I think "goto menu_01" may be replaced by
Menu_01()
' and the subroutine by
Sub Menu_01
...
End Sub
You also have a "goto menu_00" after the "End If" statement. The
menu_00 and its goto can be replaced with a loop.
Using a while loop would be a good idea. The loop using goto menu_00 is
an infinite loop, but you use "goto programendshere" to exit it. By
setting a condition variable for the loop, you can exit the loop by
changing the variable. I'm taking a guess at the FreeBASIC grammar
here:
stillChoosing = True
Do While stillChoosing
...
ElseIf strKeyPress = 81 Or strKeyPress = 113 Then
stillChoosing = False
Else
...
Loop
It looks like your code is somewhat structured already. You just need
to learn how control structures are built from gotos and other
statements to properly replace them.
Finally, since all you your menus will have the same structure, it make
sense that you can replace multiple subroutines with just one. You can
pass the text of each menu and return the result. (In Basic, that will
make it a Function rather than a Sub, I believe! But either way, it
would be a Method in Ruby.)
Putting all that together in Ruby might look something like this:
# Displays a menu and gets a response back.
def showMenu menuText
# Display the menu
puts menuText
puts
# Display a prompt
print "What do you desire? "
# Get a string as a response. (The user will have to press enter,
unlike your example.)
# chomp removes a newline from the end of the string
# downcase makes the string lowercase so "Q" and "q" are returned as
"q"
gets.chomp.downcase
end
# Here I am putting the menu text in a long string.
menu_00 = %q{menu 00
1 - open menu_01
2 - open menu_02
3 - open menu_03
4 - open menu_04
q - Quit}
# The same can be done for these when they are fleshed out
menu_01 = "menu 01"
menu_02 = "menu 02"
menu_03 = "menu 03"
menu_04 = "menu 04"
stillChoosing = true
while stillChoosing do
case showMenu(menu_00)
when "1"
response = showMenu(menu_01)
# do something with response here
when "2"
response = showMenu(menu_02)
# do something with response here
when "3"
response = showMenu(menu_03)
# do something with response here
when "4"
response = showMenu(menu_04)
# do something with response here
when "q"
stillChoosing = false
else
puts "Wrong Choice, Try Again!"
puts
end
end
on 2013-02-15 18:49
Charles Caldwell wrote in post #1097135: > Be careful saying "no one uses BASIC nowdays". I have no doubt there is > a subset of people out there still using BASIC. =) Actually I like FreeBASIC, it's cross-platform, syntax I'm somewhat familiar with ... but it lacks tutorials, books, videos etc .. also, Ruby has much more string manipulation methods (which I need for my script). And I know there is not GOTO in Ruby, I was asking for advice how should I re-write code I posted above in a Ruby way ? Looks like no one read entire original post :( ... (but I don't blame you, it's really long)
on 2013-02-15 18:50
I think GOTO gets a bad rap. Actually, I find the COMEFROM command far more terrifying. It's mentioned in the comments... Jamal
on 2013-02-15 19:14
On Fri, Feb 15, 2013 at 9:49 AM, Stu P. D'naim <lists@ruby-forum.com> wrote: > > And I know there is not GOTO in Ruby, I was asking for advice how should > I re-write code I posted above in a Ruby way ? Looks like no one read > entire original post :( ... (but I don't blame you, it's really long) > > Baseline - you are certainly free to move to classes or any other concepts you want to add on - but this would be a starting point #!/usr/bin/ruby def handle1 puts 'Got 01' end def handle2 puts 'Got 02' end def handleq @cont = 0 end @cont = 1 while @cont == 1 puts 'Pick an option' choice = gets.strip case choice when '1' handle1 when '2' handle2 when 'q' handleq end end John P.S. Probably the trickiest thing in there for you is the gets.strip - ruby will give you back the newline character when the person hits enter #strip removes the last character from the string (newline character).
on 2013-02-15 19:17
WILLS, JAMAL A wrote in post #1097138: > > def showMenu menuText > puts menuText > puts > print "What do you desire? " > > > menu_00 = %q{menu 00 > > 1 - open menu_01 > 2 - open menu_02 > 3 - open menu_03 > 4 - open menu_04 > q - Quit} > Firstly, I know what are Functions/Methods, but I find GOTO much easier to follow (and also more powrfull, because I can RETURN wherever I want - in GOSUB, and Functions, you always return to the point you started from) Secondly, this is great idea, to put entire menu in one long string. I wouldn't have thought of that !! Thanks ! I had idea (as described in original post at #2) to write code something like this: ------------------------------------------------ class Mymenus attr_acessor :over100variablesIintendtoUse def initialize #initialize every variable end def menu_00 #root menu system("cls") puts "Select something:" puts puts "1 - open menu_1" puts "2 - open menu_2" puts "3 - open menu_3" puts "4 - open menu_4" puts "q - Quit" end def menu_1 system("cls") puts "enter choice:" puts puts "1 - open menu_11" puts "2 - open menu_12" puts "3 - open menu_13" puts "4 - open menu_14" puts "b - Back" end def menu_2 system("cls") puts "enter choice:" puts puts "1 - open menu_21" puts "2 - open menu_22" puts "b - Back" end ... etc ... etc --------------------------------------------------------- ... and then probably put loop, starting with root menu, when you select number, you call new method (submenu) ... but your way seems easier, and with less methods involved
on 2013-02-15 20:07
On Fri, Feb 15, 2013 at 7:17 PM, Stu P. D'naim <lists@ruby-forum.com> wrote: > Firstly, I know what are Functions/Methods, but I find GOTO much easier > to follow (and also more powrfull, because I can RETURN wherever I want > - in GOSUB, and Functions, you always return to the point you started > from) Where's the difference? With a GOSUB the RETURN will also take you to the calling site. That's the whole point of GOSUB vs. GOTO. What makes GOSUB worse than functions is that you do not get a new scope and do not have function arguments. So you always risk messing up the caller's scope. Granted, more modern dialects of BASIC might have mechanisms to deal with that (it's been a while I worked with BASIC - and I wouldn't got back unless for experimental purposes). > > > > --------------------------------------------------------- > ... and then probably put loop, starting with root menu, when you select > number, you call new method (submenu) ... but your way seems easier, and > with less methods involved I do not know what your menus will do eventually but I would certainly make this more data driven. In this example the structure of the code is identical for all menu entries. There is no if else cascade or case statement. The proper item is just determined via the position in the Array: Menu = Struct.new :name, :code # convenience method to create a Menu instance def menu(name, &b) Menu.new name, b end menus = [ menu("Configuration") do print "Starting configuration..." sleep 5 puts " done." end , menu("Reset everything") do puts "back to square one" end, menu("Quit") do puts "quitting" exit 0 end, ] loop do puts "Enter your choice:" menus.each_with_index do |m, idx| printf "%2d %s\n", idx, m.name end print "> " input = gets.chomp begin m = menus[Integer(input)] if m m.code.call else puts "Wrong choice. Try again" end rescue ArgumentError puts "Not numeric: #{input}" end end Kind regards robert
on 2013-02-15 23:13
On Feb 15, 2013, at 08:46 , Ryan Victory <ryan@raptormail.net> wrote: > Love U Ruby: I'm really not sure what you meant by that response, but > it's irrelevant and inaccurate (there is no catch in Ruby, it's rescue)... You're wrong. http://www.zenspider.com/Languages/Ruby/QuickRef.h...
on 2013-02-16 01:49
Well, considering the OP's question was "What's the /proper/ way to replace functionality of GOTO" (emphasis mine), I'd say that pretty much any rubyist would agree that a goto like structure doesn't belong in ruby code unless it truly is the most elegant solution (escaping multiple nested loops for example). Not that it really matters either way, this question has been answered by somebody else and I believe the OP is satisfied. -Ryan
on 2013-02-16 02:13
When one is implementing a command-line interface like this one, and you don't want to have a dozen of two-line methods, judicious use of throw..catch is a reasonably nice solution.
on 2013-02-16 08:23
Hi all, On 16/02/13 11:16, Ryan Victory wrote: > Well, considering the OP's question was "What's the /proper/ way to > replace functionality of GOTO" (emphasis mine), I'd say that pretty much > any rubyist would agree that a goto like structure doesn't belong in > ruby code unless it truly is the most elegant solution (escaping > multiple nested loops for example). Not that it really matters either > way, this question has been answered by somebody else and I believe the > OP is satisfied. > > -Ryan I just wanted to say that I think Ryan is attempting to steer the OP in the right direction, which is to prefer modern, sensible structures where possible that don't have the numerous drawbacks that GOTO is saddled with. Reducing it to a car analogy, as all things do, ;) consider if someone familiar with driving an auto was asking how to make a manual move, and if you accelerated with the same pedal. Someone like Ryan steps in and very wisely says that you might want to figure out the clutch first, since the car isn't going anywhere without changing gears. Someone else points out that the clutch won't move the car at all, and that he is wrong. Sure, that's technically correct, he would be wrong, but the point is that the real problem that should be addressed involves understanding the role the clutch also plays. I also want to add that I *also* had absolutely *no* idea that Ruby had anything like GOTO and was similarly thrown (pun intended) by the mention of catch/throw. I've not had a need for it yet, myself. It's simultaneously disturbing, interesting, and scary to know this exists. ;) I started out software development around a couple of decades ago with BASIC, and then later assembly language, before moving on to Ada, C, and C++. Anyone with a similar background can imagine how much unlearning *that* required. ;) Ryan's advice is good, especially for someone in that position. Ryan, like me, just didn't know about Ruby catch/throw. It's still good advice. I just wanted to make sure at least one person has acknowledged this. Cheers, Garth
on 2013-02-16 08:31
people, people.
Every time your code takes a branch, loops the loop, takes an early
return, etc, it's executing a "goto".
Commonly used goto-es:
next
continue
break
return
The switch from a semi-unstructured language to a high-structured,
quasi functional, oo language can be as difficult as learning
idiomatic 15th century classical French for some.
The heart of a text-based, menu-driven task system is the
Read-Eval-Print loop (REPL). The basic arrangement for this in most
every structured language is so:
#!/usr/bin/env ruby
# Read-Eval-Print Loop basic command processor
def show_menu
puts "some menu"
end
def get_response
:done
end
def perform(request)
puts request.to_s
request
end
loop do
show_menu
request = get_response
result = perform(request)
break if result == :done
end
The next vital part of the tiny app will be how you want to link your
menu items, valid responses, and tasks. Thinking data structures here.
The simplest thing I can think of is a hash, where the key is the
response, and the value is a hash containing an entry for the menu
item, and an entry for the procedure/method to execute.
@commands = {
'1' => {
:menu => "Thing 1",
:action => lambda {thing1}},
'2' => {
:menu => "Thing 2",
:action => lambda {thing2}},
'0' => {
:menu => "Quit",
:action => lambda { :done }}
}
"@commands" is an /instance variable/ with the scope of the current
instance, in this case of the global class Object. We're not actually
doing anything OO here, but using ruby idioms to get something done.
In this context, where there are no classes, it acts pretty much like
a global.
"lambda" defines an anonymous procedure, which will be handy when we
get to fleshing out the perform method from above.
"thing1" and "thing2" are going to be where you build your tasks. For
now, they're just place-holder methods:
def thing1
puts "THING ONE RULES!!"
end
def thing2
puts "THING TWO FTW!"
end
The last menu option merely returns the symbol value ":done", which is
used to exit your REPL.
Now we can look at how to show the menu. Given the above @commands
data structure, this becomes trivial:
def show_menu
@commands.each do |key, value|
puts "#{value[:menu]} - Press #{key}"
end
end
This says to loop through each of the hash's members, and using the
key and value, display the action, which is in the value's :menu item,
and the input needed, which is the entry's key.
Getting the response is also rather trivial:
def get_response
gets.chomp.downcase
end
This retrieves a line from input (user types the appropriate choice
and presses return), then removes the trailing white space, and
converts it to lower case. In Ruby, the last thing evaluated by a
method is what gets returned as the result of that method.
Now, however, we need to add something more to our REPL. What happens
if the user type "XyZzY!!" ? The program as it stands will give some
kind of error and die. Instead, we need to validate what the user
typed in and give an appropriate response if we don't recognize it.
So, to our REPL, we add:
loop do
show_menu
request = get_response
if valid?(request)
result = perform(request)
else
puts "Unknown request #{request}. Try again"
result = nil
end
break if result == :done
end
And the associated "valid?" method:
def valid?(response)
@commands.keys.include?(response)
end
Which returns true if the value passed in, "response" is included in
the array of keys in @commands. This sort of thing is called
"chaining" in Ruby, and can be quite expressive, and is what gives
Ruby some of it's functional programming flavour.
Okay, now we should have a complete program:
#!/usr/bin/env ruby
# Read-Eval-Print Loop basic command processor
@commands = {
'1' => {
:menu => "Thing 1",
:action => lambda {thing1}},
'2' => {
:menu => "Thing 2",
:action => lambda {thing2}},
'0' => {
:menu => "Quit",
:action => lambda { :done }}
}
def thing1
puts "THING ONE RULES!!"
end
def thing2
puts "THING TWO FTW!"
end
def show_menu
@commands.each do |key, value|
puts "#{value[:menu]} - Press #{key}"
end
end
def get_response
gets.chomp!.downcase
end
def valid?(response)
@commands.keys.include?(response)
end
def perform(request)
@commands[request][:action].call
end
loop do
show_menu
request = get_response
if valid?(request)
result = perform(request)
else
puts "Unknown request #{request}. Try again"
result = nil
end
break if result == :done
end
If you load this into irb, you can see how it works:
1.9.3-head :010 > load 'repl.rb'
Thing 1 - Press 1
Thing 2 - Press 2
Quit - Press 0
1
THING ONE RULES!!
Thing 1 - Press 1
Thing 2 - Press 2
Quit - Press 0
1
THING ONE RULES!!
Thing 1 - Press 1
Thing 2 - Press 2
Quit - Press 0
2
THING TWO FTW!
Thing 1 - Press 1
Thing 2 - Press 2
Quit - Press 0
2
THING TWO FTW!
Thing 1 - Press 1
Thing 2 - Press 2
Quit - Press 0
1
THING ONE RULES!!
Thing 1 - Press 1
Thing 2 - Press 2
Quit - Press 0
2
THING TWO FTW!
Thing 1 - Press 1
Thing 2 - Press 2
Quit - Press 0
0
=> true
1.9.3-head :011 >
The one thing I can see missing from your original BASIC version is
that this one does not do a clear-screen at the top of each loop. That
actually gets a bit more complicated, because you have to mess with
terminal control sequences. That's beyond my knowledge.
on 2013-02-16 14:02
hash inside hash menus, interesting, same as Robert Klemme's example ... thank you all guys, now I have more than enough ideas how to start my script
on 2013-02-16 15:21
On Sat, Feb 16, 2013 at 2:03 PM, Stu P. D'naim <lists@ruby-forum.com> wrote: > hash inside hash menus, interesting, same as Robert Klemme's example ... There is one difference: I created a class while the other example uses nested Hashes. Frankly, since with Ruby it is so easy to create classes, I do not understand why people go about to suggest creating nested structures of Hash and Array combinations. Look at this example: On Sat, Feb 16, 2013 at 8:30 AM, tamouse mailing lists <tamouse.lists@gmail.com> wrote: > > } It shows quite obviously that there are "things" which always have a "menu" (or rather a name) and an "action". Why then not just create a class for that? Thing = Struct.new :menu, :action etc. > thank you all guys, now I have more than enough ideas how to start my > script Good! Kind regards robert
on 2013-02-16 15:58
On Sat, Feb 16, 2013 at 8:20 AM, Robert Klemme <shortcutter@googlemail.com> wrote: > <tamouse.lists@gmail.com> wrote: >> >> thank you all guys, now I have more than enough ideas how to start my >> script > > Good! > > Kind regards > > robert All very true. My initial thought was to refrain from jumping directly Classes, but it's probably just as well.
on 2013-02-17 17:42
i recall the Amiga thrived on tons of GOTO 's thrashed to the point where it was almost every third word on the page there is a bunch of goto's in ruby-parse.y lines 10203-10250 on github parse.y#L10203 <https://github.com/ruby/ruby/blob/trunk/parse.y#L10203>
on 2013-02-20 05:09
callcc is one way to do a functional programming style goto statement http://www.ruby-doc.org/core-2.0/Continuation.html Though it's obvious that goto is considered a taboo at the current state of the art. There are always another way to deal with conditional to executed code. goto statements became obsoleted by simply calling a function which could almost be viewed as a bookmark. This removed the code from the data which goto relied on line number for procedural representation over function verb. None the less I enjoyed creating infinite loops on my apple ][e as a child. Also recall the ? would expand to PRINT which I believe is now simply a easter egg of modern visual basic. ]LIST 10 PRINT "HELLO, WORLD!" 20 GOTO 10 OP your going to love ruby. It really brings back the old "joy of programming" we had 20 years ago =). ~Stu
on 2013-02-20 08:18
hey thanks for the heads up i really like ruby you can read it and tell what it is intended to do
on 2013-02-20 22:43
On Wed, Feb 20, 2013 at 1:18 AM, Timothy Gregory <tigre7t@gmail.com> wrote: > hey thanks for the heads up > i really like ruby you can read it and tell what it is intended to do > > You can also teach it to read and parse what you intend it to do.
Please log in before posting. Registration is free and takes only a minute.
Existing account
(Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
Log in with Google account | Log in with Yahoo account
No account? Register here.