Understanding Computation (2 page)

Read Understanding Computation Online

Authors: Tom Stuart

Tags: #COMPUTERS / Programming / General

BOOK: Understanding Computation
3.59Mb size Format: txt, pdf, ePub
Classes and Modules

It’s convenient to be
able to share method definitions between many objects. In Ruby, we can put method
definitions inside a
class
,
then create new objects by sending the
new
message to that class. The objects we get back
are
instances
of the class and incorporate its methods. For
example:

>>
class
Calculator
def
divide
(
x
,
y
)
x
/
y
end
end
=> nil
>>
c
=
Calculator
.
new
=> #
>>
c
.
class
=> Calculator
>>
c
.
divide
(
10
,
2
)
=> 5

Note that defining a method inside a
class
definition adds the method to instances of
that class, not to
main
:

>>
divide
(
10
,
2
)
NoMethodError: undefined method `divide' for main:Object

One class can bring in another class’s method definitions
through
inheritance
:

>>
class
MultiplyingCalculator
<
Calculator
def
multiply
(
x
,
y
)
x
*
y
end
end
=> nil
>>
mc
=
MultiplyingCalculator
.
new
=> #
>>
mc
.
class
=> MultiplyingCalculator
>>
mc
.
class
.
superclass
=> Calculator
>>
mc
.
multiply
(
10
,
2
)
=> 20
>>
mc
.
divide
(
10
,
2
)
=> 5

A method in a subclass can call a
superclass method of the same name by using the
super
keyword:

>>
class
BinaryMultiplyingCalculator
<
MultiplyingCalculator
def
multiply
(
x
,
y
)
result
=
super
(
x
,
y
)
result
.
to_s
(
2
)
end
end
=> nil
>>
bmc
=
BinaryMultiplyingCalculator
.
new
=> #
>>
bmc
.
multiply
(
10
,
2
)
=> "10100"

Another way of sharing
method definitions is to declare them in a
module
, which can then be included by any
class:

>>
module
Addition
def
add
(
x
,
y
)
x
+
y
end
end
=> nil
>>
class
AddingCalculator
include
Addition
end
=> AddingCalculator
>>
ac
=
AddingCalculator
.
new
=> #
>>
ac
.
add
(
10
,
2
)
=> 12
Miscellaneous Features

Here’s a grab bag of useful Ruby features that we’ll need for the
example code in this book.

Local Variables and Assignment

As we’ve already
seen, Ruby lets us declare local variables just by
assigning a value to them:

>>
greeting
=
'hello'
=> "hello"
>>
greeting
=> "hello"

We can also use
parallel assignment
to
assign values to several variables at once by breaking
apart an array:

>>
width
,
height
,
depth
=
[
1000
,
2250
,
250
]
=> [1000, 2250, 250]
>>
height
=> 2250
String Interpolation

Strings can be
single- or double-quoted. Ruby automatically performs
interpolation
on double-quoted strings, replacing
any
#{
expression
}
with
its result:

>>
"hello
#{
'dlrow'
.
reverse
}
"
=> "hello world"

If an interpolated expression returns an
object that isn’t a string, that object is automatically
sent a
to_s
message
and is expected to return a string that can be used in
its place. We can use this to control how interpolated objects
appear:

>>
o
=
Object
.
new
=> #
>>
def
o
.
to_s
'a new object'
end
=> nil
>>
"here is
#{
o
}
"
=> "here is a new object"
Inspecting Objects

Something
similar happens whenever IRB needs to display an object: the object is sent the
inspect
message and should return a string
representation of itself. All objects in Ruby have sensible default implementations of
#inspect
, but by providing our own definition, we can
control how an object appears on the console:

>>
o
=
Object
.
new
=> #
>>
def
o
.
inspect
'[my object]'
end
=> nil
>>
o
=> [my object]
Printing Strings

The
#puts
method is
available to every Ruby object (including
main
), and can be used to print strings to
standard output:

>>
x
=
128
=> 128
>>
while
x
<
1000
puts
"x is
#{
x
}
"
x
=
x
*
2
end
x is 128
x is 256
x is 512
=> nil
Variadic Methods

Method definitions
can use the
*
operator to support
a variable number of
arguments:

>>
def
join_with_commas
(
*
words
)
words
.
join
(
', '
)
end
=> nil
>>
join_with_commas
(
'one'
,
'two'
,
'three'
)
=> "one, two, three"

A method definition can’t have more than one variable-length
parameter, but normal parameters may appear on either side of it:

>>
def
join_with_commas
(
before
,
*
words
,
after
)
before
+
words
.
join
(
', '
)
+
after
end
=> nil
>>
join_with_commas
(
'Testing: '
,
'one'
,
'two'
,
'three'
,
'.'
)
=> "Testing: one, two, three."

The
*
operator can also be used
to treat each element of an array as a separate argument when sending a
message:

>>
arguments
=
[
'Testing: '
,
'one'
,
'two'
,
'three'
,
'.'
]
=> ["Testing: ", "one", "two", "three", "."]
>>
join_with_commas
(
*
arguments
)
=> "Testing: one, two, three."

And finally,
*
works in
parallel assignment too:

>>
before
,
*
words
,
after
=
[
'Testing: '
,
'one'
,
'two'
,
'three'
,
'.'
]
=> ["Testing: ", "one", "two", "three", "."]
>>
before
=> "Testing: "
>>
words
=> ["one", "two", "three"]
>>
after
=> "."
Blocks

A
block
is a piece of Ruby
code surrounded by
do
/
end
or
curly brackets. Methods can take an implicit block
argument and call the code in that block with
the
yield
keyword:

>>
def
do_three_times
yield
yield
yield
end
=> nil
>>
do_three_times
{
puts
'hello'
}
hello
hello
hello
=> nil

Blocks can take
arguments:

>>
def
do_three_times
yield
(
'first'
)
yield
(
'second'
)
yield
(
'third'
)
end
=> nil
>>
do_three_times
{
|
n
|
puts
"
#{
n
}
: hello"
}
first: hello
second: hello
third: hello
=> nil

yield
returns the result of
executing the block:

>>
def
number_names
[
yield
(
'one'
),
yield
(
'two'
),
yield
(
'three'
)
].
join
(
', '
)
end
=> nil
>>
number_names
{
|
name
|
name
.
upcase
.
reverse
}
=> "ENO, OWT, EERHT"
Enumerable

Ruby has a built-in module
called
Enumerable
that’s included by
Array
,
Hash
,
Range
, and
other classes that represent collections of values.
Enumerable
provides helpful methods
for traversing, searching, and sorting collections, many of which expect
to be called with a block. Usually the code in the block will be run
against some or all values in the collection as part of whatever job the
method does. For example:

>>
(
1
.
.
10
)
.
count
{
|
number
|
number
.
even?
}
=> 5
>>
(
1
.
.
10
)
.
select
{
|
number
|
number
.
even?
}
=> [2, 4, 6, 8, 10]
>>
(
1
.
.
10
)
.
any?
{
|
number
|
number
<
8
}
=> true
>>
(
1
.
.
10
)
.
all?
{
|
number
|
number
<
8
}
=> false
>>
(
1
.
.
5
)
.
each
do
|
number
|
if
number
.
even?
puts
"
#{
number
}
is even"
else
puts
"
#{
number
}
is odd"
end
end
1 is odd
2 is even
3 is odd
4 is even
5 is odd
=> 1..5
>>
(
1
.
.
10
)
.
map
{
|
number
|
number
*
3
}
=> [3, 6, 9, 12, 15, 18, 21, 24, 27, 30]

It’s common for the block to take one argument and send it one message with no
arguments, so Ruby
provides a
&:
message
shorthand as a more concise way of
writing the block
{ |object|
object.
message
}
:

>>
(
1
.
.
10
)
.
select
(
&
:even?
)
=> [2, 4, 6, 8, 10]
>>
[
'one'
,
'two'
,
'three'
].
map
(
&
:upcase
)
=> ["ONE", "TWO", "THREE"]

One of
Enumerable
’s methods,
#flat_map
, can be
used to evaluate an array-producing block for every value in a collection and
concatenate the results:

>>
[
'one'
,
'two'
,
'three'
].
map
(
&
:chars
)
=> [["o", "n", "e"], ["t", "w", "o"], ["t", "h", "r", "e", "e"]]
>>
[
'one'
,
'two'
,
'three'
].
flat_map
(
&
:chars
)
=> ["o", "n", "e", "t", "w", "o", "t", "h", "r", "e", "e"]

Another useful method is
#inject
, which evaluates a block for every
value in a collection and accumulates a final result:

>>
(
1
.
.
10
)
.
inject
(
0
)
{
|
result
,
number
|
result
+
number
}
=> 55
>>
(
1
.
.
10
)
.
inject
(
1
)
{
|
result
,
number
|
result
*
number
}
=> 3628800
>>
[
'one'
,
'two'
,
'three'
].
inject
(
'Words:'
)
{
|
result
,
word
|
"
#{
result
}
#{
word
}
"
}
=> "Words: one two three"

© 100 Vampire Novels China Edition 2015 - 2024    Contact for me [email protected]