10 Minutes to Your First Ruby Application

2012-11-01 01:20:07 +0000
 

By James Britt

2012-11: The code and article has been updated to fix some errors and to work with Ruby 1.9.3

At the time it was written, and because of where it was originally published, it was targeted (more or less) at people working on Windows, so there may be a few unstated assumptions in the text.

On the other hand, the command-line examples seem to reflect a unix shell, no doubt because that's where the bulk of the content was created.

Apologies in advance for any confusion.


There's no better way to experience the elegance and power of Ruby than to fire up your code editor and start writing Ruby code. Create a small, useful Ruby application, and along the way, you'll learn what makes the language tick.


So you've discovered the grace and power of Ruby and you're ready to explore the subtle but important ideas behind its elegance. Follow this tutorial to create a small, useful Ruby application. As Ruby is primarily an object-oriented language with classes and objects, you can jump right in and create a class to encapsulate behavior. The instructions begin with a simple version of the application, and then expand it. Along the way, you will learn what makes Ruby tick.

The example application will serve two purposes:
  1. Demonstrate some features of Ruby
  2. Do something useful in the process


A word on the title: Were you to write this code yourself, assuming some moderate Ruby knowledge, it probably wouldn't take more than 10 minutes. Once you learn how Ruby works and understand what sort of code it enables, you'll find that you can whip up useful utilities in short order. Of course, a walk-through of such code will take a bit more than 10 minutes if you're new to the language.

What You Need
This tutorial assumes that you already have a current version of Ruby installed, and you have a code editor handy. You don't need a fancy IDE to code in Ruby; Vim, Emacs, and TextMate are great choices. NetBeans and Eclipse work fine as well.

Target Problem: Simplifying File Launching

Ruby is primarily a text-based, command-line-oriented language. Some GUI libraries are available, as well as multiple Web application frameworks, but exploring GUI development with Ruby is beyond the scope this article. The goal here is to write something that works from the command line.

Version 0: The Launcher Code

First, create a sparse Ruby file. Ruby files end with .rb and have the pivotal line that defines the path to your Ruby interpreter up top. Call the file launcher.rb:

     #!/usr/bin/env ruby

     # Example application to demonstrate some basic Ruby features
     # This code loads a given file into an associated application

      class Launcher
      end

