Java

About Ruby

Ruby is a dynamic, fully object-oriented language that's usually grouped with scripting languages. The scripting term, for languages like Ruby, Smalltalk, and Python, is a little too limited, so I'll use the term applications language . If you've used nothing but compiled languages like Java and C, get ready to have some fun. Ruby will turn you loose. I suggest that you install it (just go to http://ruby-lang.org ), and type along. It comes with a primitive IDE, but the command line works well. Fire up a Ruby shell by typing irb. You'll get a shell prompt:

    irb(main):001:0>

Ruby Is Fully OO

From here, you can evaluate Ruby statements. You'll frequently use irb to answer those tiny questions that come up often in programming. In Ruby, everything is an object, and if you type one alone, Ruby will return that object. Type 4 and press Enter:

    irb(main):001:0> 4
    => 4

Unlike Java, numbers are objects , not primitives. For example, you can do this:

    irb(main):008:0> 4.4765.round
    => 4

Even nil is a class, standing for nothing:

    irb(main):009:0> nil.class
    => NilClass

You don't have to worry about primitives or wrappers at all. More importantly, you don't have to deal with those cases in an API. Ruby's reflection, persistence engines, and XML frameworks are all much simpler, because you don't have to deal with all the edge cases related to primitives and arrays of primitives.

Typing

Try to do an assignment without a declaration:

    irb(main):011:0> n=1
    => 1
    irb(main):012:0> n.class
    => Fixnum

So n has an object of type Fixnum. You didn't declare n at all. That's a strong hint that Ruby is dynamically typed. Now, assign something else to n:

    irb(main):013:0> n="fish"
    => "fish"
    irb(main):014:0> n.class
    => String

Now, n has a string. We changed the type of the variable i. More accurately, the type in Ruby is bound to the object, but not the thing that contains it. So Ruby is dynamically typed. Let's try to do something strange:

    irb(main):015:0> n+4
    TypeError: cannot convert Fixnum into String
            from (irb):15:in '+'
            from (irb):15

Ruby won't break its typing rules by coercing a string to a Fixnum. That means Ruby is strongly typed.[*] You can get its length by invoking the size method on n:

Conditionals

Ruby's conditionals will remind you more of C than Java. In Ruby, nil and false evaluate to false, and everything else (including TRue) means true. Read that sentence again. Unlike C, 0 is true. You should also notice that false and "false" are different. One is the Boolean constant for false, and one is a string. For example, puts "It's false." unless "false" returns nil, but puts "It's false." unless false will print It's false.

Ruby also has a few more conventions that you should know about. ? and ! are both valid in method names. By convention, methods ending in ? are tests. For example, nil? would test to see if a value is Nil. Methods ending in ! are potentially dangerous, because they have side effects. For example, a method called replace(in_string, sub_string, replacement) might return a string with the substring replaced, while replace!(in_string, sub_string, replacement) would modify the input string.

Like Java, Ruby has an if statement. Ruby also supports an unless statement that works the same way. You can use if or unless in block form, as you do in Java. You can also tack them onto the end of a line, to conditionally execute a single line of code. So, you can do something like this:

    irb(main):099:0> def silence?(b)
    irb(main):100:1>   puts   "SCREAM!" unless b
    irb(main):101:1> end
    => nil
    irb(main):106:0> silence? "False"
    => nil
    irb(main):107:0> silence? "false"
    => nil
    irb(main):108:0> silence? 0
    => nil
    irb(main):109:0> silence? "quit kicking the cat"
    => nil
    irb(main):110:0> silence? false
    SCREAM!
    => nil
    irb(main):111:0> silence? nil
    SCREAM!
    => nil

Take a look at the silence? method. Ruby returns the value of the last statement, unless a method explicitly returns something. In this case, the statement puts "SCREAM!" unless b always returns nil. More importantly, the method prints SCREAM unless you pass it a true value.

Looping

Ruby has two conditional loops. You'll notice that many of Ruby's libraries help you by returning nil when they're done. If you're reading from standard input, you might do this:

    irb(main):010:0> puts line while line=gets
    one
    one
    two
    two
    ^Z
    => nil

