Passing functions in Ruby: harder than it looks

People say, “Oh, Ruby has functional programming! We pass blocks around all the time!”

I’m sorry to inform you: Ruby blocks are not first-class functions.

Functional programming is so called because we find it useful to pass functions as values. When we do that, we expect that the function can be called and:

  • it’ll get its own level on the stack
  • when it returns, that level of the stack will go away
  • this stack is the same one used by the caller.

When a function returns, whoever calls it gets the return value and goes on its merry way. Sounds easy, right?

Yeah, not in Ruby. See, the common case in Ruby is to pass code in as a block, something like this:

format_all_the_things { |a| “format #{a} into a string” }

Then inside the higher-order function, the block of code is called using yield:

def format_all_the_things
   one = yield “thing 1”
   two = yield “thing 2”
   [one, two].join ” & “
end

Here, the block of code is an invisible parameter. It’s a little like a first-class function, but not really. For one, blocks don’t get their own level on the stack. For another, they don’t execute in the same stack as the caller. Best I can tell, this “yield” keyword fires up a coroutine, which gets its own stack. Two violations of my expectations of a first-class function

Danger: return

Use the return keyword in a block, and you invite all kinds of trouble. See, return means it’s time to knock a level off the stack. Since the block didn’t get its own level in the stack, it has to return from the caller. But in this case, since it was started in a coroutine, there’s nothing there to go back to — LocalJumpError.

 > format_all_the_things { |a| return “yay #{a}” }
LocalJumpError: unexpected return

So, return is your enemy in blocks. Also in Procs.

this is even stranger
wrap the whole thing in a lambda and the block doesn’t local jump error; it happily returns from the lambda when it hits the first return keyword inside a block. There’s some magic going on here. This works for blocks, but not Procs. Can anyone explain this to me?

> lam = -> { format_all_the_things { |a| return “yay #{a}” } }
> lam.call
 => “yay thing 1”

Ruby has a third option: lambdas. These behave much more like the functions that I know and love.

lam = lambda { |a| return “yay #{a}” }

Lambdas do (seem to) get their own level on the stack, and when they return they return from themselves, not their caller.
Unfortunately this still doesn’t work with yield.

> format_all_the_things &lam
LocalJumpError: unexpected return

Dangit! I thought I could trust lambdas, but I was wrong.
Turns out even lambdas behave sanely with return only if accessed using .call() instead of yield. This is the friendly way:

def format_all_the_things(&formatter)
   one = formatter.call(“thing 1”)
   two = formatter.call(“thing 2”)
   [one, two].join ” & “
 end

format_all_the_things &lam
 => “yay thing 1 & yay thing 2” 

This is the right way to pass and use functions as values in Ruby: lambdas and .call().

Next

“Just don’t use return!” you may say. Ruby devs are wonderfully disciplined at avoiding the language’s less respectable features.

Sometimes the code is clearer if you can exit the function as soon as you know what the return value is. Especially with one of those handy post-statement ifs:

return “REDACTED” if (a.contains_sensitive_information)

Oddly, there is a control statement that appears to do exactly what I want return to do, and that’s next. No longer a loop-control statement, it appears in Ruby 2.0 to be everything we could wish return to be.

> format_all_the_things { |a| next “REDACTED” if (a.include?(“1”)); “yay #{a}” }
 => “REDACTED & yay thing 2” 

More danger: Break

There are some circumstances where you can use break within a block to end Ruby’s internal iteration early.

[4,5,-1,7].each { |n| break :invalid if n < 0 }

This works only with blocks, not with Procs (LocalJumpError) or lambdas (ignored). I think it’s evil. Code that exercises flow control on its caller is not a function value.

Conclusion

If you’re going to use a functional programming style in Ruby, use lambdas and invoke them with .call().

——
This is all in Ruby 2.0.

Here are my experiments in blocks, Procs, and lambdas with return, break, and next.

Come, Come, Commala: My husband’s excellent facebook status right now: Just got a message…

thepaisleyelf:

My husband’s excellent facebook status right now:

Just got a message saying that changing my profile picture to show support for the LGBT community’s Supreme Court battle today won’t make any difference, and asking sarcastically if I wanted a medal for my “valiant effort.” Well, I’m sorry, but…

Come, Come, Commala: My husband’s excellent facebook status right now: Just got a message…

Declarative management

//platform.twitter.com/widgets.js

The same can be said of declarative programming. If we tell the implementation what to do, not how to do it, then the implementation is free to improvise and innovate. This might happen in a library, in a compiler, or at runtime. Focus on properties the interface advertises, and we aren’t limited to implementations we can understand.

Unstructured teams

In her classic article The Tyranny of Structurelessness, about the women’s liberation movement and every group people ever, Joreen describes the magic self-organized, effective team:

“working in this kind of group is a very heady experience; it is also rare and very hard to replicate.”

Why yes, yes it is. When companies manage to build a cohesive team, they should value it. Because companies aren’t made of business plans; they’re made of people. Great products come from teams like this:

1) "It is task oriented. …The task determines what needs to be done and when it needs to be done.“ (on a wall with post-its!)

2) It has a common language for interaction. The easy way to achieve this is homogeneity. If you also want diversity, then we need "everyone [to know] everyone else well enough to understand the nuances.”

3) There is a high degree of communication. Information flows between all members of the group. “This is only possible if the group is small and people practically live together for the most crucial phases of the task.”

4) “There is a low degree of skill specialization. Not everyone has to be able to do everything, but everything must be able to be done by more than one person.”

Sounds a lot like an agile team! These unstructured, close-knit and collectively-motivated teams are just what we’re aiming for. Joreen documented these back in 1970, and now software gives us the field to attempt to replicate this.

Welcome All the People

On our team, in the user group, and at conferences, we try to build community. In each conversation, we do this by emphasizing ways we are alike.

  •  Who doesn’t like beer?
  •  Monty Python is funny.
  •  Everyone has done Object Oriented programming.
  •  We all went to college.
  •  Each of us can grow a beard.
  •  Video games are awesome.
  •  Everyone can see and hear.
  •  We all grew up watching Saturday morning cartoons.

Each of these shared references draws together most of the developer audience and promotes a feeling of belonging. And each of them further isolates people who don’t fit these assumptions.

I’ve been guilty of this. Writing presentations or posts, I look for domains familiar to my audience, and choose role playing. This emphasizes the programmer == geek stereotype, and the people who don’t know what it means to “level up” feel stupid and out of place.

When a reference appeals to the majority then people who don’t get it feel isolated. No one asks, “What does that mean?” when 80% of the audience reacts with knowing laughter.

Let’s build community on what every one of us shares.

  • We want to learn from each other. 
  • Each of us wants to help build better software.

If you don’t want to learn or build good software then don’t read this blog. From now on, I aim to make only these assumptions about the people on my team, in my user groups, and at each conference.

Dick jokes? Funny, yes. Offensive, no. It isn’t about offensive/not offensive anymore. It’s about welcoming/isolating. When a particular joke applies to 80% of the audience differently than to the other 20%, that joke is isolating. That builds homogeneity. I want everyone to feel welcome, I want diverse ideas and contributions. I’m aiming for inclusion.

These are my action items:

  • Consider my target audience as the attendee mix I hope to see someday. Men and women and transgendered, Asian and African and European, 20s and 40s and 60s. People who like sports, and cooking, and quilting.
  • Give context to my references. The next time I mention the Prime Directive of Programming, I’ll talk about how Captain Picard in Star Trek: The Next Generation follows the Prime Directive of “do no harm.”
  • Have hallway and table conversations about a diverse range of topics, that any random person might be able to jump in on. I’ll keep the stuff that freaks some people out for parties and private.

Let’s make each other think. Development communities should be about learning, not warm fuzzies of all being alike, when “all” means “people who resemble me.” I want to be all in, and let all in.

Baggage makes your life easier, and also crappier

Hilary Mason writes about what she’s learned about travel: take half as much stuff as you think you need, and twice the money. In other words: prepare less and be resourceful more. This leads to flexibility and great surprises.

Someone on Svbtle wrote about design in the same way. When you approach the layout of a page with a standard template in your hand, you’re bringing what you think you need to make the page quickly. This limits you! It limits the page. This particular part of the site may not need a login link at the top, or a search bar. Perhaps something else would be better in that spot, or nothing.

A great web site is more than functional. It’s an experience. Optimizing user experience, like taking a trip, is best done with less baggage.

Let go of decisions you’ve already made and look afreash at today’s feature, today’s adventure.

I wish Svbtle had a search feature that I could find 😦 I’d link that post if I could.