Default Assignment Ruby (|| =) vs Reset Error

Since a ||= 1 equivalent to a || a = 1 a || a = 1 , we can say that there is syntactic sugar for this:

 if a.nil? a = 1 end 

Similarly, given that session is a hash-like object, the following:

 def increment_session_counter session[:counter] ||= 0 session[:counter] += 1 end 

is equivalent to:

 def increment_session_counter if session[:counter].nil? session[:counter] = 0 end session[:counter] += 1 end 

Does this mean that an implicit if will be executed every time in the original definition of increment_session_counter ? Since session[:counter] will most likely be nil only for the first time (ie <1% of the time), I get the feeling that the following code is better, since the implicit if will not be run every time:

 def increment_session_counter session[:counter] += 1 rescue NoMethodError session[:counter] = 1 end 

Is this code better in that sense?

Having said that, I have no idea how Rescue implemented in ruby ​​and whether this is really relevant with respect to the small optimization that can bring, if it is.

+5
source share
3 answers

session[:counter] += 1 performs three functions:

  • select value ( Hash#[] )
  • increase value ( Integer#+ )
  • save incremental value ( Hash#[]= )

It is quite convenient, but its brevity also makes it inflexible.

Providing a default value is much easier if you separate the following steps:

 def increment_session_counter session[:counter] = session.fetch(:counter, 0) + 1 end 
+3
source

Capturing a bug is a pretty smart idea, but it's also harder to read than using ||= . But it would be even easier to set the initial value when creating the hash in the first place:

 @session = {:counter => 0} def increment_session_counter @session[:counter] += 1 end 

This does not work when the key is not known in advance:

 def increment_user_counter(username) @session[username] ||= 0 @session[username] += 1 end 

However, in this case, your assumption that the counter value is only zero is thrown into jeopardy. In fact, since many distributions follow the power law , probably 1 will be the most common counter.

Therefore, if you know the possible values ​​in advance, it is best to set them to zero when the program or class is initialized, and skip the need to check the default value. And if you do not know all the possible values ​​in advance, you will most likely find the instruction ||= as often as not. There are probably several scenarios in which using an exception is the best solution, assuming nil checking is significantly cheaper .

+3
source

I tried some tests with the following code. This application is for rails. Rails v.5.1.1 / Ruby v2.4.1

Initially, the goal is simply to count the number of visits in any show actions of some controllers, for example:

 include SessionCount ... def show ... @counter = increment_session_counter ... 

Then I could display the counter in the respective views.

So I took care of the appropriate code from both versions of the default destination, which I wanted to check:

 module SessionCount private #Counter version A def increment_session_counter_A session[:counter] ||= 0 session[:counter] += 1 end #Counter version B def increment_session_counter_B session[:counter] += 1 rescue session[:counter] = 1 end end 

To test both versions of the default destination, I changed the code of my controllers as follows:

 include SessionCount ... def show ... t0 = Time.now 1000000.times do session[:counter] = 0; #initialization for normalization purpose increment_session_counter_A end t1 = Time.now puts "Elapsed time: #{((end_time - beginning_time)*1000).round} ms" @counter = increment_session_counter_A ... 

Note. In this code, initialization is here to provide a “happy journey” (where the value is not equal to zero). In a real scenario, this will happen only for the first time for a given user.

Here are the results:
I get an average of 3100 ms with version A operator ( ||= ).
I get an average of 2000 ms with version B ( rescue ).

But the interesting part begins now.

In the previous code, the code is executed after a “happy journey”, where an exception does not occur.
Therefore, I changed the initialization that I did for normalization purposes as follows to provide an “exception path”:

 1000000.times do session[:counter] = nil; #initialization for normalization purpose increment_session_counter_A end 

And here are the results:
I get an average of 3500 ms with the operator version A ( ||= ).
I get an average of about 60,000 ms with version B ( rescue ). Yes, I tried a couple of times only.

So, I can conclude that spickermann said exception handling is really quite expensive.

But I think that there are many cases when the first initialization is very rare (for example, on a blog where there is no message at the very beginning).
In such situations, there is no reason to test nil every time, and it would be interesting to use exception handling.

I'm wrong?

I don’t want a bit about some ms .. Here I just want to know if this idiom has a design meaning. I see the difference in both versions, as the difference between while... end and do ... while , as the intention does not match.

+1
source

Source: https://habr.com/ru/post/1268763/


All Articles