From: tamouse mailing lists Date: 2013-02-16T16:30:27+09:00 Subject: Re: [from BASIC to Ruby] so, what's the proper way to replace funcionality of GOTO ? 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.