Feature #11717
closedObject#trap -- pass object to block and return result
Description
Object#trap
can be thought as useful counterpart for Object#tap
: like tap, it passes object to the block; unlike tap, it returns results of the block, not object itself.
Rationale
#trap
could allow to gracefully chain processing of objects, which isn't Enumerable
, therefore enforcing "functional" style in Ruby (which considered good).
Use case
# Assume we grab some resource from web:
SomeWebClient.get('http://some/url', param1: 'value1', param2: 'value2').body
# And now, the body of response is JSON, and we want it parsed. How to express it?
# Option 1: wrap:
JSON.parse(SomeWebClient.get('http://some/url', param1: 'value1', param2: 'value2').body)
# Downside: messy parenthesis, and need to jump back and forth to understand the meaning
# Option 2: intermediate variable:
s = SomeWebClient.get('http://some/url', param1: 'value1', param2: 'value2').body
JSON.parse(s)
# Downside: intermediate variable is not elegant
# Option 3: monkey-patch (or refine)
SomeWebClient.get('http://some/url', param1: 'value1', param2: 'value2').body.from_json
# Downside: monkey-patching is a last resort; also, your classes should be already patched when you stuck with this case
# Option 4 (proposed): trap
SomeWebClient.get('http://some/url', param1: 'value1', param2: 'value2').body.
trap{|s| JSON.parse(s)} # => parsed JSON
And when you are thinking with code, experimenting with code (especially in irb, but in editor too), only last option is "natural" river of thoughts: do this, then do that (extract data from web, then parse it).
Naming
- it is similar enough to
tap
; - it is specific enough to not be used widely in some popular library (or isn't it?);
- mnemonic is "do something and trap (catch) the value".
WDYT?
Updated by 0x0dea (D.E. Akers) almost 9 years ago
You're looking for #instance_eval
:
'foo'.instance_eval { |obj| obj.size } # => 3
Updated by zverok (Victor Shepelev) almost 9 years ago
Nope.
I'm aware of #instance_eval
last 10 years or so (I even can recall times when #instance_exec
were external library method, not part of the core).
Primary goal/usage of #instance_eval
is to "dig inside". Primary goal/usage of #trap
is to "apply next transormation". Even if #trap
will be implemented as a simple alias of #instance_eval
, it will have some usage/popularity outside of #instance_eval
's domain. On my opinion only, of course.
Updated by phluid61 (Matthew Kerwin) almost 9 years ago
Updated by phluid61 (Matthew Kerwin) almost 9 years ago
Victor Shepelev wrote:
Naming
- it is similar enough to
tap
;- it is specific enough to not be used widely in some popular library (or isn't it?);
- mnemonic is "do something and trap (catch) the value".
WDYT?
"trap" already means "trap a signal", it comes from long-standing Unix terminology; see Signal#trap
Updated by 0x0dea (D.E. Akers) almost 9 years ago
Victor Shepelev wrote:
Even if
#trap
will be implemented as a simple alias of#instance_eval
...
If you did in fact know that you were essentially requesting an alias for #instance_eval
, this was a remarkably roundabout way to go about it.
Updated by zverok (Victor Shepelev) almost 9 years ago
"trap" already means "trap a signal", it comes from long-standing Unix terminology
Ooops. Completely forgot about this one :(
Clearly this is something the Ruby community wants. Just as clearly, it's something nobody can name.
Yeah, can see it now.
Thinking further, I wonder if just Object#yield
could be parsed correctly...
If you did in fact know that you were essentially requesting an alias for #instance_eval, this was a remarkably roundabout way to go about it.
But hey. It is NOT, in fact:
class MyClass
def with_instance_eval(filename)
File.read(filename).instance_eval{|s| p [s, self]; parse(s)}
end
def with_trap(filename)
File.read(filename).trap{|s| p [s, self]; parse(s)}
end
def parse(str)
JSON.parse(str)
end
end
puts "#trap:"
p MyClass.new.with_trap('test.json')
puts "#instance_eval:"
p MyClass.new.with_instance_eval('test.json')
Output:
#trap:
["{\"test\": 1}\n", #<MyClass:0x911f428>]
{"test"=>1}
#instance_eval:
["{\"test\": 1}\n", "{\"test\": 1}\n"]
trap.rb:in `block in with_instance_eval': undefined method `parse' for "{\"test\": 1}\n":String (NoMethodError)
Updated by nobu (Nobuyoshi Nakada) almost 9 years ago
- Is duplicate of Feature #6721: Object#yield_self added
Updated by nobu (Nobuyoshi Nakada) about 8 years ago
- Has duplicate Feature #12760: Optional block argument for `itself` added
Updated by nobu (Nobuyoshi Nakada) almost 8 years ago
- Has duplicate Feature #13172: Method that yields object to block and returns result added
Updated by nobu (Nobuyoshi Nakada) over 7 years ago
- Status changed from Open to Closed
Applied in changeset trunk|r58528.
object.c: Kernel#yield_self
- object.c (rb_obj_yield_self): new method which yields the
receiver and returns the result.
[ruby-core:46320] [Feature #6721]