Ruby - Is it possible to alias a method to the safe navigation operator

Ruby 2.3 introduced the safe navigation operator, however I find that its syntax is too discrete an can be easy to miss when briefly scanning code. Instead I much prefer the syntax of try as it looks much more obvious and intentional.

So my question is in Ruby 2.3+, Is there a way to alias or monkey patch a method to the safe navigation operator &. to a custom method name ie. s.fast_try(:upcase!).fast_try(:downcase) instead of writing s&.upcase!&.downcase

The main idea is to attempt to improve performance over another implementation such as try method. No, I dont care about the minor behaviour differences between try and safe navigation operator. Also, I do not mind some obscure argument limitations if they cannot be avoided, just point them out.

ruby-on-railsruby

Answers

answered 4 weeks ago engineersmnky #1

I guess you could go with something as simple as

class Object
  def fast_try(meth,*args,&block)
    self&.public_send(meth,*args,&block)
  end
end

For Example:

["string","STRING","pOw"].map do |s| 
  s.fast_try(:upcase!)
     .fast_try(:chars)
     .fast_try(:find, ->{"No S"}) { |a| a == "S" }
     .fast_try(:prepend, "!")
end
#=> ["!S",nil,"!No S"]

While your question states, " No, I don't care about the minor behavior differences between try and safe navigation operator.", given the fact that you have written a gem and noted the following

Differences between FastTry and ActiveSupport#try

It is not our goal to maintain any consistency with the ActiveSupport version of the try method. I do however want to maintain a simple list of the differences. Please create a PR or Issue if you find a difference that should be documented here.

Nothing reported yet

I feel it prudent to mention that there are discernible, and potentially poignant, differences between the 2 here is a Repl Spec to show the differences and for the sake of this answer and the fact that the link might die the output of this spec is as follows:

ActiveSupport#try vs. Safe Navigation (&.)
  #try
    handles would be NoMethodError with nil (using #respond_to?)
    does not work on a BasicObject
    behaves like a method call
      with no method name given
        when block_given?
          yields self to a block with arity > 0
          evaluates block with arity == 0 in the context of self
        when no block_given?
          raises an ArgumentError
      with a method_name given
        a non nil object
          uses public_send for message transmission
        nil
          calls NilClass#try and returns nil
  #try!
    does not handle NoMethodError
    does not work on a BasicObject
    behaves like a method call
      with no method name given
        when block_given?
          yields self to a block with arity > 0
          evaluates block with arity == 0 in the context of self
        when no block_given?
          raises an ArgumentError
      with a method_name given
        a non nil object
          uses public_send for message transmission
        nil
          calls NilClass#try and returns nil
  &. (safe navigation)
    does not handle NoMethodError
    raises a SyntaxError with no method name given when block_given?
    raises a SyntaxError with no method name given when no block_given?
    works on a BasicObject
    does not act like a method call
      with a method_name given
        a non nil object
          &. is not called
        nil
          returns nil without a method call

answered 4 weeks ago Weston Ganger #2

I have created a gem to solve this problem based on info from this StackOverflow answer.

fast_try is a simple method wrapper to the safe navigation operator.

Features / Goals:

  • Utilize the safe navigation operator (&.) while improving readability
  • Improve performance over other implementations such as the ActiveSupport#try method
  • Do not worry about most obscure arguments / syntax limitations if they cannot be avoided. Simplicity and speed is key.
  • The methods provided should behave very similarly to the safe navigation operator. If you need to retain the exact behaviour of ActiveSupport try, simply set your own custom method name(s) for FastTry.
  • The only dependency is Ruby 2.3+. It does NOT require Rails /ActiveSupport anything else however it works great with it too!

In an Initializer:

FastTry.method_names = [:try, :custom_try, :try!]
require 'fast_try/apply'

Usage:

str.try(:upcase).try(:downcase)

# the above statement is now equivalent to using the safe navigation operator Ex.
str&.upcase&.downcase

comments powered by Disqus