Episode Script
Let’s say we run a trendy brewpub. We pride ourselves in our extensive stock of unique and obscure beer styles from a variety of boutique breweries around the world.
When a customer asks for a particular style of beer, we look through our extensive beer cellar and give them a bottle of beer in that style.
Of course, some styles are too obscure even for us, and in that case we come up nil.
require "./beer_cellar"
lookup_beer("Antarctic Pale Ale")
# => #<struct Beer
# style="Antarctic Pale Ale",
# name="Icebreaker Brewery Pingu Pale">
lookup_beer("Lager")
# => nil
It takes a long time for someone to go searching through the aisles of our beer vaults and then come back with a bottle.
Given this fact, and since we can’t always guarantee we’ll find a customer’s first choice of beer style, we prefer that they give us a list of styles.
We can then take the list and search through stacks one style at a time, until we find a match.
We’re using detect here, which will return the first item for which the block returns a truthy value.
For instance, a customer might hand us a list with “Skinny Stout” and “Blue IPA” listed. We take the list, and find that while we’re all out of skinny stouts, we do indeed have a blue IPA in stock.
require "./beer_cellar"
def find_beer(*style_list)
style_list.detect{|style| lookup_beer(style)}
end
find_beer("Skinny Stout", "Blue IPA")
# => "Blue IPA"
Except… whoopsie. We just returned the style of beer that we found. It would have been better if we had come back with an actual beer.
Let’s fix this. We assign the found style to a local variable.
Then, so long as a style was found, we look it up again to get the actual beer instance.
Now when we search for a list of styles, we get the actual beer back instead of just the name of a style.
require "./beer_cellar"
def find_beer(*style_list)
in_stock_style = style_list.detect{|style| lookup_beer(style)}
in_stock_style && lookup_beer(in_stock_style)
end
find_beer("Skinny Stout", "Blue IPA")
# => #<struct Beer style="Blue IPA", name="Indy's Inscrutapale">
This works, but there’s something seriously smelly about this code. We’re calling lookup_beer twice: once to determine if a beer can be found for the give style, and then a second time to retrieve the beer and return it.
Let’s see if we can rewrite this so it only calls lookup_beer once.
We’ll send #map to the list of beer styles.
Inside the block, we map from the style to the found beer for that style—if any.
The resulting list may contain nil values for any styles that couldn’t be found.
So we compact them out.
Then we take the first value from the compacted list.
Let’s try this out. We’ll pass in a list of 3 desired styles. We get a hit on the very first one.
However, even though we’ve eliminated the second lookup_beer call from our code, this method call actually had to do more beer lookups than the last one we tried. How can we tell? Well, it just so happens that we’ve instrumented the lookup_beer method with a call counter.
When we check the counter, we see that it has been incremented three times.
Why is this? Remember, map always executes its block for ever member of a collection.
So even though we only returned the first found beer, we actually looked up all three styles in our cellar.
require "./beer_cellar"
def find_beer(*style_list)
style_list
.map{|style| lookup_beer(style)}
.compact
.first
end
find_beer("Blue IPA", "Stinky Saison", "Norwegian Blue")
# => #<struct Beer style="Blue IPA", name="Indy's Inscrutapale">
$lookups # => 3
Our bar helpers are getting tired of running up and down the cellar steps. They demand we optimize this process.
One way we could fix this problem is by rewriting the code in a more imperative style.
We start by initializing a found variable to nil.
Then we iterate over the beer style list with #each.
For each style, we do our lookup and assign the result to the found variable.
Then we break if the found variable is non-nil.
Finally we return the value of the found variable.
Let’s try this version out. Given the same style list, we get the same beer returned.
But this time, the lookup counter reads 1 instead of 3.
require "./beer_cellar"
def find_beer(*style_list)
found = nil
style_list.each do |style|
found = lookup_beer(style)
break if found
end
found
end
find_beer("Blue IPA", "Stinky Saison", "Norwegian Blue")
# => #<struct Beer style="Blue IPA", name="Indy's Inscrutapale">
$lookups # => 1
So great, we’ve solved our efficiency problem. But this is clunky imperative code. We’re a hip, happening modern brewpub! Can’t we do this in a more slick, functional way??
We can, but the technique is a little non-obvious.
We’ll go back to sending #detect to the style_list.
This time, we assign the found beer—if any—to a found variable.
Now, if we actually found a matching beer, we want to exit from the detect iteration early, so we don’t do any needless lookups.
In order to do this, we follow this statement with and break. Here we’re using the and control-flow operator we learned about in episode #125.
But if we left this code as-is, we’d be back to returning the found style not the found beer. Because #detect returns the matching item from its receiver, not the result of the block.
Except… back in episode #71, we learned that when given a value, break overrides the return value of the block it is exiting from.
So we add the found variable as the argument to break.
Now, when this sub-expression is invoked, the return value of the whole #detect call will be forced to be the found beer.
Let’s call this method one more time. We give it a list of beer styles as usual.
This time, the code has to make 2 lookups before finding a “Belgian Whitebock” beer.
require "./beer_cellar"
def find_beer(*style_list)
style_list.detect do |style|
found = lookup_beer(style) and break found
end
end
find_beer("Decaf Porter", "Belgian Whitebock", "Hot & Sour")
# => #<struct Beer
# style="Belgian Whitebock",
# name="Babelfish Brewing Ole' Zebra">
$lookups # => 2
Now, this might all seem like an elaborate and contrived example to set up a fiddly little functional programming puzzle. But this is actually a scenario that I find crops up fairly frequently: I want to find the first item in a list for which a block returns non-nil, but rather than returning the item itself, I want to return the value of the block for the matching item.
This is the most elegant solution I’ve found for this particular scenario, and so I thought I’d share it with you. Happy hacking!


