Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 60 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
60
Dung lượng
289,93 KB
Nội dung
Part 3 Built-in classes and modules In part 3, we’ll look in detail at a number of the built-in classes and modules that are part of the Ruby language. Some of them—strings and integers, for example—you’ve seen before, at least briefly or in a usage context. Here, we'll go into depth about each of the classes and modules we look at, and you’ll learn how to use several instance and/or class methods from each one. The material presented here represents a selection of classes and modules, and a selection of the methods of those classes and modules. The selection is weighted toward those that are more, rather than less, likely to be of use to Rails developers. However, what's really of use to Rails developers is a grasp of the Ruby language as a system and a reasonably general Ruby literacy. A dual principle has guided the con- tent of these chapters: not casting such a wide net that the Rails orientation gets diluted out of existence, but also not scrubbing Ruby clean of its integral, organic, systemic qualities. Chapter 9 covers some preliminaries that will help you get your bearings in the subsequent chapters. It pulls together aspects that a lot of objects have in common, so those points don’t have to be covered repeatedly or confined to one chapter when they really apply to all. From chapter 10 on, we’ll discuss specific classes and modules. Chapter 10 deals with scalar objects: one-dimensional objects like strings and numbers. Chapter 11 covers Ruby’s built-in collection classes: Array and Hash. In the course of these two chapters, you’ll also learn about the Comparable and Enu- merable modules, which are the source of searching, filtering, and sorting capabil- ities for many Ruby classes (and which you can mix into your own classes). Chapter 12 discusses regular expressions and, with that material in place, cir- cles back to discuss string- and array-related methods that use regular expressions as arguments. Finally, Chapter 13 introduces you to dynamic Ruby—an umbrella term for a number of subtopics having to do with Ruby’s ability to change the pro- gramming environment almost arbitrarily during runtime. As was the case with part 2, Rails-related examples will be used, where doing so makes sense, to illustrate and sometimes to illuminate Ruby points. (You’ll see quite a few examples that use the irb simple-prompt style of presentation, as described in the “Code conventions” section at the beginning of the book.) By the time we’ve finished this part, you’ll be well equipped to move on to part 4 and the Ruby-informed redesign of the music store application. 233 Built-in essentials In this chapter ■ Literal object constructors ■ Syntactic sugar ■ Iterators in depth ■ Boolean objects and states ■ Object comparison techniques ■ Runtime inspection of objects’ capabilities 234 CHAPTER 9 Built-in essentials The later chapters in this part of the book will cover specific built-in classes: what they are, what you can do with them, what methods their instances have. This chap- ter will discuss a selection of topics that cut across a number of built-in classes. The goal is to collect in one place important material that applies to more than one of the chapters to come. That way, as you explore the built-in classes fur- ther, you’ll have a grounding in the common material, and you’ll be familiar with some recurrently important techniques. The topics we’ll cover here all have Ruby-wide relevance in common; learning about them up front can save you a lot of fragmentary effort later. However, they’re a miscellaneous bunch of subjects—and therefore worth seeing listed in one place, before you begin reading the chapter: ■ Literal constructors—Ways to create certain objects with syntax, rather than with a call to new ■ Recurrent syntactic sugar—Things Ruby lets you do to make your code look nicer ■ Methods that change their receivers—Cases where calling a method puts the receiver in a different state, and why it matters ■ Methods that convert among classes—Methods that convert an object to a class ■ Iterators reiterated—Further exploration of iterators and their uses ■ Boolean states, Boolean objects, and nil —A close look at true and false and related concepts in Ruby ■ Comparing two objects—Ruby-wide techniques, both default and customizable, for object-to-object comparison ■ Listing an object’s methods—An important set of techniques for runtime reflection on the capabilities of an object You’ll find all of these topics useful as you work through this book, and as you read and/or write Ruby code (including, but not limited to, Rails source and/or application code) in the future. You may want to fire up an irb session for this chapter; it makes frequent use of the irb session format for the code examples, and you can often try the examples with small variations to get a feel for how Ruby behaves. 9.1 Ruby’s literal constructors Ruby has a lot of built-in classes. Most of them can be instantiated using new : str = String.new arr = Array.new Ruby’s literal constructors 235 Some can’t; for example, you can’t create a new instance of the class Integer . But for the most part, you can create new instances of the built-in classes. In addition, a lucky, select few built-in classes enjoy the privilege of having literal constructors. That means you can use special notation, instead of a call to new , to cre- ate a new object of that class. The classes with literal constructors are shown in table 9.1. When you use one of these literal constructors, you bring a new object into existence. We’ll look in considerable detail at most of these classes and the corresponding literal constructors. (The only class on the list to which we won’t devote a whole section or more is Range ; but you’ll see an explanation of ranges along the way when we encounter them.) Meanwhile, try to begin getting used to these nota- tions, so you can recognize these data types on sight. They’re very common; you’ll probably see "" and [] more often than you’ll see String.new and Array.new . NOTE LITERAL CONSTRUCTOR CHARACTERS WITH MORE THAN ONE MEANING Some of the notation used for literal constructors has more than one meaning in Ruby. Many objects have a method called [] that looks like a literal array constructor but isn’t. Code blocks, as you’ve seen, can be delimited with curly braces—but they’re still code blocks, not hash liter- als. This kind of overloading of notation is a consequence of the finite number of symbols on the keyboard. You can always tell what the nota- tion means by its context, and there are few enough contexts that with a little practice, it will be easy to differentiate. We’ll turn next to some cases of syntactic sugar that you’ll see, and possibly use, recurrently. Table 9.1 Summary of literal constructors for those built-in Ruby classes that have them Class Literal constructor Example(s) String Quotation marks "new string" or 'new string' Symbol Leading colon :symbol or :"symbol with spaces" Array Square brackets [1,2,3,4,5] Hash Curly braces {"New York" => "NY", "Oregon" => "OR"} Range Two or three dots 0 10 or 0 9 Regexp Forward slashes /([a-z]+)/ 236 CHAPTER 9 Built-in essentials 9.2 Recurrent syntactic sugar As you know, Ruby sometimes let you use sugary notation in place of the usual object.method(args) method-calling syntax. This lets you do nice-looking things, such as using a plus sign between two numbers, like an operator: x = 1 + 2 Here’s the odd-looking method-style equivalent: x = 1.+(2) As you delve more deeply into Ruby and its built-in methods, be aware that certain methods always get this treatment. Methods in this special group—whether they’re methods of built-in classes, or methods you write in your own classes—can always be called with the syntactic sugar notation rather than the method-call notation. For example, you can define the plus-sign method on an object you’ve created. Here’s a somewhat bizarre but perfectly valid example: obj = Object.new def obj.+(other_obj) "Trying to add something to me, eh?" end puts obj + 100 # output: Trying to add something to me, eh? The plus sign in the puts statement is a call to the + method of obj , with the inte- ger 100 as the single argument. If the method chooses to ignore the argument, and not to perform addition of any kind, it can. A number of Ruby’s automatically sugared methods are collected in table 9.2. Table 9.2 Methods with operator-style syntactic sugar calling notation Category Name Definition example Calling example Sugared notation Arithmetic method/ operators + def +(x) obj.+(x) obj + x - def -(x) obj (x) obj - x * def *(x) obj.*(x) obj * x / def /(x) obj./(x) obj / x % def %(x) obj.%(x) obj % x Get/set/append data [] def [](x) obj.[](x) obj[x] []= def []=(x,y) obj.[]=(x,y) obj[x] = y << def <<(x) obj.<<(x) obj << x Recurrent syntactic sugar 237 Remembering which methods get the sugar treatment is not difficult. They fall into several distinct categories, as table 9.2 shows. These categories are for conve- nience of grouping only; you can define []= to output Hamlet, if you feel like it. The category names indicate how these method names are used in Ruby’s built-in classes, and how they’re most often used, by convention, when programmers implement them in new classes. The extensive use of this kind of syntactic sugar—where something looks like an operator but is a method call—tells you a lot about the philosophy behind Ruby as a programming language. The fact that you can define and even redefine elements like the plus sign, the minus sign, and square brackets means that Ruby has a great deal of flexibility. No matter what domain you’re modeling, you can decide that you want to be able to add two of your objects together; all you have to do is define the + method, after which you’ll be able to use + as an operator. There are limits to what you can redefine in Ruby. You can’t redefine any of the literal object constructors: {} is always a hash literal (or a code block, in that context), "" will always be a string, and so forth. 9.2.1 Special treatment of += Another bit of syntactic sugar you’ll see a lot is the += construct: x = 1 x += 1 Ruby always interprets this to mean x = x + 1 Comparison method/ operators == def ==(x) obj.==(x) obj == x > def >(x) obj.>(x) obj > x < def <(x) obj.<(x) obj < x >= def >=(x) obj.>=(x) obj >= x <= def <=(x) obj.<=(x) obj <= x Case equality operator === def ===(x) obj.===(x) obj === x Table 9.2 Methods with operator-style syntactic sugar calling notation (continued) Category Name Definition example Calling example Sugared notation 238 CHAPTER 9 Built-in essentials This approach works for all the arithmetic method/operators, as shown in table 9.3. The sugar provides a way to make code more concise. You can use either form. The non-sugared version looks reasonably good in these cases (unlike some other instances of sugar, where you’d end up with code like x./(y) if you didn’t have the option of writing x/y ). We’ll look next at an important criterion by which methods in Ruby can be dis- tinguished from each other: whether they bring about permanent changes to the content or state of the objects on which they are called. 9.3 Methods that change their receivers (or don’t) The basic scenario of calling a method is always the same: 1 A message is sent to a receiver (an object). 2 The object executes the first method on its method lookup path whose name matches the message (or handles the error condition if there’s no such method). 3 The method returns a value. That’s what always happens. In addition, some things sometimes happen. The first two will be familiar; the third is what we’ll focus on here: ■ A method call may (or may not) include arguments. ■ A method may (or may not) yield one or more times to a code block associated with the method call. ■ A method may (or may not) modify its receiver. What does it mean for a method to modify its receiver? Table 9.3 Sugar notation for arithmetic method/operators Sugar notation How Ruby sees it x += 1 x = x + 1 x -= 1 x = x - 1 x *= 2 x = x * 2 x /= 2 x = x / 2 x %= 2 x = x % 2 Methods that change their receivers (or don’t) 239 9.3.1 Receiver-changing basics To gain perspective on methods that change their receivers, let’s start with an example of one that doesn’t. Let’s say you have a string: str = "hello" You wish to print it out with the first letter capitalized. Ruby has a handy capitalize method for strings: puts str.capitalize The result is “ Hello ”. Here, the call to capitalize gives you, as its return value, a new string. It’s this new string that you print out with puts . The original string, which served as the receiver of the “ capitalize ” message, still starts with a small h. You can test this by printing it out: puts str # output: hello str is still “hello,” not “Hello”. Now, let’s use another string method—this time, one that modifies its receiver. We’ll check for changes to the original string after making this method call, too: str = "hello" str.replace("goodbye") puts str This time, you see “goodbye”. You haven’t manufactured a new string; rather, you’ve modified the old string. The replace method changes the content of its receiver. (We’ll talk about String#replace in more detail in chapter 10.) You should always be aware of whether the method you’re calling changes its receiver. Neither option is always right or wrong. Which is best depends on what you’re doing, but it’s important to know. One consideration, weighing in on the side of modifying objects instead of creating new ones, is efficiency: Creating new objects (like a second string that’s identical to the first except for one letter) is expensive, in terms of memory and processing. This doesn’t matter much if you’re dealing with a small number of objects. But when you get into, say, han- dling data from large files, and using loops and iterators to do so, creating new objects can be a drain on resources. On the other hand, you need to be cautious about modifying objects in place because other parts of the program may depend on those objects not to change. For example, let’s say you have a database of names. You read the names out of the database into an array. At some point, you need to process the names for printed output—all in capital letters. You may do something like this: 240 CHAPTER 9 Built-in essentials names.each do |name| capped = name.upcase # code that does something with capped end In this example, capped is a new object: an uppercase duplicate of name . When you go through the same array later, in a situation where you do not want the names in uppercase, such as saving them back to the database, the names will be the way they were originally. By creating a new string ( capped ) to represent the uppercase version of each name, you avoid the side effect of changing the names permanently. The opera- tion you perform on the names achieves its goals without changing the basic state of the data. Sometimes you’ll want to change an object permanently, and some- times you’ll want not to; there’s nothing wrong with that, as long as you know which you’re doing, and why. 9.3.2 bang (!) methods Ruby lets you define a method whose name ends with an exclamation point. The built-in classes have many such methods. The exclamation point, or bang, has no significance to Ruby internally; bang methods are called and executed just like any other method. However, by conven- tion, the bang labels a method as dangerous—specifically, as the dangerous equiva- lent of a method with the same name but without the bang. Dangerous can mean whatever the person writing the method wants it to mean. In the case of the built-in classes, it usually (although not always) means this method, unlike its non-bang equivalent, permanently modifies its receiver. You’ll find a number of pairs of methods, one with the bang and one without. Those without the bang perform an action and return a freshly minted object, reflecting the results of the action (capitalizing a string, sorting an array, and so on). The bang versions of the same methods perform the action, but they do so in place: Instead of creating a new object, they transform the original object. Examples of such pairs of methods include sort / sort! for arrays, upcase / upcase! for strings, chomp / chomp! for strings, and reverse / reverse! for strings and arrays. In each case, if you call the non-bang version of the method on the object, you get a new object. If you call the bang version, you operate in-place on the same object to which you sent the message. In the rest of the book, you’ll see mention made several times of methods that have bang equivalents. Unless otherwise specified, that means the bang version of Iterate through array of names one at a time [...]... domain of built-in Ruby methods Everyone who writes Ruby programs deals one way or another with object state—and that means dealing with the evolution of an object’s state, including changes to that state brought about by method calls during program execution What state means—and, therefore, what it means for a method call to change its receiver—varies from one case, one class, to another For a string object,... the string; for a ticket object, it’s the venue, price, performer, and so forth The case of ActiveRecord objects provides an interesting illustration of some of the ramifications of receiver-changing under complex— and, for our purposes, particular relevant—circumstances 9.3.3 Specialized and extended receiver-changing in ActiveRecord objects Depending how much, and what, you’ve done with Rails, and... objects, along with the special object nil 9 .6. 1 True and false as states Every expression in Ruby is either true or false, in a logical or Boolean sense The best way to get a handle on this is to think in terms of conditional statements For every expression in Ruby, you can do this: if expression # execution reaches this point only if expression is true end For lots of expressions, such code makes no... “b” is greater than the string “a” However, “a” is greater than “A”, because the order is done by ASCII value, and the ASCII values for “a” and “A” are 97 and 65 , respectively Similarly, the string “.” is greater than “,” because the ASCII value for a period is 46 and that for a comma is 44 At this point, we’ll leave strings behind—although you’ll continue to see them all over the place—and turn our attention... Comparing two objects 251 9.7 Comparing two objects Ruby objects are created with the capacity to compare themselves to other objects for equality, using any of several methods Some objects can also compare themselves to each other for greater-than and less-than relationships; and you can teach objects that can’t do these things how to do them Tests for equality are the most common comparison tests,... Object class, these methods are redefined to do meaningful work for different objects Two of them, at most, are redefined; equal? is usually left alone so that you can always use it to check whether two objects are exactly the same object Furthermore, Ruby gives you a suite of tools for object comparisons, and not always just comparison for equality We’ll look next at how equality tests and their redefinitions... object needs, or should have, all these methods (It’s hard to imagine what it would mean for one bicycle to be greater than or equal to another.) But for those that do need them, Ruby provides a convenient way to get them All you have to do is the following: 1 Mix in a module called Comparable (which comes with Ruby) 2 Define a comparison method with the name in your class The comparison method... Once you’ve done that, Ruby has all it needs to provide the corresponding comparison methods For example, let’s say you’re taking bids on a job and using a Ruby script to help you keep track of what bids have come in You decide it would be handy to be able to compare any two Bid objects, based on estimate, using simple comparison operators like > and < Greater than means asking for more money, and less... methods: built-in methods for performing conversions from one core class to another The chapter also reviewed the importance of iterators, something you’ll see a lot of in upcoming chapters You’ve also learned a number of important points and techniques concerning Boolean (true/false) values and comparison between objects You’ve seen that every object in Ruby has a Boolean value and that Ruby also has special... that perform the change on the original string instead of returning a new string ■ A few non-bang methods perform changes on the original string The names of these methods make it clear that this is happening (such as replace), even though there’s no ! on the name ■ Some string methods return something other than a string for example, the to_i (to integer) conversion method Working with strings 261 Another . of the irb session format for the code examples, and you can often try the examples with small variations to get a feel for how Ruby behaves. 9.1 Ruby s literal constructors Ruby has a lot of. transform the original object. Examples of such pairs of methods include sort / sort! for arrays, upcase / upcase! for strings, chomp / chomp! for strings, and reverse / reverse! for. concepts in Ruby ■ Comparing two objects Ruby- wide techniques, both default and customizable, for object-to-object comparison ■ Listing an object’s methods—An important set of techniques for runtime reflection