Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Ruby / Yukihiro Matsumoto_Programming Ruby.doc
Скачиваний:
122
Добавлен:
06.06.2015
Размер:
2.71 Mб
Скачать

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:

  1. Modules provide a namespace and prevent name clashes.

  2. 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.

Соседние файлы в папке Ruby