Notice you can use a pound sign (#) to start a line-level comment. Everything to the right of the # is hidden from the interpreter. Ruby has a means for commenting multiple lines of code, too. Class names begin with a capital letter; classes are constants, and all Ruby constants start with a capital letter. (For a more complete overview of Ruby syntax, please see "Ruby—A Diamond of a Programming Language?", Part 1 and Part 2.)

While this code seemingly does nothing, it is executable. If you're playing along at home, you should see that your copy of the code executes. A simple way to run a Ruby script is to simply call the ruby interpreter and pass the name of the file, like this (see Sidebar 1. Instructions for Executing launcher.rb in Unix and Windows):

$ ruby launcher.rb

When you run the file, you should see nothing—unless there's an error of some sort in the code. So, nothing is good. It doesn't mean nothing is happening; when the ruby interpreter parses your file, it encounters your class definition and makes it available for creating objects. The following code adds the class definition to your code:

     #!/usr/bin/env ruby

     # Example application to demonstrate some basic Ruby features
     # This code loads a given file into an associated application

      class Launcher
      end

     launcher = Launcher.new

The code first creates a variable (launcher) that is assigned a reference to a new instance of the class Launcher. You do not have to declare the type of the variable. Ruby uses strong, dynamic typing, and variables can hold references to objects of any type. Pretty much everything in Ruby is an object, including strings, numbers, and regular expressions. Each of these has a formal creation method (e.g., String.new), but Ruby tries to make it easy and fluid to work with the common cases.

Second, Ruby creates the object instance by invoking new on your Launcher class. New is a class method; it's analogous to constructor methods in Java. Of course, an empty object won't get you far, so you must add some behavior.

Adding Behavior

The essence of your application takes a given file name and passes it to an associated application for processing of some sort. The launcher code will need to know how to do this mapping, so when you create an instance of a Launcher class, you must pass in some sort of mapping. You've seen that you can use the class method new to create an instance of a class. To create an instance that starts life with some set of data, you can pass in arguments to new. To handle this, you of course will have to add some code to Launcher:

  def initialize app_map 
    @app_map = app_map
  end

You define methods in Ruby using the def keyword, followed by the method name, and then the augment list, if any. The argument list is in parentheses for clarity, though Ruby will allow you to omit them when the meaning of the code is unambiguous (see Sidebar 2. Why You Add initialize Method When Passing Arguments to new Method).

It's worth noting then that Ruby objects begin life with assorted built-in behavior. You can use these as is, or opt to override them.

Instance Variables

Your initialize method takes one argument, app_map. Again, as with the earlier variable, you do not give the types of method arguments. You just say that the method takes one argument (app_map), and in the body of the method this argument gets assigned to the variable @app_map. The @ symbol indicates that the variable is an instance variable (i.e., it is available to all the code in this object). You create this instance variable when you create your object, and it will be available to any other methods you add to your code.

To have your application execute a given file using the associated application, drop some more code into it:

class Launcher

  def initialize app_map 
    @app_map = app_map
  end

  # Execute the given file using the associate app
  def run file_name 
    application = select_app file_name 
    system "#{application} #{file_name}" 
  end

  # Given a file, look up the matching application
  def select_app file_name 
    ftype = file_type file_name 
    @app_map[ ftype ]
  end

  # Return the part of the file name string after the last '.'
  def file_type file_name 
    File.extname( file_name ).gsub( /^\./, '' ).downcase 
  end

end


The method run takes a file name as its argument, passes it to select_app to find out which application to execute, and then uses Ruby's system method to invoke that application, passing the file name. The system method simply kicks the given command into a sub-shell. While select_app takes the file name, calls file_type to get a 'normalized' file extension, and then uses that as a key into @app_map to see which application to run.

Finally, file_type takes the file name and uses a class method on Ruby's File class to get the extension. The string returned by extname includes the period (.) that precedes the file extension. You don't need that, so the code uses gsub (or global substitute) to strip it; it then converts what remains to all lowercase letters with downcase.

For compactness, all these method calls are chained together. The string returned from File.extname is the receiver of the gsub request; the string returned from gsub then becomes the receiver of the call to downcase.

The example code so far has used objects that you expect to be Strings and Hashes, but what you really care about is that these objects will respond to particular messages in an appropriate way. (Before delving into how to call your shiny new object, see Sidebar 3. A Few Words About Objects, Types, and Behavior.) For such a small application, the subtlety and power of an object system based on messages and run-time behavior may not be critical, but it is important to understand this as you go on to write larger Ruby applications.

Rounding Out Version 0

Finish up this first version by putting it to use. You can add the following code to the end of the file to create an instance of Launcher and use it to run an application:

def help
  print " 
  You must pass in the path to the file to launch.

  Usage: #{__FILE__} target_file
" 
end

if ARGV.empty?
  help
  exit
end

app_map = {
 'html' => 'firefox',
 'rb' => 'gvim',
 'jpg' => 'gimp'
}

l = Launcher.new app_map 
target = ARGV.join ' ' 
l.run target 

You can download this code here.


The method help will render instructions if needed. ARGV is the argument vector; it is a built-in Ruby object that holds all the parameters passed to your program. If it's empty, then your program has nothing to work with, so it displays the help and exits. Otherwise, it creates a hash object and assigns it to the variable app_map.

The { ... } notation is Ruby's literal syntax for creating a Hash object. You could have used Hash.new, but it's verbose. Using the literal notation, you map hash keys to values using =>. The hash is used to populate your Launcher instance, while the command-line arguments are collected into a single string stored in the variable target, which is passed into run.

Before trying this code, you need to change the application values used in app_map so that they refer to the proper executable. Assuming you have "rb" mapped to a text editor, you can try the code like this:

$ ruby launcher.rb launcher.rb


This should open your source code in your editor.

Bulking Up to Version 1 with Dynamic Loading

So far, so good with Version 0, but you can do better. Rather than having a simple, direct mapping of file types to the application, you could map file types to execution handlers. That is, you can define code for your file types that can then decide which application to run, and with which arguments, depending on additional command-line arguments.

For example, if you are doing web development and have created an HTML file, you most often want to view it in a browser. So your application as it is works OK. But sometimes you want to view it using a particular browser. Right now, Launcher only allows a single application association. What you may want is the ability to launch myfile.html in the Opera web browser:

$ ./launcher myfile.html opera

Or you my want to perform some syntax checking on the HTML:

$ ./launcher myfile.html syntax


In other words, you want to add some smarts (see Sidebar 4. The Smarts Behind Launching Logic).

Dynamic Loading
To add those smarts, you will change your program so that you can associate file types with Ruby code rather than associating a particular application. That Ruby code will handle the launching logic, allowing you to decide just how clever to be when launching an application for a given file type (see Sidebar 5. Dynamic Class Loading with Defined Custom Ruby Classes).

Before doing this, make one small change. Having all your code in one place is handy, but it's not a good practice for anything but the smallest apps. For the sake of better organization, split out the general class code from the code that interacts with the user. Do this by creating a file, go.rb, and moving all but the actual Launcher code into that file (i.e, that last chunk of code you just added):

#!/usr/bin/env ruby

require 'launcher'

# Script to invoke launcher using command-line args
def help
  print " 
  You must pass in the path to the file to launch.

  Usage: #{__FILE__} target_file
" 
end

unless ARGV.size > 0
  help
  exit
end

app_map = {
   'html' => 'firefox',
   'txt' => 'gvim',
   'jpg' => 'gimp'
}

l = Launcher.new app_map 
target = ARGV.join ' ' 
l.run target 


Note the extra line of code near the top:

$:.unshift '.' 
require 'launcher'

You need these line to make your Launcher available to the current script.

[EDIT: Since Ruby 1.9, ruby does not automatically include the current directory on the require look-up path. $:.unshift '.' takes the current folder and adds it to the statr of the array of places your ruby app will look for files to require.]

The require method looks for a file matching the given string. The file extension is omitted, so Ruby first will assume you want a .rb file but also will look for a compiled library (e.g., .so) if it doesn't find a Ruby file. (Ruby searches a pre-defined load-path, which includes the current directory, so if you keep launcher.rb in the same place as go.rb, you're good. If you move it, you have to be more explicit about were Ruby can find it.)

