Converting and Coercing Objects to Different Types

Problem

You have an object of one type and you want to use it as though it were of another type.

Solution

You might not have to do anything at all. Ruby doesn't enforce type safety unless the programmer has explicitly written it in. If your original class defines the same methods as the class you were thinking of converting it to, you might be able to use your object as is.

If you do have to convert from one class to another, Ruby provides conversion methods for most common paths:

	"4".to_i # => 4
	4.to_s # => "4"
	Time.now.to_f # => 1143572140.90932
	{ "key1" => "value1", "key2" => "value2" }.to_a
	# => [["key1", "value1"], ["key2", "value2"]]

If all else fails, you might be able to manually create an instance of the new class, and set its instance variables using the old data.

Discussion

Some programming languages have a "cast" operator that forces the compiler to treat an object of one type like an object of another type. A cast is usually a programmer's assertion that he knows more about the types of objects than the compiler. Ruby has no cast operator. From Ruby's perspective, type checking is just an extra hoop you have to jump through. A cast operator would make it easier to jump through that hoop, but Ruby omits the hoop altogether.

Wherever you're tempted to cast an object to another type, you should be able to just do nothing. If your object can be used as the other type, there's no problem: if not, then casting it to that type wouldn't have helped anyway.

Here's a concrete example. You probably don't need to convert a hash into an array just so you can pass it into an iteration method that expects an array. If that method only calls each on its argument, it doesn't really "expect an array:" it expects a reasonable implementation of each. Ruby hashes provide that implementation just as well as arrays.

	def print_each(array)
	 array.each { |x| puts x.inspect }
	end

	hash = { "pickled peppers" => "peck of",
	 "sick sheep" => "sixth" }
	print_each(hash.to_a)
	# ["sick sheep", "sixth"]
	# ["pickled peppers", "peck of"]

	print_each(hash)
	# ["sick sheep", "sixth"]
	# ["pickled peppers", "peck of"]

Ruby does provide methods for converting one data type into another. These methods follow the naming convention to_[other type], and they usually create a brand new object of the new type, but containing the old data. They are generally used when you want to use some method of the new data type, or display or store the data in another format.

In the case of print_each, not converting the hash to an array gives the same results as converting, and the code is shorter and faster when it doesn't do the conversion. But converting a hash into an array of key-value pairs does let you call methods defined by Array but not by Hash. If what you really want is an arraysomething ordered, something you can modify with push and popthere's no reason not to convert to an array and stop using the hash.

	array = hash.to_a
	# => [["sick sheep", "sixth"], ["pickled peppers", "peck of"]]

	# Print out a tongue-twisting invoice.
	until array.empty?
	 item, quantity = array.pop
	 puts "#{quantity} #{item}"
	end
	# peck of pickled peppers
	# sixth sick sheep

Some methods convert one data type to another as a side effect: for instance, sorting a hash implicitly converts it into an array, since hashes have no notion of ordering.

	hash.sort
	# => [["pickled peppers", "peck of"], ["sick sheep", "sixth"]]

 

Number conversion and coercion

Most of the commonly used conversion methods in stock Ruby are in the number classes. This makes sense because arithmetic operations can give different results depending on the numeric types of the inputs. This is one place where Ruby's conversion methods are used as a substitute for casting. Here, to_f is used to force Ruby to perform floating-point division instead of integer division:

	3/4 # => 0
	3/4.to_f # => 0.75

Integers and floating-point numbers have to_i and to_f methods to convert back and forth between each other. BigDecimal or Rational objects define the same methods; they also define some brand new conversion methods: to_d to convert a number to BigDecimal, and to_r to convert a number to Rational. To convert to or from Rational objects you just have to require 'rational'. To convert to or from BigDecimal objects you must require 'bigdecimal' and also require 'bigdecimal/utils'.

	require 'rational'
	Rational(1, 3).to_f # => 0.333333333333333
	Rational(11, 5).to_i # => 2
	2.to_r # => Rational(2, 1)

Here's a table that shows how to convert between Ruby's basic numeric types.

Table 8-1.

 

Integer

Floating-point

BigDecimal

Rational

Integer

to_i(identity)

to_f

to_r.to_d

to_r

Float

to_i(decimal discard)

to_f (new)

to_d

to_d.to_r (include bigdecimal/util)

BigDecimal

to_i

to_f

to_d (new)

to_r (include bigdecimal/util)

Rational

to_i(dec discard)

to_f (approx)

to_d (include bigdecimal/util)

to_r (identity)

Two cases deserve special mention. You can't convert a floating-point number directly into rational number, but you can do it through BigDecimal. The result will be imprecise, because floating-point numbers are imprecise.

	require 'bigdecimal'
	require 'bigdecimal/util'

	one_third = 1/3.0 # => 0.333333333333333
	one_third.to_r
	# NoMethodError: undefined method 'to_r' for 0.333333333333333:Float
	one_third.to_d.to_r # => Rational(333333333333333, 1000000000000000)

Similarly, the best way to convert an Integer to a BigDecimal is to convert it to a rational number first.

	20.to_d
	# NoMethodError: undefined method 'to_d' for 20:Fixnum
	20.to_r.to_d # => #

When it needs to perform arithmetic operations on two numbers of different types, Ruby uses a method called coerce. Every numeric type implements a coerce method that takes a single number as its argument. It returns an array of two numbers: the object itself and the argument passed into coerce. Either or both numbers might undergo a conversion, but whatever happens, both the numbers in the return array must be of the same type. The arithmetic operation is performed on these two numbers, coerced into the same type.

This way, the authors of numeric classes don't have to make their arithmetic operations support operations on objects of different types. If they implement coerce, they know that their arithmetic operations will only be passed in another object of the same type.

This is easiest to see for the Complex class. Below, every input to coerce is transformed into an equivalent complex number so that it can be used in arithmetic operations along with the complex number i:

	require 'complex'
	i = Complex(0, 1) # => Complex(0, 1)
	i.coerce(3) # => [Complex(3, 0), Complex(0, 1)]
	i.coerce(2.5) # => [Complex(2.5, 0), Complex(0, 1)]

This, incidentally, is why 3/4 uses integer division but 3/4.to_f uses floating-point division. 3.coerce(4) returns two integer objects, so the arithmetic methods of Fixnum are used. 3.coerce(4.0) returns two floating-point numbers, so the arithmetic methods of Float are used.

Other conversion methods

All Ruby objects define conversion methods to_s and inspect, which give a string representation of the object. Usually inspect is the more readable of the two formats.

	[1, 2, 3].to_s # => "123"
	[1, 2, 3].inspect # => "[1, 2, 3]"

Here's a grab bag of other notable conversion methods found within the Ruby standard library. This should give you a picture of what Ruby conversion methods typically do.

  • MatchData#to_a creates an array containing the match groups of a regular expression match.
  • Matrix#to_a converts a mathematical matrix into a nested array.
  • Enumerable#to_a iterates over any enumerable object and collects the results in an array.
  • Net::HTTPHeader#to_hash returns a hash mapping the names of HTTP headers to their values.
  • String#to_f and String#to_i parse strings into numeric objects. Including the bigdecimal/util library will define String#to_d, which parses a string into a BigDecimal object.
  • Including the yaml library will define to_yaml methods for all of Ruby's built-in classes: Array#to_yaml, String#to_yaml, and so on.

See Also

  • Recipe 1.12, "Testing Whether an Object Is String-Like"
  • Recipe 2.1, "Parsing a Number from a String"
  • Recipe 8.10, "Getting a Human-Readable Printout of Any Object"


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