Part of my journey, over the last year or so, to understand functional programming have led me to paradigm shifts and discoveries. One such discovery was: any? and all?.
Before going into the reasons of why these functions must be part of your toolkit, let me show you example code.
list = ['cat', 'dog', 'snake', 'crocodile', 'spider']
# any fit those size requirements?
list.any? {|x| x.size > 6} # true
list.any? {|x| x.size < 4} # true
list.any? {|x| x.size > 40} # false
# all fit those size requirements?
list.all? {|x| x.size > 6} # false
list.all? {|x| x.size < 80} # true
# any is 'snake'
list.include? 'snake' # true
list.any? {|x| x == 'snake'} # true
# any starts with 's'?
list.any? {|x| x =~ /^s/} # true
# all starts with 's'?
list.all? {|x| x =~ /^s/} # false
The high-level description of any and all is that they are functions, part of Enumerable, that take a block. This block is a test that any element must satisfy or that all elements must satisfy.
What makes any? and all? interesting is that they are semantic. Suddenly, your code doesn’t just iterate over a list, it states its intent: I’m looking to see if any/all items fit that condition.
In fact, to me it makes such a difference in meaning, that I started to convince people I work with to use the words ‘any’ and ‘all’ when giving me specifications: “Do you mean to say that I should transfer to operator when any order has been cancelled or when all orders have been cancelled?” Not only does it forces people to be more rigorous when it comes to specifications, my code starts to look very eerily like the specification itself
transfer if orders.any? {|x| x.status == 'CANCELLED'}
Let me revisit this, any? and all?:
- make your code shorter
- more readable
- semantic
- closer to natural language and specifications
Let me push this further: in any language that supports higher-level abstractions and blocks/closures, outright iteration should be taken as a sign that you are doing something wrong.
Of course, sometimes, iteration is what you need. I won’t talk about those cases. What I’d like to communicate is that, more often than not, iteration is used in places where much better tools could be used.
In Ruby, some of the tools that could be used instead of each are:
- any?
- all?
- collect (aka map)
- detect (aka find)
- grep
- select (aka find_all)
- include? (aka member?)
- inject
- min/max
- partition
- reject
Referring to Enumerable might be a powerful productivity boost.
Armed with this knowledge, I’d like you to stop yourself next time you iterate over a structure and ask: “What am I exactly trying to do here?”
Prototype (javascript) API documentation is rightfully explicit about avoiding each.
“When using Enumerable, beginners often create sub-par code, performance-wise, by simple lack of familiarity with the API.”
http://www.prototypejs.org/api/enumerable
I often store instantiated objects in an array and use invoke on it. I remember in my pre-functional programming days I would do a for loop but it would be longer to read when I came back to it months later.
[...] I used for the last 2 interviews was: any or all. Let’s just say that the results were not… productive. The problem with ‘any or [...]
[...] interesting. (tags: rails) [...]