Feature #17471
opensend_if method for improved conditional chaining
Description
Background¶
Method chaining is very important to many Ruby users, since everything in Ruby is an object.
It also allows easier functional programming, because it implements a pipeline where each step can happen without mutation.
Conditional chaining allows an even more declarative style of programming. Right now, it is possible to conditionally chain methods to a degree but in some cases it is a bit verbose.
Proposal¶
I propose that a send_if
method is added, which works roughly like this:
# Internal condition
puts 'If you give me a number larger than 5, I will double it. I will subtract 1 in any case.'
number = gets.chomp.to_i
# An implementation without send_if
puts (number > 5 ? number.send(:*, 2) : number).send(:-, 1)
# Implementation with send_if [1]
puts number.send_if(:*, 2) {|obj| obj > 5}.send(:-, 1)
# External condition
puts 'Do you want a loud Merry Christmas? (y or I take it as a no)'
answer = gets.chomp
# An implementation without send_if
puts %w(Merry Christmas).send(:map, &->(e) {answer == 'y' ? e.upcase : e}).join(' ')
# Implementation with send_if [2]
puts %w(Merry Christmas).send_if(:map, proc: :upcase ) { answer == 'y' }.join(' ')
Implementation¶
Here is a Ruby implementation (obviously, everything is released under the same license terms as Ruby itself):
class Object
def send_if(method, *args, proc: nil)
yield(self) ? self.send(method, *args, &proc) : self
end
end
This implementation works as intended with both examples I posted above.
Evaluation¶
I don't believe send_if
brings significant performance penalties, compared to the alternatives.
I am not 100% satisfied with my implementation in terms of usability, for two reasons:
- I did not find any stdlib methods which are consistent with the function signature I've specified. More specifically, I don't like the named
proc:
parameter I used, but I couldn't think of a better alternative. Please, tasukete! - Ruby does not support multiple blocks, which would be required for my ideal implementation (short of [3], see later):
puts %w(Merry Christmas).send_if(:map, &:upcase) { answer == 'y' }.join(' ')
Discussion¶
I know for sure there are more skilled Rubyists than myself here who can come up with nicer alternatives to my send_if
examples, but I think send_if
would be nice to have because:
- The
*_if
family of methods is a staple of the stdlib (e.g.receive_if
,delete_if
,keep_if
, etc.) - In some cases, it decreases the amount of code needed
I know my examples could be written without ever using send
but send
makes it possible to use any Ruby method (rather than write specific methods like map_if
, etc.).
In the future, some syntactic sugar could be built so that method chaining is even more fluid, without any need for send
. An example using an .?{}
operator I just made up:
# Syntax-level conditional chaining [3]
puts %w(Merry Christmas).?{answer == 'y'}map(&:upcase).join(' ')
Of course, {answer == 'y'}
would be a block and this would be equivalent to my example above [2], but without any need for a send
method (since this operator would apply to all methods).
If someone is interested, I can make a separate proposal for this operator, but perhaps it's asking too much :)
I'd be happy to discover more elegant solutions and critiques!
Merry Christmas to everybody and thanks for reading!