问题
I'm going through a phase of trying to avoid temporary variables and over-use of conditional where I can use a more fluid style of coding. I've taken a great liking to using #tap
in places where I want to get the value I need to return, but do something with it before I return it.
def fluid_method
something_complicated(a, b, c).tap do |obj|
obj.update(:x => y)
end
end
Vs. the procedural:
def non_fluid_method
obj = something_complicated(a, b, c)
obj.update(:x => y)
obj # <= I don't like this, if it's avoidable
end
Obviously the above examples are simple, but this is a pretty common coding style in the ruby community nonetheless. I'll sometimes use #inject
to pass an object through a series of filters too:
things.inject(whatever) do |obj, thing|
thing.filter(obj)
end
Vs. the procedural:
obj = whatever
things.each do |thing|
obj = thing.filter(obj)
end
obj
Now I'm facing repeated use of a condition like the following, and looking for a more fluid approach to handling it:
def not_nice_method
obj = something_complex(a, b, c)
if a_predicate_check?
obj.one_more_method_call
else
obj
end
end
The (slightly) cleaner solution is to avoid the temporary variable at the cost of duplication:
def not_nice_method
if a_predicate_check?
something_complex(a, b, c).one_more_method_call
else
something_complex(a, b, c)
end
end
I can't help but feeling the desire to use something almost like #tap
here though.
What other patterns might I follow here. I realise this is all just nonsensical sugar to some people and that I should just move onto more interesting problems, but I'm trying to learn to write in a more functional style, so I'm just curious what long-term rubyists have determined to be good ways to tackle situations like this. These examples are hugely simplified.
回答1:
Define Object#as
:
class Object
def as
yield self
end
end
And now you can write:
def not_sure_this_is_nice_enough_method1
something_complex(a, b, c).as do |obj|
a_predicate_check? ? obj.one_more_method_call : obj
end
end
回答2:
def best_nice_method
something_complex(a, b, c).tap |obj|
break obj.one_more_method_call if a_predicate_check?
end
end
The magic is break
in tap
returns another value.
new
ruby 2.5 has yield_self
which exactly you want.
https://stackoverflow.com/a/47890832/683157
回答3:
I found a method in the Facets gem that might be what you were looking for: Kernel#ergo
So your original method:
def not_nice_method
obj = something_complex(a, b, c)
if a_predicate_check?
obj.one_more_method_call
else
obj
end
end
might end up looking something like this:
require 'facets/kernel/ergo'
def nice_method
something_complex(a, b, c).ergo do |_|
a_predicate_check? ? _.one_more_method_call : _
end
end
回答4:
I needed to do something like this and I like tokland's answer, but I didn't want to pollute Object for the small script I was writing. Instead, I made use of tap
on an array:
[something_complicated].tap { |s| s[0] = new_cool_thing)}.first
回答5:
instance_eval
can be misused for this purpose
"this".instance_eval { |test| test + " works" }
since 2.5 it is possible to use yield_self
"easy".yield_self{ |a| a + " peasy" }
Read more:
https://ruby-doc.org/core-1.9.3/BasicObject.html#method-i-instance_eval
https://ruby-doc.org/core-2.5.0/Object.html#method-i-yield_self
来源:https://stackoverflow.com/questions/7878687/combinatory-method-like-tap-but-able-to-return-a-different-value