Downloads: go.rb and launcher.rb

Writing a Handler Class

Now that you have a simple framework for routing file names to Ruby code, create a handler class for HTML files. The class needs to implement a run method that accepts at least one argument for the target file name, and an optional array of additional parameters. The class name must be Html in a file named html.rb, and placed in a handlers subdirectory:

class Html

  DEFAULT_BROWSER = 'firefox'

  def run file, args
    if args.empty?
      system "#{DEFAULT_BROWSER} #{file}" 
    else
      dispatch_on_parameters file, args
    end
  end

  def dispatch_on_parameters file, args
    cmd = args.shift
    send "do_#{cmd}", file, args 
  end

  def do_opera file, args=nil
    system "opera #{file}  #{args}" 
  end

  def do_konq file, args=nil
    system "konqueror #{file}  #{args}" 
  end
end

The code defines a constant for a default browser. In the absence of any extra arguments, then, you can have the target file launched in Firefox. (Note that you may have to change this so that it defines an executable command. On my Ubuntu machine I can run firefox with no explicit path and have a browser come up. On Windows, for example, the full path to the executable may be needed.)

If there are additional arguments, run calls out to dispatch_on_parameters, which extracts the first item from the args array and uses it to dynamically construct a message string. The send method is built in to all Ruby objects. It allows you to explicitly send a message to an object. When used by itself (as you are doing here), the receiver object is assumed to be the current object. So the code is sending a message to itself.

You prepend do_ to the actual argument value as a safeguard against method name collision. (For example, if the first argument were exit, you probably would not want to invoke Ruby's exit method. You'd call do_exit, which would then decide what the correct behavior should be).

This handler code has some fairly trivial examples of possible parameter handling. As is, you can launch a target HTML file in either some default browser or specify a particular browser:

$ ./go index.html opera
$ ./go index.html konq

A Little Overtime for Coolness

You've received an educational and practical example, but can you push things a little further? Of course you can. Mind you, this will take you past the 10-minute mark, but it should be worth it.

The standard Ruby distribution includes a wealth of libraries for all sorts of tasks. One of the most interesting is REXML, an XML parser written in pure Ruby. Developer Sean Russell wrote REXML to allow the manipulation of XML using a Ruby-style API rather than the usual W3C DOM API. Before too long, Sean's work became part of the Ruby standard library.

