“I need a new drug”:http://youtube.com/watch?v=MMSFX1Vb3xQ. One that won’t quit. One that will let me sensibly structure the use of blocks in Ruby such that they can run across multiple lines and yet still chain blocks together.
OK, so its not really a _drug_ I need, per se. Though, I’m sure what Huey Lewis really needed was a new drug. But what I need is a convention, or perhaps some syntax. It should say to you, “HEY. Adam’s doing something clever with blocks here. Keep your eyes open.”
I find myself desiring a new syntax and/or convention, when using blocks. I’ve been trying to write in a more functional style lately, especially a pure-functional style wherein you never call a method for its side-effects. I don’t think I’m the only person doing this. Jim Weirich sort of alluded to it in a post about “when to use @do/end@ vs. braces”:http://onestepback.org/index.cgi/Tech/Ruby/BraceVsDoEnd.rdoc. Rick DeNatale “took it a step further”:http://talklikeaduck.denhaven2.com/articles/2007/10/02/ruby-blocks-do-or-brace. I want to lay it bare.
Let’s start with something innocuous:
ary = [1,2,3]
result = ary.map do |n|
x = n * 4
end
result # => [4, 8, 12]
You get the value back of each object in the array, _mapped_ to the result of calling the block. Fun times. Now, let’s put it on one line and do something clever with it.
ary.map { |n| n * 4 }.select { |n| n == 4 } # => [4]
Now we’re cooking! Ruby’s syntax allows us to chain methods _and_ blocks. Which turns out nice in this case where I want to filter down the array of mapped values. But let’s pretend we need to do something clever in those blocks.
ary.map { |n|
n * 4
# Some
# clever
# things...
}.select { |n|
n == 4
# More
# clever
# things...
}.any? { |n|
n == 4
# Gratuitiou
# clever
# things...
} # true
That’s the best I could come up with for chained, multi-line blocks. It looks “Weirichian”. Despite that, it kinda makes me vomit in my mouth.
So, since “I got awesome feedback on my last question of taste”:https://therealadam.com/archive/2008/04/28/can-i-send-you-a-message/, I’m tapping you, my favorite Ruby developer, for more. The ground rules are that you can’t extract the logic into a real method and you can’t jam everything onto one line. You *have* to use multi-line blocks. What looks good to you here?
So why *can’t* you extract the logic into multiple methods? Adding an artificial constraint in the name of forcing the issue might point to the possibility that *there is no right answer*. Maybe you need even more than a refactoring that extracts the code into several intention revealing methods. Maybe you need a new object.
Whether you have multiple blocks that span multiple lines, or just multiple lines of code, you could likely improve the overall quality of the code by decomposing the problem more rather than work around a ugly wart by putting some make up on it.
What is happening in the map block? And what are you wanting to select out of that transformed collection? And then what are you filtering our in the end with any? I have to read all the code in each block before I know the answer to that. Why not just tell me directly by wrapping them each in a method.
I’d agree with Marcel here. I think the answer to using multiple blocks is generally *not* to; a method extract is warranted.
If you’re chaining multiple blocks, you’re executing multiple steps on a piece of data (or a filtered result). Why not give those steps names so what’s occurring is comprehensible at a glance? Why not support documentation of each step?
Some might argue that chaining blocks together is necessary for processing _complex_ data structures, and it’s a mundane, common enough problem that method extraction seems too tedious or feels too contrived. My general feeling on that is simple: make better, more domain specific data structures in the first place (make a class already). Sometimes the problem is the problem.
@Marcel I’m typically pretty shy about introducing new classes. That said, I think you’ve got the right idea.
@Bruce “sometimes the problem is the problem” is going on a slide sometime in the near future ;)