2.4. Ruby Idioms: Poetry Mode, Blocks, Duck Typing

A programming idiom is a way of doing or expressing something that occurs frequently in code written by experienced users of a given programming language. While there may be other ways to accomplish the same task, the idiomatic way is the one that is most readily intention-revealing to other experienced users of the language. Your goal when learning a new language should be to learn to “think in” that language by understanding and using its idioms well, or in other words, to avoid the well-known pitfall that “you can write FORTRAN in any language”. In this section we explore three key Ruby idioms: passing arguments to methods (“poetry mode” and named parameters), blocks, and duck typing.

link_to('Edit', {:controller => 'students', :action => 'edit'})
link_to 'Edit',  :controller => 'students', :action => 'edit'
link_to 'Edit', controller: 'students', action: 'edit'
Figure 2.7: Three legal and equivalent calls to the method link_to (which we’ll meet in Section 4.4) that takes one string argument and one hash argument. The first is fully parenthesized, the second omits the parentheses around the call arguments and the curly braces around the final hash argument, and the third uses

Poetry mode and named parameters. Figures 2.7 and 2.8 show two pervasive idioms related to Ruby method calls. The first, poetry mode, allows omitting parentheses around the arguments to a method call when the parsing is unambiguous. In addition, when the last argument to a method call is a hash, the curly braces around the hash literal can be omitted.

In early versions of Ruby, hash arguments were often used to emulate the named parameter feature (also called keyword arguments) available in languages such as Python, C#, and others. For example, the documentation for the link_to method used in Figure 2.7 tells us that :controller and :action are just two of many possible additional (and optional) values that can be passed to the method. True named parameters became available in Ruby 2.0, as Figure 2.8 shows; nonetheless, a great deal of Ruby code written prior to Ruby 2.0 still uses hashes to pass optional arguments or provide default values for arguments.

Blocks. Ruby uses the term block somewhat differently than other languages do. In Ruby, a block is just a method without a name, or an anonymous lambda expression in programming-language terminology. Like a regular named method, it has arguments and can use local variables.

As Figure 2.9 shows, one of the most common uses of blocks is to implement data structure traversal. The instance method each, available in all Ruby classes that are collection-like, takes a single argument consisting of a block (anonymous lambda) to which each member of the collection will be passed. each is an example of an internal iterator. Rubyists like to say that Ruby collections “manage their own traversal,” because it’s up to the receiver of each to decide how to implement that method to yield each collection element. (Indeed, in Figure 2.9, we can’t even tell what the underlying type of movie_list is.)

# Using 'named keyword' arguments
def greet(name, last_name: "", greeting: "Hi")
    "#{greeting}, #{name} #{last_name}!"
end
greet("Dave")               # => "Hi, Dave! "
greet("Dave", last_name: "Fox") # => "Hi, Dave Fox!"
greet("Dave", greeting: "Yo")   # => "Yo, Dave!"
greet("Dave", greeting: "Hey", last_name: "Patterson")
        # => "Hey, Dave Patterson!" - order of keyword args irrelevant
greet(greeting: "Yo")              # ArgumentError, since first arg is required
Figure 2.8: The use of keyword arguments or named parameters allows you to define methods in which some arguments are optional or assume default values. Named parameters can improve clarity for methods that take multiple arguments, though we will see in Chapter 9 that one should usually minimize the number of arguments a method accepts.
def print_movies(movie_list)
    movie_list.each do |m|
        puts "#{m.title} (rated: #{m.rating})"
    end
end
Figure 2.9: each takes one argument—a block—and passes each element of the collection to the block in turn. A block is bracketed by do and end, and any arguments expected by the block are enclosed in |pipe symbols| after the do. Each time through the block, m is set to the next element of movie_list.

Figure 2.10 shows a simple example of such a collection operator, which can be used with any collection that implements each as a way of traversing itself. Note once again that we have no idea how the collection is implemented: all we need to know is that it implements the instance method each to enumerate its elements. Ruby provides a wide variety of such collection methods; Figure 2.11 lists some of the most useful. With some practice, you will automatically start to express operations on collections in terms of these functional idioms rather than in terms of imperative loops. Although Ruby allows for i in collection, each allows us to take better advantage of duck typing, which we’ll see shortly, to improve code reuse.

