6
\$\begingroup\$

I'm currently following the tutorials over at RubyMonk, and one of the problems I need to solve is to write a subtract function that would meet these conditions:

  • invoking subtract(4, 5) should return -1
  • invoking subtract(-10, 2, 3) should return -15
  • invoking subtract(0, 0, 0, 0, -10) should return 10

Coming from a traditional imperative programming background (C, Lua, Java, etc.), my first attempt would have been something like this:

def subtract *numbers
  start = numbers[0]
  tail  = numbers.drop(1) 

  for i in tail do
    start -= i
  end

  return start
end

But, this just felt wrong in Ruby, and I don't doubt that it is. Trying to use a more Ruby-esque style, I thought it would be better to use some of Array's methods.

Here is my latest version:

def subtract *numbers
  (numbers.drop 1).inject(numbers[0]) { |x, y| x-y}
end

One thing to note is that this is essentially the same thing as above, I just moved most of the looping into Array#inject. To me, simply moving logic isn't a new style per se, and so conceptually this is nothing new to me.

Therefore my questions are:

  • Is this true Ruby style? If not, how could I make it so, and how would that new style differ from the current?
  • Is there any way to improve readability? ('Cause honestly the current version lacks quite a bit).
\$\endgroup\$

3 Answers 3

4
\$\begingroup\$

Some notes:

  • def subtract *numbers: While Ruby allows to omit parens, the community consensus is that it makes signatures harder to read.

  • (numbers.drop 1): When you have to write parens like this, it's a signal that you should just write them in the method call. It looks like Scala or Haskell, not Ruby.

  • As @Nakilon pointed out, you can drop the drop/first, a fold without an initial value takes the first one.

  • The identity value for subtraction is 0, let's use it for empty inputs.

  • xs.inject(initial) { |acc, x| acc.method(x) } -> xs.inject(initial, :method)

We can now write:

def subtract(*numbers)
  numbers.inject(0, :-)
end

For me, simply moving logic isn't a new style per se, and so conceptually this is nothing new to me.

I don't agree. The steps followed are conceptually the same, yes, but the use of a widely known generic abstraction like reduce/inject has reduced the overall complexity. Of course, one abstraction does not make much difference, but dozens of them change the way a whole program describes what it's doing. And that's what programing is about: building abstractions to reduce complexity.

\$\endgroup\$
2
  • \$\begingroup\$ Okay thank you for the guidance. :) I agree that abstraction is key. I just felt that perhaps Ruby had a more awesome, totally new, nothing a C programmer could of thought up way of doing it. :P P.S. I actually wanted my Ruby to look like Haskell, but okay, whatever...*shrugs* haha \$\endgroup\$
    – Miguel
    Apr 23, 2013 at 23:57
  • \$\begingroup\$ @Miguel. In fact we can write numbers.inject(0, :-) as Nakilon suggested. In any case, nothing special about Ruby on this regard, that can be done in any language that supports basic functional programming. \$\endgroup\$
    – tokland
    Apr 24, 2013 at 9:38
3
\$\begingroup\$

I don't understand, why do you need .first and .drop:

def subtract(*numbers)
  numbers.inject{ |acc, x| acc - x }
end

Or:

def subtract(*numbers)
  numbers.inject :-
end
\$\endgroup\$
0
0
\$\begingroup\$

They are NOT the same:
Original: "invoking subtract(4, 5) should return -1"

def subtract(*numbers)
  numbers.inject :-
end
subtract(4,5) -> -9

How do we get the correct result with nice Ruby code?

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Are you answering or asking? This space is reserved for answering only. \$\endgroup\$ Apr 16, 2018 at 10:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.