15

I want to avoid reevaluation of a value in method call. Untill now, I was doing this:

def some_method
  @some_method ||= begin
    # lot's of code
  end
end

But it ends up quite ugly. In some code, I saw something like the following:

def some_method
  @some_method ||= some_method!
end

private

def some_method!
  # lot's of code
end

I don't like the bang (!) at the end, so I came up with this:

def some_method
  @some_method ||= _some_method
end

private

def _some_method
  # lot's of code
end
  • Is prepending with an underscore a good convention?
  • Is there some other convention for memoized/non-memoized pairs of methods?
  • Is there some convention to memoize multi-line methods?
Mark Thomas
  • 37,131
  • 11
  • 74
  • 101
Kostas
  • 8,356
  • 11
  • 47
  • 63
  • 2
    Related: http://stackoverflow.com/questions/696338/ Using Rails' class `ActiveSupport::Memoize` seems to be nice: http://www.railway.at/articles/2008/09/20/a-guide-to-memoization/. – sawa Oct 10 '12 at 12:38
  • What kind of answer are you expecting? Vise and Agis have both suggested good conventions. Also, your second block of code is a common convention. – Josh Voigts Oct 12 '12 at 18:08
  • 1
    I like the `||= begin...end`. I find it quite elegant. – Linuxios Oct 13 '12 at 00:30
  • It's up to you.. __some_method, compute_some_method, calculate_some_method, some_method_impl, __some_method_code – w00d Oct 18 '12 at 22:29
  • Be aware: There [is a convention](http://stackoverflow.com/questions/612189/why-are-exclamation-marks-used-in-ruby-methods) around when and why to use exclamation marks. I think this falls a little outside the convention's purpose, but it's worth thinking about. Again slightly off what's relevant here, but [coding by convention](http://en.wikipedia.org/wiki/Convention_over_configuration) has value, so while it's fine to say "I'm not modifying the object", saying "I don't like the bang" is a less-than-stellar justification for avoiding it. – lindes Dec 30 '13 at 04:21

6 Answers6

27

I would do it like this:

def filesize
  @filesize ||= calculate_filesize
end

private

def calculate_filesize
  # ...
end

So I'd just name the method differently, as I think it makes more sense.

Agis
  • 32,639
  • 3
  • 73
  • 81
  • I don't like putting so many characters in front of a method's name in order to flag it as an unmemoized version. – Kostas Oct 10 '12 at 13:59
  • 9
    Why? It's semantically meaningful and extremely obvious what it's doing. The mindless quest for fewer characters has you ignoring the best answer. – user229044 Oct 17 '12 at 20:13
  • 1
    +1. These names describe exactly what the functions are doing, including their expected performance profile. – Jürgen Strobel Oct 19 '12 at 08:20
  • Even though the bounty went elsewhere, I accept this one since it is more in line with other ruby conventions. – Kostas Nov 14 '12 at 11:28
8

There is one more way, more Java-style I think.

First of all you should implement annotations, like "Java-style annotations in Ruby" and "How to simulate Java-like annotations in Ruby?".

Then you should add annotation like _cacheable that will said to method that it should return instance variable and if it is null it should calculate it by invoking method, so your code will be more clear:

_cacheable
def some_method
   # do_some_work
end
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
4

I use the memoist gem, which lets you easily memoize a method without having to alter your original method or create two methods.

So for example, instead of having two methods, file_size and calculate_file_size, and having to implement the memoization yourself with an instance variable:

def file_size
  @file_size ||= calculate_file_size
end

def calculate_file_size
  # code to calculate the file size
end

you can just do this:

def file_size
  # code to calculate the file size
end
memoize :file_size

Each memoized function comes with a way to flush the existing value.

object.file_size       # returns the memoized value
object.file_size(true) # bypasses the memoized value and rememoizes it

So calling object.file_size(true) would be the equivalent of calling object.calculate_file_size...

polarblau
  • 17,649
  • 7
  • 63
  • 84
Tyler Rick
  • 9,191
  • 6
  • 60
  • 60
2

I usually use begin, end as per your first example, but if there's a bit more code, I just look if the variable exists, no need to create another method just for that.

def some_method
  return @some_method if @some_method
  # lot's of code
  @some_method
end
vise
  • 12,713
  • 11
  • 52
  • 64
  • That won't work. You need to put `return` in front of the first line within the def. – sawa Oct 10 '12 at 12:36
  • The only thing I don't like with this approach is that the coder needs to edit the method in order to assign the `@some_method` variable somewhere in it, ergo mixing the memoization logic within the method's logic. – Kostas Oct 10 '12 at 13:57
  • I think you're being overly harsh, as it depends on what the method does in the first place. For instance I sometimes use this in rails helpers and it makes perfect sense. Not to mention that the private keyword inside modules doesn't work all that well to begin with. And as a side note, because you downvoted Agis as well, it's not nice to downvote all the answers you receive just because you don't entirely agree with them. – vise Oct 10 '12 at 14:21
1

I don't like the bang either. I use

def some_method 
  @some_method_memo ||= some_method_eval 
end 

private 

def some_method_eval
  # lot's of code 
end 

Here eval is shorthand for evaluation. I like the way this reads and also that it makes the public interface concise.

I despise conventions that rely on underscores as distinguishing marks: they are both error prone and require that I remember YAMC (yet another meaningless convention). The Ada language, designed for safety-critical applications, does not allow leading, trailing, or multiple underscores. Nice idea.

Gene
  • 46,253
  • 4
  • 58
  • 96
0

I normally do it like in Agis answer or:

def filesize() @filesize ||=
  calculate_filesize
end

BTW:

I often use this memoization technique:

def filesize() @_memo[:filesize] ||=
  calculate_filesize
end

Which will allow you to later clear all memoized variables with one simple @_memo.clear. The @_memo variable should be initialized like this Hash.new { |h, k| h[k] = Hash.new }. It gives you many of the adventages of using ActiveSupport::Memoize and similar meta programmed techniques which might be much slower.

Community
  • 1
  • 1
Szymon Jeż
  • 8,273
  • 4
  • 42
  • 60