For the sake of simplicity, your HTML files in this example must use XHTML because REXML handles only XML. (There are very good Ruby tools for processing near-arbitrary HTML, one being Hpricot. However, they require installing additional libraries, the explanation of which is beyond the scope of this article.) Trusting that you are working with well-formed XHTML source, you can have your HTML handler do some file analysis. Add this code to the end of your Html class and you'll be able to run some simple reports on your XHTML:

  def do_report file, args=nil 
    require 'rexml/document'
    begin 
      dom = REXML::Document.new( IO.read( file ) )
      if args.empty?
        puts basic_xhtml_report( dom )
      else
        puts report_on( dom, args.first )
      end
    rescue Exception
      warn "There was a problem reading '#{file}':\n#{$!}" 
    end
  end

  def report_on dom, element
    els =   dom.root.elements.to_a "//#{element}" 
    "The document has #{els.size} '#{element}' elements" 
  end

  def basic_xhtml_report dom  
    report = []
    css = dom.root.elements.to_a '//link[@rel="stylesheet"]' 
    unless css.empty?
      report << "The file references #{css.size} stylesheets" 
      css.each do |el|
        file_name = el.attributes['href']
        file_name.gsub! /^\//, ''
        unless File.exist? file_name
          report << "*** Cannot find stylesheet file '#{file_name}'" 
        end
      end
    end

    js = dom.root.elements.to_a '//script' 
    unless js.empty?
      report << "The file references #{js.size} JavaScript files" 
      js.each do |el|
        file_name = el.attributes['src']
        file_name.gsub! /^\//, ''
        unless File.exist? file_name
          report << "*** Cannot find JavaScript file '#{file_name}'" 
        end
      end
    end

    report.join "\n" 
  end


There's a lot going on here, but the key method is do_report. The code creates a REXML Document object and assigns it to dom. If there are no extra arguments, you get back a basic report. Otherwise, the code does some cursory examination of a particular element.

The report_on method takes a document argument and an element name, and uses REXML's XPath features to find out how often that element is used. Although it's rudimentary, it certainly can serve as a demonstration and starting point for you to keep hacking.

The basic_xhtml_report method is similar, but focuses on a particular set of elements. It uses REXML to find all the CSS and JavaScript references, and then uses the File class to check that the referenced files exist. Again, not deep, but adding additional logic makes for a nice project.

You can download these files from here,

Clean, Expressive Code with Minimal Scaffolding

You now should have a better understanding of some of the features that make Ruby so special, namely:
  • Ruby is primarily an object-oriented language, where a key concept is objects responding to messages.
  • Ruby uses strong, dynamic typing, where the notion of "type" is based on what an object can do more than on a particular class name or inheritance hierarchy. An object's behavior is not confined to a literal mapping of messages to methods, and behavior may be constructed dynamically at run time.
  • Ruby classes are open; you are free to alter their behavior for what you deem appropriate for a given application.


This combination of open classes and dynamic behavior enables you to write clean, expressive code with a minimum of boilerplate scaffolding. Ruby gets out of the way and lets you get coding.

Sidebar 1. Instructions for Executing launcher.rb in Unix and Windows
On Unix systems you can set the file as executable and call it directly:

$ chmod u+x launcher.rb
$ ./launcher.rb

Windows users have a leg up here if they used the so-called One-Click Ruby Installer. It takes a few more clicks than one, but in the end it sets up an association for .rb files. So a Windows user should be able to execute the app straight off as follows:

C:\some\dir> launcher.rb

However, Windows users should also know that though they can launch a Ruby file by double clicking on it from the Windows file explorer, the results are fleeting: code will execute in a command shell, which will remain visible only so long as the application is running. For the sake of this demonstration, it's best to run the file from a command shell.
Back to article
Sidebar 2. Why You Add initialize Method When Passing Arguments to 'new' Method
You're probably thinking, why am I adding a method named "initialize" when I want to pass arguments to a method named "new"? The reason has to do with how Ruby creates objects from classes. All classes (such as Launcher) inherit from the class Object, and part of the deal is that the objects they create have a default initialize method. When the class method new is called, it first allocates some resources for the desired object, and then invokes the fresh object's initialize method. If you wish to provide creation parameters via new, you must define your own initialize method to handle the arguments in the newly created instance.
Back to article
Sidebar 3. A Few Words About Objects, Types, and Behavior
Ruby follows a message-passing model of object-oriented programming. When you see code like foo.bar, it means that the message "bar" is being passed to the object referenced by foo. Most of the time, that object will have a method bar, and when you see such code you may be tempted to think of it as calling foo's bar method. However, while that is convenient (and common), it is important to know what's happening under the hood.