How about:
style_list.lazy.map { |style| lookup_beer(style) }.detect(&:itself)
OK, you got me, I admit I opted to omit mention of the lazy version because it just felt like it had a little too much of a “whaaaa…???” factor. But yeah – lazy map is the other functional approach I know of. In some languages, it’s probably the most idiomatic way to do it.
I haven’t seen this sort of return-from-detect thing before – still neat 🙂
And if you find that using “.compact.first” (like in the first functional example) easier to grasp than “.detect(&:itself)”, you can add the compact method to Enumerable::Lazy: https://gist.github.com/sunny/8b99ee8fe243a069e2f1a19e61c16ec2
I like the lazy map with detect as it is concise and efficient.
You can also consider what you are doing to be reducing a list to the first found item.
style_list.reduce(nil) { |beer, style| beer || lookup_beer(style) }
You do end up going over the full list but as soon as you find your beer you just keep returning it for every other value in the list. Not as good as the lazy + detect, but perhaps less surprising than breaking with a value.
I like this. In case other viewers are curious, I wasn’t clear on why the final result needs to use `Enumerable#detect` instead of `Enumerable#each`.
You’ll get the same outcome for a successful lookup if you do `#each`:
“`
def find_beer(*style_list)
style_list.each do |style|
found = lookup_beer(style) and break found
end
end
find_beer(“Decaf Porter”, “Belgian Whitebock”, “Hot & Sour”)
# => #
“`
but if you use `#each` your return value will be the list of styles instead of `nil` when you fail to find a beer matching one of the styles on your list.
Ah. It looks like part of my code example gets scrubbed out incorrectly by some comment formatter. Here is my comment as a gist: https://gist.github.com/backus/92e3a05458d4738370423ea7296080ad
Thanks — was going to ask that 🙂
Ah, yes, sorry I didn’t make that clear.
One little gotcha I encountered when I just used this is the “truthyness” of an empty array.
if “lookup_beer(style)” returns an array, the break has to be on a new line:
found = lookup_beer(style)
found.present? and break found