- •Programming Ruby The Pragmatic Programmer's Guide
- •Foreword
- •Preface
- •Ruby Sparkles
- •What Kind of Language Is Ruby?
- •Is Ruby for Me?
- •Why Did We Write This Book?
- •Ruby Versions
- •Installing Ruby
- •Building Ruby
- •Running Ruby
- •Interactive Ruby
- •Ruby Programs
- •Resources
- •Acknowledgments
- •Notation Conventions
- •Roadmap
- •Ruby.New
- •Ruby Is an Object-Oriented Language
- •Some Basic Ruby
- •Arrays and Hashes
- •Control Structures
- •Regular Expressions
- •Blocks and Iterators
- •Reading and 'Riting
- •Onward and Upward
- •Classes, Objects, and Variables
- •Inheritance and Messages
- •Inheritance and Mixins
- •Objects and Attributes
- •Writable Attributes
- •Virtual Attributes
- •Class Variables and Class Methods
- •Class Variables
- •Class Methods
- •Singletons and Other Constructors
- •Access Control
- •Specifying Access Control
- •Variables
- •Containers, Blocks, and Iterators
- •Containers
- •Implementing a SongList Container
- •Blocks and Iterators
- •Implementing Iterators
- •Blocks for Transactions
- •Blocks Can Be Closures
- •Standard Types
- •Numbers
- •Strings
- •Working with Strings
- •Ranges as Sequences
- •Ranges as Conditions
- •Ranges as Intervals
- •Regular Expressions
- •Patterns
- •Anchors
- •Character Classes
- •Repetition
- •Alternation
- •Grouping
- •Pattern-Based Substitution
- •Backslash Sequences in the Substitution
- •Object-Oriented Regular Expressions
- •More About Methods
- •Defining a Method
- •Variable-Length Argument Lists
- •Methods and Blocks
- •Calling a Method
- •Expanding Arrays in Method Calls
- •Making Blocks More Dynamic
- •Collecting Hash Arguments
- •Expressions
- •Operator Expressions
- •Miscellaneous Expressions
- •Command Expansion
- •Backquotes Are Soft
- •Assignment
- •Parallel Assignment
- •Nested Assignments
- •Other Forms of Assignment
- •Conditional Execution
- •Boolean Expressions
- •Defined?, And, Or, and Not
- •If and Unless Expressions
- •If and Unless Modifiers
- •Case Expressions
- •Iterators
- •Break, Redo, and Next
- •Variable Scope and Loops
- •Exceptions, Catch, and Throw
- •The Exception Class
- •Handling Exceptions
- •Tidying Up
- •Play It Again
- •Raising Exceptions
- •Adding Information to Exceptions
- •Catch and Throw
- •Modules
- •Namespaces
- •Instance Variables in Mixins
- •Iterators and the Enumerable Module
- •Including Other Files
- •Basic Input and Output
- •What Is an io Object?
- •Opening and Closing Files
- •Reading and Writing Files
- •Iterators for Reading
- •Writing to Files
- •Talking to Networks
- •Threads and Processes
- •Multithreading
- •Creating Ruby Threads
- •Manipulating Threads
- •Thread Variables
- •Threads and Exceptions
- •Controlling the Thread Scheduler
- •Mutual Exclusion
- •The Mutex Class
- •Condition Variables
- •Running Multiple Processes
- •Spawning New Processes
- •Independent Children
- •Blocks and Subprocesses
- •When Trouble Strikes
- •Ruby Debugger
- •Interactive Ruby
- •Editor Support
- •But It Doesn't Work!
- •But It's Too Slow!
- •Create Locals Outside Blocks
- •Use the Profiler
- •Ruby and Its World
- •Command-Line Arguments
- •Command-Line Options
- •Program Termination
- •Environment Variables
- •Writing to Environment Variables
- •Where Ruby Finds Its Modules
- •Build Environment
- •Ruby and the Web
- •Writing cgi Scripts
- •Using cgi.Rb
- •Quoting
- •Creating Forms and html
- •Cookies
- •Sessions
- •Embedding Ruby in html
- •Using eruby
- •Installing eruby in Apache
- •Improving Performance
- •Ruby Tk
- •Simple Tk Application
- •Widgets
- •Setting Widget Options
- •Getting Widget Data
- •Setting/Getting Options Dynamically
- •Sample Application
- •Binding Events
- •Scrolling
- •Just One More Thing
- •Translating from Perl/Tk Documentation
- •Object Creation
- •Running Ruby Under Windows
- •Windows Automation
- •Getting and Setting Properties
- •Named Arguments
- •For each
- •An Example
- •Optimizing
- •Extending Ruby
- •Ruby Objects in c
- •Value as a Pointer
- •Value as an Immediate Object
- •Writing Ruby in c
- •Evaluating Ruby Expressions in c
- •Sharing Data Between Ruby and c
- •Directly Sharing Variables
- •Wrapping c Structures
- •An Example
- •Memory Allocation
- •Creating an Extension
- •Creating a Makefile with extconf.Rb
- •Static Linking
- •Embedding a Ruby Interpreter
- •Bridging Ruby to Other Languages
- •Ruby c Language api
- •The Ruby Language
- •Source Layout
- •Begin and end Blocks
- •General Delimited Input
- •The Basic Types
- •Integer and Floating Point Numbers
- •Strings
- •Requirements for a Hash Key
- •Symbols
- •Regular Expressions
- •Regular Expression Options
- •Regular Expression Patterns
- •Substitutions
- •Extensions
- •Variable/Method Ambiguity
- •Variables and Constants
- •Scope of Constants and Variables
- •Predefined Variables
- •Exception Information
- •Pattern Matching Variables
- •Input/Output Variables
- •Execution Environment Variables
- •Standard Objects
- •Global Constants
- •Expressions Single Terms
- •Operator Expressions
- •More on Assignment
- •Parallel Assignment
- •Block Expressions
- •Boolean Expressions
- •Truth Values
- •And, Or, Not, and Defined?
- •Comparison Operators
- •Ranges in Boolean Expressions
- •Regular Expressions in Boolean Expressions
- •While and Until Modifiers
- •Break, Redo, Next, and Retry
- •Method Definition
- •Method Arguments
- •Invoking a Method
- •Class Definition
- •Creating Objects from Classes
- •Class Attribute Declarations
- •Module Definitions
- •Mixins---Including Modules
- •Module Functions
- •Access Control
- •Blocks, Closures, and Proc Objects
- •Proc Objects
- •Exceptions
- •Raising Exceptions
- •Handling Exceptions
- •Retrying a Block
- •Catch and Throw
- •Classes and Objects
- •How Classes and Objects Interact
- •Your Basic, Everyday Object
- •Object-Specific Classes
- •Mixin Modules
- •Extending Objects
- •Class and Module Definitions
- •Class Names Are Constants
- •Inheritance and Visibility
- •Freezing Objects
- •Locking Ruby in the Safe
- •Safe Levels
- •Tainted Objects
- •Reflection, ObjectSpace, and Distributed Ruby
- •Looking at Objects
- •Looking Inside Objects
- •Looking at Classes
- •Looking Inside Classes
- •Calling Methods Dynamically
- •Performance Considerations
- •System Hooks
- •Runtime Callbacks
- •Tracing Your Program's Execution
- •How Did We Get Here?
- •Marshaling and Distributed Ruby
- •Custom Serialization Strategy
- •Distributed Ruby
- •Compile Time? Runtime? Anytime!
- •Standard Library
Adding Information to Exceptions
You can define your own exceptions to hold any information that you need to pass out from the site of an error. For example, certain types of network errors might be transient depending on the circumstances. If such an error occurs, and the circumstances are right, you could set a flag in the exception to tell the handler that it might be worth retrying the operation.
class RetryException < RuntimeError attr :okToRetry def initialize(okToRetry) @okToRetry = okToRetry end end |
Somewhere down in the depths of the code, a transient error occurs.
def readData(socket) data = socket.read(512) if data.nil? raise RetryException.new(true), "transient read error" end # .. normal processing end |
Higher up the call stack, we handle the exception.
begin stuff = readData(socket) # .. process stuff rescue RetryException => detail retry if detail.okToRetry raise end |
Catch and Throw
While the exception mechanism of raiseandrescueis great for abandoning execution when things go wrong, it's sometimes nice to be able to jump out of some deeply nested construct during normal processing. This is wherecatchandthrowcome in handy.
catch (:done) do while gets throw :done unless fields = split(/\t/) songList.add(Song.new(*fields)) end songList.play end |
catchdefines a block that is labeled with the given name (which may be aSymbolor aString). The block is executed normally until athrowis encountered.
When Ruby encounters a throw, it zips back up the call stack looking for acatchblock with a matching symbol. When it finds it, Ruby unwinds the stack to that point and terminates the block. If thethrowis called with the optional second parameter, that value is returned as the value of thecatch. So, in the previous example, if the input does not contain correctly formatted lines, thethrowwill skip to the end of the correspondingcatch, not only terminating thewhileloop but also skipping the playing of the song list.
The following example uses a throwto terminate interaction with the user if ``!'' is typed in response to any prompt.
def promptAndGet(prompt) print prompt res = readline.chomp throw :quitRequested if res == "!" return res end
catch :quitRequested do name = promptAndGet("Name: ") age = promptAndGet("Age: ") sex = promptAndGet("Sex: ") # .. # process information end |
As this example illustrates, the throwdoes not have to appear within the static scope of thecatch.
Modules
Modules are a way of grouping together methods, classes, and constants. Modules give you two major benefits:
Modules provide a namespace and prevent name clashes.
Modules implement the mixin facility.
Namespaces
As you start to write bigger and bigger Ruby programs, you'll naturally find yourself producing chunks of reusable code---libraries of related routines that are generally applicable. You'll want to break this code out into separate files so the contents can be shared among different Ruby programs.
Often this code will be organized into classes, so you'll probably stick a class (or a set of interrelated classes) into a file.
However, there are times when you want to group things together that don't naturally form a class.
An initial approach might be to put all these things into a file and simply load that file into any program that needs it. This is the way the C language works. However, there's a problem. Say you write a set of trigonometry functions sin,cos, and so on. You stuff them all into a file,trig.rb, for future generations to enjoy. Meanwhile, Sally is working on a simulation of good and evil, and codes up a set of her own useful routines, includingbeGoodandsin, and sticks them intoaction.rb. Joe, who wants to write a program to find out how many angels can dance on the head of a pin, needs to load bothtrig.rbandaction.rbinto his program. But both define a method calledsin. Bad news.
The answer is the module mechanism. Modules define a namespace, a sandbox in which your methods and constants can play without having to worry about being stepped on by other methods and constants. The trig functions can go into one module:
module Trig PI = 3.141592654 def Trig.sin(x) # .. end def Trig.cos(x) # .. end end |
and the good and bad action methods can go into another:
module Action VERY_BAD = 0 BAD = 1 def Action.sin(badness) # ... end end |
Module constants are named just like class constants, with an initial uppercase letter. The method definitions look similar, too: these module methods are defined just like class methods.
If a third program wants to use these modules, it can simply load up the two files (using the Ruby requirestatement, which we discuss on page 103) and reference the qualified names.
require "trig" require "action"
y = Trig.sin(Trig::PI/4) wrongdoing = Action.sin(Action::VERY_BAD) |
As with class methods, you call a module method by preceding its name with the module's name and a period, and you reference a constant using the module name and two colons.
Mixins
Modules have another, wonderful use. At a stroke, they pretty much eliminate the need for multiple inheritance, providing a facility called a mixin.
In the previous section's examples, we defined module methods, methods whose names were prefixed by the module name. If this made you think of class methods, your next thought might well be ``what happens if I define instance methods within a module?'' Good question. A module can't have instances, because a module isn't a class. However, you can includea module within a class definition. When this happens, all the module's instance methods are suddenly available as methods in the class as well. They getmixed in. In fact, mixed-in modules effectively behave as superclasses.
module Debug | ||
def whoAmI? | ||
"#{self.type.name} (\##{self.id}): #{self.to_s}" | ||
end | ||
end | ||
class Phonograph | ||
include Debug | ||
# ... | ||
end | ||
class EightTrack | ||
include Debug | ||
# ... | ||
end | ||
ph = Phonograph.new("West End Blues") | ||
et = EightTrack.new("Surrealistic Pillow") | ||
| ||
ph.whoAmI? |
» |
"Phonograph (#537762134): West End Blues" |
et.whoAmI? |
» |
"EightTrack (#537761824): Surrealistic Pillow" |
By including the Debugmodule, bothPhonographandEightTrackgain access to thewhoAmI?instance method.
A couple of points about the includestatement before we go on. First, it has nothing to do with files. C programmers use a preprocessor directive called#includeto insert the contents of one file into another during compilation. The Rubyincludestatement simply makes a reference to a named module. If that module is in a separate file, you must userequireto drag that file in before usinginclude. Second, a Rubyincludedoes not simply copy the module's instance methods into the class. Instead, it makes a reference from the class to the included module. If multiple classes include that module, they'll all point to the same thing. If you change the definition of a method within a module, even while your program is running, all classes that include that module will exhibit the new behavior.[Of course, we're speaking only of methods here. Instance variables are always per-object, for example.]
Mixins give you a wonderfully controlled way of adding functionality to classes. However, their true power comes out when the code in the mixin starts to interact with code in the class that uses it. Let's take the standard Ruby mixin Comparableas an example. TheComparablemixin can be used to add the comparison operators (<,<=,==,>=, and>), as well as the methodbetween?, to a class. For this to work,Comparableassumes that any class that uses it defines the operator<=>. So, as a class writer, you define the one method,<=>, includeComparable, and get six comparison functions for free. Let's try this with ourSongclass, by making the songs comparable based on their duration. All we have to do is include theComparablemodule and implement the comparison operator<=>.
class Song include Comparable def <=>(other) self.duration <=> other.duration end end |
We can check that the results are sensible with a few test songs.
song1 = Song.new("My Way", "Sinatra", 225) | ||
song2 = Song.new("Bicylops", "Fleck", 260) | ||
| ||
song1 <=> song2 |
» |
-1 |
song1 < song2 |
» |
true |
song1 == song1 |
» |
true |
song1 > song2 |
» |
false |
Finally, back on page 43 we showed an implementation of Smalltalk's injectfunction, implementing it within classArray. We promised then that we'd make it more generally applicable. What better way than making it a mixin module?
module Inject def inject(n) each do |value| n = yield(n, value) end n end def sum(initial = 0) inject(initial) { |n, value| n + value } end def product(initial = 1) inject(initial) { |n, value| n * value } end end |
We can then test this by mixing it into some built-in classes.
class Array | ||
include Inject | ||
end | ||
[ 1, 2, 3, 4, 5 ].sum |
» |
15 |
[ 1, 2, 3, 4, 5 ].product |
» |
120 |
class Range | ||
include Inject | ||
end | ||
(1..5).sum |
» |
15 |
(1..5).product |
» |
120 |
('a'..'m').sum("Letters: ") |
» |
"Letters: abcdefghijklm" |
For a more extensive example of a mixin, have a look at the documentation for the Enumerablemodule, which starts on page 403.