When an object receives a message, it first looks for a corresponding method. The search will work its way up the inheritance hierarchy*, starting with the object's own class, until it reaches the Object class. If no match is found, then the method method_missing is called. As you may have guessed, there's a default implementation of method_missing, and it does nothing more than raise an exception. But just as you were able to override the default definition of initialize, you also can alter method_missing. You are free to redefine it so your object might apply some smarts to handling arbitrary message requests, making it appear that the object implements many more methods than it actually does.

This flexibility is at the core of one of the most appealing aspects of Ruby, but it also points to an important aspect that may trouble some people. You've seen that you do not declare data types when creating variables or defining method argument lists. If you want to check data types, you can. Code can ask for an object's type, and act accordingly. For example, you may want to write a method that accepts either a file name (e.g., a String object ) or a file handle (e.g., a File object). But Ruby code rarely checks an object's type simply for defensive measures, refusing to continue unless given an object that asserts itself to be a certain type. Because classes and objects are mutable at run-time, the notion of type in Ruby is essentially defined as the behavior of an object at any given time. Type is defined by which methods an object responds to, not which class it comes from. As Rubyist Logan Capaldo once said, "In Ruby, no one cares who your parents were. All they care about is if you know what you are talking about."

The general term for this is duck typing, from the phrase, "If it walks like a duck and quacks like a duck, then it's a duck."


* Note: This is not quite true; inheritance is but one way an object can obtain some behavior. For a better explanation of how Ruby decides what to do when an object is given a message, watch Patrick Farley at MountainWest RubyConf 2008 explain Ruby internals.
In fact, watching this talk should be one of your next steps in learning Ruby; metaprogamming tends to have a weird, mysterious aura around it, as if it were only something for Ruby gurus. Truth is, if you understand a few core properties of Ruby objects you too can be a metaprogramming master.
Back to article
Sidebar 4. The Smarts Behind Launching Logic
Where you were mapping a file extension to a particular application name, you now want to add associations with Ruby code. Specifically, you want Ruby classes custom-coded for each type of file you want to process.

First, create a new Ruby source file named launcherx.rb (the x is for extended) in the directory as launcher.rb:

#!/usr/bin/env ruby
# File launcherx.rb
require 'launcher'

class Launcher
  def handler file 
    get_handler(file) || build_handler(file)
  end
  
  def build_handler file 
    handler = Class.new 
    application = select_app file
    eval "def handler.run file,  args=nil
      system '#{application} \#{file} \#{args}'  
    end"
    handler
  end

  def get_handler file
    begin
      here = File.expand_path( File.dirname __FILE__  )
      ftype = file_type file
      require "#{here}/handlers/#{ftype }"
      Object.const_get( ftype.capitalize ).new
    rescue Exception
      nil
    end
  end

  # Execute the given file using he associate app
  def run file, args = nil 
    handler(file).run file, args
  end
end



The first thing to note is that the code is calling require to load the existing definition of Launcher. Yet your new code also defines a class named Launcher. What gives? When Ruby encounters a class definition that uses the name of an existing class, it updates the existing class with the new class. The methods defined in your first version of Launcher are still there; new methods defined in the additional code get added. And, as in the case of run, when new code uses the same name as existing code, the new code replaces the old. The upshot of this is that you do not have to duplicate the code from your first version; you need only add to or modify it.

[EDIT: In going through the code I found that the original version of build_handler did not escape the use of string interpolation. Here's what that means: Ruby allows you to create strings that contain evaluated bits of Ruby code. For example, "Today is #{Time.now}", will, when evaluated by Ruby, contain the date at the time of execution. The stuff inside of #{ ... } is first evaluated before being placed into the resulting string.

Now: Suppose you want to create a string that contains Ruby code that can be used (at a later date) for string interpolation? What build_handler is doing is dynamically generating code for a method to be used at some later time. That code needs to contain some string interpolation. The trick is to embed #{ ... } in such a way that it is not immediately evaluated (when we are first dynamically creating the method code). The way to do that is to escape the leading hash character using a backslash: \#{ ... }.

The original version did not do this. The code worked more or less coincidentally, given the artificial use-case for this article. The first version ended up hard-coding specific values for file and args. However, if build_handler is passed, for example, 'html', then the generated methods should be something like
   def handler.run file,  args=nil
     system 'firefox  #{file} #{args}'  
   end
]

