Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Brandon Weaver
Brandon Weaver

Posted on • Edited on

     

Understanding Ruby - Memoization

Introduction

Memoization is a common technique in Ruby, but alas it's one with a few potential gotchas we need to be aware of to use it effectively. This post gives a short introduction to things that you probably want to watch out for when memoizing, or rather remembering, values that methods compute.

Difficulty

Foundational

Little to no prerequisite knowledge required. This post focuses on foundational and fundamental knowledge for Ruby programmers.

Memoization

First off, what do we mean when we say memoization? Shortly put it's a way to "remember" a value a method might produce, especially whenever that method happens to be expensive. If it happens to take a few seconds to run a method it'd probably be a good idea to store what the result was just to make sure we don't have to run the entire thing over again.

This, in essence, is memoization. We memorize, memoize, remember, or otherwise keep a record of what happened last time we did something.

That all said rarely is something quite so straightforward, and this post does elide a few concerns in favor of being a more introductory text. Shall we get into it then?

Instance Variables and||=

The first method, and the one you're most likely to see, is that an instance method of a class happens to use||= and an instance variable to remember a value:

classMyClassdefsome_method@some_method||=some_expensive_computation_or_api_callendend
Enter fullscreen modeExit fullscreen mode

If we happen to say this:

our_thing=MyClass.newout_thing.some_methodout_thing.some_method
Enter fullscreen modeExit fullscreen mode

...the second run will not run the code on the right side, thesome_expensive_computation_or_api_call, which might be something more along the lines of say:

@some_method||=Net::HTTP.get(URI("url_goes_here")).then{|http_response|JSON.parse(http_response)}
Enter fullscreen modeExit fullscreen mode

||= and What it Does

Why does that work though? Well let's take a quick detour to look at||= and what it does. The short version is that these two lines are roughly equivalent:

a=a||"new value"a||="new value"
Enter fullscreen modeExit fullscreen mode

The second only elides the= a || part in favor of||=. This works great in most cases, but it has one glaring weakness: legitimate falsy values.

Legitimate Falsy Values

In Ruby onlynil andfalse are considered to be falsy. The problem is iffalse ornil happen to be an expected return of an expensive method. Let's say we had this:

classMyClassdefsome_question?@some_question||=api_response.include?("expected value")enddefapi_responseNet::HTTP.get(URI("url_goes_here"))endend
Enter fullscreen modeExit fullscreen mode

Chances are if the response ofsome_question? isfalse that's a valid return, and we don't want to go calling the API repeatedly until it happens to be true, meaning that||= would cause it to get called repeatedly.

That's not great, so what do we do about it instead?

Thedefined? method

We can check whether an instance variable was even defined in the first place usingdefined?. It works on any type of variable, so let's start with a local variable here:

defined?a# => nil
Enter fullscreen modeExit fullscreen mode

Now you might expect this to give a Boolean result, but in actuality it does something a bit odd:

defined?a# => nila=5defined?a# => "local-variable"
Enter fullscreen modeExit fullscreen mode

Considering aString is truthy this doesn't really present an issue, but it's good to know that this does not returntrue orfalse, but rather the type of variable ornil.

Why is this important? Well if we took that same method from above where||= fails to "remember" legitimately false values we could make it into something like this instead:

classMyClassdefsome_question?return@some_questionifdefined?@some_question@some_question=api_response.include?("expected value")enddefapi_responseNet::HTTP.get(URI("url_goes_here"))endend
Enter fullscreen modeExit fullscreen mode

Now if that instance variable happens to be set the value will be remembered despite being falsy in nature.

So Which Do I Use?

If a method is Boolean (it returnstrue orfalse) in nature usedefined?. If it returns any other type, or you want to recompute in the cases offalse ornil you're safe to use||= instead.

You Said It's More Complicated?

Yep. Left as an exercise to the reader what do you reckon happens if we start introducing method arguments into the equation, or other inputs from other parts of the application? In those cases the responses are only valid in the same context, like being called with the same arguments.

You can still do this with aHash, yes, but we won't get too far into that in this article as that would be more advanced. If you want a more detailed explanation of this feel free to leave a comment and I can look into it.

There's also the entire&&= operator which very few people use, which is more of a "yes, and..." concept, but that's also another article. I may write that one regardless as I very rarely see it in production code, but it definitely has a place there.

Wrapping Up

This, again, is more of an introduction to memoization in Ruby. There are more detailed guides out there including this RubyConf talk called "Achieving Fast Method Metaprogramming: Lessons from Memowize"

I will be looking to expand this series as I get back into mentorship more at work with common questions I happen to see come up. If you happen to have questions about any part of Ruby feel free to reach out or comment on this post and I can take a look into covering them as well.

Top comments(5)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
hachi8833 profile image
hachi8833
  • Joined

Hello, I'm a bit confused with the fact thatbody inapi_response.body.include?("expected value") doesn't work. Looks likebody is unnecessary in the case. Or do I misunderstand something?

CollapseExpand
 
baweaver profile image
Brandon Weaver
Senior Staff Eng at One Medical. Autistic / ADHD, He / Him. I'm the Lemur guy.
  • Location
    San Francisco, CA
  • Work
    Senior Staff Eng at One Medical
  • Joined

Too used to usingHTTP.rb, bad habit. I will fix this.

CollapseExpand
 
hachi8833 profile image
hachi8833
  • Joined

Thank you for the fix!❤️

CollapseExpand
 
maxfindel profile image
Max F. Findel
Indie Developer. Love privacy-focused software. I read the terms and conditions before accepting them.
  • Location
    Chile
  • Work
    CTO at Lexgo
  • Joined

Thanks for the detailed explanations. The example for boolean variables is really useful! 🙌

CollapseExpand
 
klkelvinlin profile image
Kelvin Lin
  • Joined

These series are very helpful. Thank you!

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Senior Staff Eng at One Medical. Autistic / ADHD, He / Him. I'm the Lemur guy.
  • Location
    San Francisco, CA
  • Work
    Senior Staff Eng at One Medical
  • Joined

More fromBrandon Weaver

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp