Implementing Enumerable: Write One Method, Get 22 Free

Implementing Enumerable Write One Method, Get 22 Free

Problem

You want to give a class all the useful iterator and iteration-related features of Ruby's arrays (sort, detect, inject, and so on), but your class can't be a subclass of Array. You don't want to define all those methods yourself.

Solution

Implement an each method, then include the Enumerable module. It defines 22 of the most useful iteration methods in terms of the each implementation you provide.

Here's a class that keeps multiple arrays under the covers. By defining each, it can expose a large interface that lets the user treat it like a single array:

	class MultiArray
	 include Enumerable

	 def initialize(*arrays)
	 @arrays = arrays
	 end

	 def each
	 @arrays.each { |a| a.each { |x| yield x } }
	 end
	end

	ma = MultiArray.new([1, 2], [3], [4])
	ma.collect # => [1, 2, 3, 4]
	ma.detect { |x| x > 3 } # => 4
	ma.map { |x| x ** 2 } # => [1, 4, 9, 16]
	ma.each_with_index { |x, i| puts "Element #{i} is #{x}" }
	# Element 0 is 1
	# Element 1 is 2
	# Element 2 is 3
	# Element 3 is 4

 

Discussion

The Enumerable module is the most common mixin module. It lets you add a lot of behavior to your class for a little investment. Since Ruby relies so heavily on iterator methods, and almost every data structure can be iterated over in some way, it's no wonder that so many of the classes in Ruby's standard library include Enumerable: Dir, Hash, Range, and String, just to name a few.

Here's the complete list of methods you can get by including Enumerable. Many of them are described elsewhere in this book, especially in Chapter 4. Perhaps the most useful are collect, inject, find_all, and sort_by.

	Enumerable.instance_methods.sort
	# => ["all?", "any?", "collect", "detect", "each_with_index", "entries",
	# => "find", "find_all", "grep", "include?", "inject", "map", "max",
	# => "member?", "min", "partition", "reject", "select", "sort", "sort_by",
	# => "to_a", "zip"]

Although you can get all these methods simply by implementing an each method, some of the methods won't work unless your each implementation returns objects that can be compared to each other. For example, a data structure that contains both numbers and strings can't be sorted, since it makes no sense to compare a number to a string:

	ma.sort # => [1, 2, 3, 4]
	mixed_type_ma = MultiArray.new([1, 2, 3], ["a", "b", "c"])
	mixed_type_ma.sort
	# ArgumentError: comparison of Fixnum with String failed

The methods subject to this restriction are max, min, sort, and sort_by. Since you probably don't have complete control over the types of the data stored in your data structure, the best strategy is probably to just let a method fail if the data is incompatible. This is what Array does:

	[1, 2, 3, "a", "b", "c"].sort
	# ArgumentError: comparison of Fixnum with String failed

One more example: in this one, I'll make Module itself include Enumerable. My each implementation will iterate over the instance methods defined by a class or module. This makes it easy to find methods of a class that meet certain criteria.

	class Module
	 include Enumerable
	 def each
	 instance_methods.each { |x| yield x }
	 end
	end

	# Find all instance methods of String that modify the string in place.
	String.find_all { |method_name| method_name[-1] == ?! }
	# => ["sub!", "upcase!", "delete!", "lstrip!", "succ!", "gsub!",
	# => "squeeze!", "downcase!", "rstrip!", "slice!", "chop!", "capitalize!",
	# => "tr!", "chomp!", "next!", "swapcase!", "reverse!", "tr_s!", "strip!"]

	# Find all instance methods of Fixnum that take 2 arguments.
	sample = 0
	sample.class.find_all { |method_name| sample.method(method_name).arity == 2 }
	# => ["instance_variable_set", "between?"]

 

See Also

  • Many of the recipes in Chapter 4 actually cover methods of Enumerable; see especially Recipe 4.12, "Building Up a Hash Using Injection"
  • Recipe 9.1, "Simulating Multiple Inheritance with Mixins"


Strings

Numbers

Date and Time

Arrays

Hashes

Files and Directories

Code Blocks and Iteration

Objects and Classes8

Modules and Namespaces

Reflection and Metaprogramming

XML and HTML

Graphics and Other File Formats

Databases and Persistence

Internet Services

Web Development Ruby on Rails

Web Services and Distributed Programming

Testing, Debugging, Optimizing, and Documenting

Packaging and Distributing Software

Automating Tasks with Rake

Multitasking and Multithreading

User Interface

Extending Ruby with Other Languages

System Administration



Ruby Cookbook
Ruby Cookbook (Cookbooks (OReilly))
ISBN: 0596523696
EAN: 2147483647
Year: N/A
Pages: 399

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net