Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve the Memoizable module #1139

Merged
merged 8 commits into from
May 13, 2020

Conversation

matthewmcgarvey
Copy link
Member

@matthewmcgarvey matthewmcgarvey commented May 13, 2020

Fixes #1119

Purpose

This adds support for memoization of nil responses as well as using the macro with methods with arguments. I've not written any real macros before and this seems to be quite a monster so I have no idea if this is any good or not or if there are any missing edge cases.

It made sense to me to store the cached value in a tuple with all the arguments that are passed. If the method does not take any arguments but returns a string the tuple will just be Tuple(String) but if the method takes in a number it would be Tuple(String, Int32). Then, those tuple values that aren't the return value are checked against all passed in args. I didn't expect iterating over the args with a for-loop to automatically handle named arguments but my test seems to show that it does.

Example

class Users::Show < BrowserAction
  get "/users/:user_id" do
    stuff = get_user_and_make_api_call(user_id)
    # other stuff...
  end

  memoized get_user_and_make_api_call(user_id : String) : User
    get_user(user_id) && make_api_call
  end
end

becomes...

class Users::Show < BrowserAction
  get "/users/:user_id" do
    stuff = get_user_and_make_api_call(user_id)
    # other stuff...
  end

  @__memoized_get_user_and_make_api_call : Tuple(User, String)?

  def get_user_and_make_api_call__uncached(user_id : String) : User
    get_user(user_id) && make_api_call
  end

  def get_user_and_make_api_call__tuple_cached(user_id : String) : Tuple(User, String)
    @__memoized_get_user_and_make_api_call = nil if user_id != @__memoized_get_user_and_make_api_call.try &.at(1)
    @__memoized_get_user_and_make_api_call ||= -> do
      result = get_user_and_make_api_call__uncached(user_id)
      {result, user_id}
    end.call.not_nil!
  end

  def get_user_and_make_api_call(user_id : String) : User
    get_user_and_make_api_call__tuple_cached(user_id).first
  end
end

Checklist

  • - An issue already exists detailing the issue/or feature request that this PR fixes
  • - All specs are formatted with crystal tool format spec src
  • - Inline documentation has been added and/or updated
  • - Lucky builds on docker with ./script/setup
  • - All builds and specs pass on docker with ./script/test

@matthewmcgarvey matthewmcgarvey changed the title First take of improved Memoizable Improve the Memoizable module May 13, 2020
Copy link
Member

@jwoertink jwoertink left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dude! 🤩 this is amazing! I had been thinking about how to best tackle this, and I love the solution you came up with. I just left some small comments, but this is good to go!

spec/lucky/memoize_spec.cr Outdated Show resolved Hide resolved
src/lucky/memoizable.cr Show resolved Hide resolved
src/lucky/memoizable.cr Show resolved Hide resolved
Copy link
Member

@paulcsmith paulcsmith left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AMAZING work. Macros are crazy so I appreciate you showing the generated output for ease of reference.

Just a couple small tweaks I suggested, but overall this is awesome. Can't wait to get it in

spec/lucky/memoize_spec.cr Outdated Show resolved Hide resolved
spec/lucky/memoize_spec.cr Outdated Show resolved Hide resolved
# a return type for your method, or if your return type is a `Union`.
# Arguments are not allowed in memoized methods because these can change the return value.
# a return type for your method, or if any arguments are missing a type.
# Arguments are allowed but as soon as one changes, the previous value is no longer held on to.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not totally sure I understand this part. I think it does hold onto the value, but it creates a new value for the new arguments?

I wonder if this might just be expected and so this sentence could be left off. What do you think?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't tell if we're on the same page or not. Calling the memoized method with new arguments and then with the old arguments will re-run the memoized method. Does the updated description make this more clear?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohhhh, so if you do my_method(1) and then my_method(2). my_method(2) will overwrite the cached value. So if you do my_method(1) again it will re-run the method.

Is that right? If so I think I got it and the doc changes look good!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly 👍

@paulcsmith
Copy link
Member

Thank you :D 🚀 ❤️

@paulcsmith paulcsmith merged commit 9be023f into luckyframework:master May 13, 2020
@matthewmcgarvey matthewmcgarvey deleted the memoization-update branch May 13, 2020 22:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support Nil in memoize
3 participants