Functional Processing of an Array
Ruby, Iteration 4
First of all, Haskell List
s are equivalent to Ruby
Array
s. They're also equivalent to Java
List
s and C++ vector
s. Well, Java and C++
don't have functional processing—yet!
But all those cool list-processing functions you saw in Haskell are also available in Ruby.
Set Up
- Create a new folder
_languages/ruby/iteration03
. - Create a new test-case class
MapSelectInjectTest
in this folder. - Create a new
Rakefile
in this folder.
Check out the Ruby templates.
Ruby also has an interactive interpreter:
unix-% irb
Run irb in the folder for this iteration.
Map
Here's a map
:
>> [1, 2, 3].map { |x| x + 1 }
Try this in irb.
There are two significant differences between Haskell and Ruby that are revealed by this expression.
- Ruby is, first and foremost, object-oriented. So
map
is a method that must be applied toArray
objects. Ruby uses the.
notation to invoke methods on objects. - Lambda abstraction have to be written explicitly in Ruby. You
could have gotten by with
(+1)
for the function to map in Haskell, but in Ruby you have to provide the full definition.
You can assert this result like so:
assert_equal [2, 3, 4], [1, 2, 3].map { |n| n + 1 }
Write a test method test_map_plus_one
that make
three assertions for this application of map
using
three different arrays.
Select
Haskell calls it filter
; Ruby calls it
select
.
>> (1..20).to_a.select { |n| n % 2 == 0 }
If you're not sure what any subexpression does, try it in irb! (Or GHCi if it's Haskell!)
In Ruby it's (1..20).to_a
; in Haskell it's
[1..20]
.
Write a new test method test_select_evens
that
makes three assertions on three different arrays.
Inject
Haskell calls it foldl
or foldr
; Ruby
calls it inject
. A puzzle: which is it?!!?
>> (1..20).to_a.inject(0) { |sum, n| sum + n }
Unlike foldl
and foldr
, you have to be
explicit about the accumulator that's collecting the folded value
(sum
in this example). Note carefully that the lambda
just has to return the next value; don't set
sum
(or whatever accumulator you use) to be the new
value!
Write a new test method test_inject_sum
that makes
three assertions with three different arrays.
Things for You to Do
Add new test methods to MapSelectInjectTest
as
described here. Make at least three assertions in each test method
demonstrating the requested technique. (One of the assertions can
(and should!) be on the empty array.)
test_sum_of_squares
should sum up the squares of the elements using bothmap
andinject
.test_sum_of_squares_of_positives
should sum up the squares of the positive elements usingselect
,map
, andinject
.test_sum_of_evens_with_select_and_inject
should add together only the even numbers using bothselect
andinject
.test_sum_of_evens_with_only_inject
should add together only the even numbers using onlyinject
.test_order_matters
looks like this:result = [0, 1, -1, 2, -2, 3, -3].map { |x| x + 1 }.select { |x| x > 1 } assert_equal [2, 3, 4], result # TODO: select, then map assert_equal [2, 3, 4], result
Replace theTODO
comment with an assignment toresult
which appliesselect
first.test_exponents
should compute a chain of exponents using onlyreverse
,**
, andinject
.test_reverse_with_inject
should reverse the elements of the list using onlyinject
and array concatenation ([a] + [b]
).
Questions to consider for an exam:
- Why did you have to change the lambdas when switching the order
of
map
andselect
? - Why doesn't Ruby have an
inject_left
and aninject_right
?