The loop continued until I entered the end-of-file character. Of course, you can also direct the input stream to a file. Plus you can use while at the beginning of a line, as long as you terminate it with an end:

    irb(main):013:0> while line=gets
    irb(main):014:1>   puts line
    irb(main):015:1> end

You've already seen Until, the other looping construct. It works in exactly the same way, but it will fire the loop while the expression is false. You'll also see a for loop later, but that's just syntactic sugar.

Ranges

Java programmers typically will specify a range using an arithmetic expression, like this:

    class Range {
      public static void main (String args[  ]) {
        int i = 4;
        if (2 < i && i < 8) System.out.println("true");
      }
    }

You can do something similar in Ruby, but you've got another alternative. Ruby supports first-class range support. x..y represents values from x to y, inclusive. For example, 1..3 represents 1, 2, 3. You can include the 3 with a third period. As you can imagine, ranges in Ruby are objects:

    irb(main):004:0> range=1..3
    => 1..3
    irb(main):005:0> range.class
    => Range

You can also check to see if something is in a range, using the = = = operator:

    irb(main):010:0> ('a'..'z') =  =  = 'h'
    => true
    irb(main):011:0> ('a'..'z') =  =  = 'H'
    => false
    irb(main):012:0> (1..10) =  =  = 5
    => true

You get more convenient syntactic sugar. Now, a for loop turns into this:

    irb(main):021:0> for c in 'g'..'k'
    irb(main):022:1>   puts c
    irb(main):023:1> end
    g
    h
    i
    j
    k

for/in loops also work with Arrays and Hashes. Ranges introduce = = =, another type of comparison. Next, you'll see a third type of comparison, called match, which you'll use with regular expressions .

Regular Expressions

Java has an API that supports regular expressions. Ruby builds regular expressions into the syntax. Some like regular expressions and others do not. To me, they're a critical part of dealing with strings. Just like any other type of programming, you can take them too far. If you've got 16 consecutive backslashes, it's probably time to refactor. Still, they can be much more useful than similar code, handwritten to recognize certain patterns.

In Ruby, you'll define a regular expression between slashes. You'll match regular expressions like this:

    irb(main):027:0> regex = /better/
    => /better/
    irb(main):028:0> regex.class
    => Regexp
    irb(main):029:0> "Mine is bigger" =~ regex
    => nil
    irb(main):030:0> "Mine is better" =~ regex
    => 8

Ruby returns the index of the character at the match. Ruby regular expressions are much more powerful than I can show you here. I'll just say that Java developers spend at least half of their time dealing with strings. When you think about it, servlets, XML strings, configuration files, deployment descriptors, and application data can all be strings. To parse them effectively, you need first-class pattern matching, such as regular expressions and ranges. Java 1.5 closes the gap some, but not completely.

Containers

Ruby containers are like Java's collections. You just saw an array. Like Java, arrays are objects: [1,2,3].class returns Array. Unlike Java, everything in an array is also an object. Ruby also has a Hash. Like Java's HashMaps, a Ruby Hash is an object. Unlike Java's HashMap, a Ruby Hash also has some syntactic sugar. You use braces instead of brackets, and you use key=>value to define one key-value pair, like this:

    irb(main):011:0> numbers={0=>"zero", 1=>"one", 2=>"two", 3=>"three"}
    => {0=>"zero", 1=>"one", 2=>"two", 3=>"three"}
    irb(main):012:0> 4.times {|i| puts numbers[i]}
    zero
    one
    two
    three

Like Java collections, Ruby containers hold objects, and they need not be homogeneous. In version 1.5, Java's generics let you build type-safe collections. You could modify Ruby's Array or Hash to make them type safe. (Remember, you can modify any of Ruby's classes directly. It's a dynamic language.) While Ruby doesn't have dozens of types of containers like Java does, you will notice some benefits immediately:

  • Since there's no distinction between primitives and other objects, you can put literally anything into any given container, and you can nest them easily.

  • Since everything inherits from object, everything has a hash code.

  • The language gives you the same syntactic sugar for hashes as for arrays.

  • Code blocks make iteration tighter and easier.