Not so incidentally, this also works on core Ruby classes. For example, you can add to or alter the behavior of String by defining a String class with your own methods. If your code is frequently altering strings (perhaps to replace special characters), you can make your code clearer with something like this:

class String
  def amp_escape
    self.gsub '&', '&amp;' 
  end
end



Which then enables your code to do this:

"This & that".amp_escape


Your new application file now needs to handle the new behavior. The run method changes because this new version will be invoking Ruby code instead of directly calling a shell command, and you want the option of passing in additional arguments. Therefore, this version expects a file name and an optional array of arguments. It's optional because in the methods argument list you're giving it a default value of nil. Arguments pre-assigned this way must always come last in the argument list.

Whereas your first version simply used the file extension to pull an application name from a hash, this code uses handler to create a corresponding Ruby class to handle the given file name. The handler method is short; it first calls get_handler to see if it can locate a matching handler class. The || method is Ruby's logical OR. Should get_handler return false (or nil, which Ruby treats as false), then the code to the right of || is invoked. If there is no defined handler class, then the code makes one.

Recall that your new version of run will expect get_handler to return an object that responds to the run message. The build_handler method therefore needs to define a class with this behavior. There are a variety of ways you could do this. Here, you're going to first create a generic instance of the class Class and then dynamically add a run method that knows how to handle the particular file type in question.

Your new Launcher class retained the application map code from the original. This mapping serves as a fallback for handling files in the absence of any special Ruby code, meaning that your new version still does what the first version did. Your code can still call select_app to get the default application. The trick now is to get that into a method on your new class.

Perhaps the simplest way to do this is to build a string with the code you might write if you did know which application to invoke. You then have Ruby eval (i.e., evaluate) this string, making it part of the current process. (Note: capricious use of eval on arbitrary strings is not wise. It works well for the sample application and helps demonstrate an interesting feature of Ruby, but use it with care in more serious applications--especially any code that allows input from users.)

Just like that, build_handler can now return an object (albeit sparse) that knows how to do the one thing that matters: respond to a run request.

Back to article
Sidebar 5. Dynamic Class Loading
The real fun is in defining custom Ruby classes that have more interesting implementations of run. First, assume all these classes will live in files named after the file extension they handle. For example, a handler class designed to process HTML files will go into a file named html.rb. Also, all such files will go into a relative subdirectory named handlers. Asserting these two conventions allows the get_handler code to know just what to look for and where to look for it, bypassing a need for lengthy configuration settings.

When get_handler is called, it:
  1. Uses some built-in File methods to figure out the current file-path location (__FILE__ is a special Ruby variable that refers to the actual file containing the current code).
  2. Appends your pre-defined handlers directory to the current path.
  3. Uses the file extension of the target file name to derive the name of the file holding the handler class code.

All of this is passed to require with the expectation that such a file exists and that it will be loaded. If all goes well, Ruby will load and parse this file, making the desired class available to your code.

Now, without knowing the name of the class you want to instantiate in advance, you again need to do a bit of dynamic invocation. You could again use eval, but you can also reach into Ruby's list of constants (remember, classes are Ruby constants) and call new. Again, if all has gone well, Object.const_get will return the class desired, and new will then return an instance.

Should something go wrong (perhaps there is no such file to load, or the code in the file is malformed) and Ruby raises an exception, the code uses rescue to handle things. You could use rescue with more specific exceptions for more targeted error handling, but for the purposes of this example, you simply want to trap all exceptions and quietly return nil.

You may have noticed that get_handler does not explicitly specify which value to return. Indeed, none of your methods have done so. In Ruby, the return value of a method (with some exceptions) is the value of the last expression executed. get_handler has one top-level expression: begin/rescue/end. Its value will be either the value of the last expression in the begin/rescue section or the value created in rescue/end. Ruby does define return, which exits a method returning the provided value, but method flow control is sufficient to define an unambiguous return in most cases.
Back to article
This article was originally published on DevX.com