Duck Typing. You may be surprised to learn, though, that the collection methods summarized in Figure 2.11 (and several others not in the figure) aren’t part of Ruby’s Array class. In fact, they aren’t even part of any superclass from which Array and other collection types inherit. Instead, they take advantage of an even more powerful reuse mechanism: A mix-in is a named collection of related methods that can be added to any class fulfilling some “contract” with the mixed-in methods. A module is Ruby’s method for packaging together a group of methods as a mix-in. The Ruby statement include ModuleName inside a class definition mixes the instance methods, class methods, and variables of the module into that class. The collection methods in Figure 2.11 are defined in a module called Enumerable that is part of Ruby’s standard library and is mixed in to all of Ruby’s collection classes. As its documentation states, Enumerable requires the class mixing it in to provide an each method, since Enumerable’s collection methods are implemented in terms of each. It doesn’t matter what class you mix it into as long as that class defines the each instance method, and neither the class nor the mix-in have to declare their intentions in advance. For example, the each method in Ruby’s Array class iterates over the array elements, whereas the each method in the IO class iterates over the lines of a file or other I/O stream. Mix-ins thereby allow reusing whole collections of behaviors across classes that are otherwise unrelated.

# find largest element in a collection
def maximum(collection)
    result = collection.first
    collection.each do |item|
        result = item if item > result
    end
    result
end
maximum([3,4,2,1])     # => 4
maximum(["a","x","b"]) # => "x"
max([RomanNumeral.new('XL'), RomanNumeral.new('LI')] # => 'LI'

class RomanNumeral
    include Comparable
    def initialize(roman_numeral_string)
        @orig_string = roman_numeral_string
        @value = RomanNumeral.convert_from_roman(roman_numeral_string)
    end
    def <=>(other)
        @value <=> other
    end
    def to_s
        @orig_string
    end
    def self.convert_from_roman(str)
        # ...code to convert Roman numerals from strings...
    end
end
Figure 2.10: This example finds the maximum-valued element in any collection that responds to each, and is agnostic to the type(s) of the element(s) in the collection as long as they respond to >. It even works on Roman numerals if we have a RomanNumeral class that either defines > explicitly, or defines <=> and mixes in the Comparable module to define <, >, and so on.

Similarly, a class that defines the “spaceship operator” <=>, which returns −1,0,1 depending on whether its second argument is less than, equal to, or greater than its first argument, can mix in the Comparable module, which defines <, <=, >, >=, ==, and between? in terms of <=>. For example, the Time class defines <=> and mixes in Comparable, allowing you to write Time.now.between?(Time.parse("19:00"), Time.parse("23:15")).

The term “duck typing” is a popular description of this capability, because “if something looks like a duck and quacks like a duck, it might as well be a duck.” From Enumerable’s point of view, if a class has an each method, it might as well be a collection, thus allowing Enumerable to provide other methods implemented in terms of each. When Ruby programmers say that some class “quacks like an Array,” they usually mean that it’s not necessarily an Array nor a descendant of Array, but it responds to most of the same methods as Array and can therefore be used wherever an Array would be used.

2.11
Figure 2.11: Some common Ruby methods on collections. For those that expect a block, the “Block” column shows the number of arguments expected by the block; if blank, the method doesn’t expect a block. For example, a call to sort, whose block expects 2 arguments, might look like: c.sort { |a,b| a <=> b }. These methods all return a new object rather than modifying the receiver, but some methods also have a destructive variant ending in !, for example sort!, that modify the receiver in place as well as returning the modified value. Use destructive methods with extreme care, if at all.

Self-Check 2.4.1. Write one line of Ruby that checks whether a string s is a palindrome, that is, it reads the same backwards as forwards. Hint: Use the methods in Figure 2.11, and don’t forget that upper vs. lowercase shouldn’t matter: ReDivider is a palindrome.

s.downcase == s.downcase.reverse

You might think you could say s.reverse=~Regexp.new(s), but that would fail if s happens to contain regexp metacharacters such as $.

Self-Check 2.4.2. Suppose you mix Enumerable into a class Foo that does not provide the each method. What error will be raised when you call Foo.new.map { |elt| puts elt } ?

The map method in Enumerable will attempt to call each on its receiver, but since the new Foo object doesn’t define each, Ruby will raise an Undefined Method error.

Self-Check 2.4.3. Which statement is correct and why: (a) include ’enumerable’ (b) include Enumerable

(b) is correct, since include expects the name of a module, which (like a class name) is a constant rather than a string.