If you're a big Java collections user who's used a dynamic language before, you probably noticed that Java collections often feel wrong. You have to circumvent static type checking, because you're adding something to a collection as an object, and you're forced to cast it to something else when you retrieve it. Iteration is painful and awkward. A collection doesn't feel like a standard array, which can possibly contain primitives.

Ruby containers will feel altogether different. You won't have to deal with the maddening type casts or generic syntax. Code blocks simplify iteration. You don't see too many types of collections, but don't let that fool you. Using the rich methods, you can use Array as a list, queue, stack, or any other type of ordered collection. For instance, let's use Array as a stack:

    irb(main):001:0> stack=[1,2,3]
    => [1, 2, 3]
    irb(main):002:0> stack.push "cat"
    => [1, 2, 3, "cat"]
    irb(main):003:0> stack.pop
    => "cat"
    irb(main):004:0> stack
    => [1, 2, 3]

Similarly, you can use Hash whenever you need a set, dictionary, or any type of unordered collection. You'll find yourself doing more with collections, and less customized iteration.

Files

Iterating through a file works much like iterating through a collection. You'll create a new file and pass it a code block. For example, here's a simple GREP:

    File.open(ARGV[0]) do |file|
      rx = Regexp.new(ARGV[1])
      while line=file.gets
        puts line if line =~ rx
      end
    end

To use it, type it into a file called grep.rb. Then, you can call it (outside of irb) like this:

    ruby grep.rb filename regex

Notice what you don't see. You don't have to close the file or manage exceptions. This implementation makes sure the file will be closed if an exception occurs. You're effectively using a library that specifies everything on the outside of a control loop that iterates through a file. Ruby does the repetitive dirty work for you, and you customize the inside of the control loop with a code block.

Why Should You Care?

By now, you should be getting a feel for the power and simplicity of Ruby. You can probably see how the lines of code go down and the abstraction goes up. You might think it doesn't make any difference. You could lean ever harder on your development environments and on code generation tools like XDoclet , and shield yourself from some of the problem, but let me tell you: lines of code matter!

  • You still have to understand anything that your tools generate. I work with dozens of people every year that don't understand the SQL that Hibernate cranks out, and others who have to maintain generated code, after they tailor it for their needs.

  • The more code you have, the more bugs it can hide. Unit testing can take you only so far. You'll still need to inspect code to enhance it or maintain it.

  • Writing code is not the only cost. You also need to consider the cost of training, maintaining, and extending your code.

  • Each code generation technique that you use limits your flexibility. Most Java developers now depend on tools to do more and more. Each tool that you adopt carries a cost. I'm an IDEA man, but some of my customers use Eclipse. I'm nowhere nearly as effective on it, so my customer loses something when I am forced to use it. XDoclet increases the feedback cycle.

  • Java developers rely increasingly on XML for configuration. Remember, configuration is still code. Developers from other languages often find Java's over-reliance on XML configuration annoying. We use so much configuration outside of the language because configuration in Java is painful and tedious. We do configuration in XML rather than properties because...well, because overuse of XML in Java is a fad. Meanwhile, configuration in Ruby is usually clean and comfortable.

You may be willing to pay the costs related to lines of code, but you should also consider higher abstractions. With Java, you must use unsightly iterators. With Ruby, you wind up building the iteration strategies into your containers and reusing that logic.

Said another way, Java customization usually happens with an outside-in strategy. You build big chunks of reusable code that fill out the inside of your applications. But that's only one kind of customization. For many jobs, you'd like to keep a generic implementation of a job, and customize a few lines of code on the inside of a method. Iterating through a JDBC loop, processing a file, and iterating through a collection are only a few examples of this strategy. Some Java developers call this strategy inversion of control .

Ruby lets you program with both styles, as shown in Figure 6-1. Code written with that strategy is a joy to maintain, and it hides repetition from you. To be fair, some Java frameworks, like Spring, do some of this for you as well, but it's not as easy in Java, and this style of programming is not nearly as common, since you have to use the heavyweight anonymous inner class to do so. In dynamic languages like Ruby and Smalltalk, this programming strategy gives you tremendous intellectual freedom, both in the frameworks that you use and in the frameworks that you build.

by BrainBellupdated
Advertisement: