Today, I was working with some ruby code that had to find the first product in one of the current contexts. Here is the code:
1 | def find_product_in_current_contexts |
This code tries to find the first product in the current contexts in the order they are defined. However, the above code has a tiny bug. Can you figure out what it is?
In cases where there are no products in any of the contexts this function
returns the array [1, 2, 3]
instead of returning nil
because Array.each
returns the array and in the case where we don’t find the product we don’t
return early.
We can easily fix this by adding an extra return at the end of the function.
1 | def find_product_in_current_contexts |
The fix is awkward, let us see if we can improve this.
We could use .map
to find a product for every context and return the first not
nil
record like so:
1 | def find_product_in_current_contexts |
This looks much cleaner! And it doesn’t have the previous bug either. However, this code is not efficient, we want to return the first product we find for all the contexts, and the above code always looks in all contexts even if it finds a product for the first context. We need to be lazy!
Lazy enumerator for the win!
Calling .lazy
on an enumerable gives you a lazy enumerator and the neat thing
about that is it only executes the chain of functions as many times as needed.
Here is a short example which demonstrates its use:
1 | def find(id) |
As you can see from the above example, the lazy enumerator executes only as many times as necessary. Here is another example from the ruby docs, to drive the point home:
1 | irb> (1..Float::INFINITY).lazy.select(&:odd?).drop(10).take(2).to_a |
Now applying this to our code is pretty straightforward, we just need to add a
call to #.lazy
before we map and we are all set!
1 | def find_product_in_current_contexts |
Ah, nice functional ruby!