-
Notifications
You must be signed in to change notification settings - Fork 22
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
Remove eval, memoize methodwise, traits, callables, typed caches #70
Remove eval, memoize methodwise, traits, callables, typed caches #70
Conversation
Fix JuliaCollections#48, at the cost of putting the variable in a poorly-performing global. Not sure if this is acceptable. It's frustrating that Julia seemingly lacks the tools to deal with this elegantly. - If `const` worked in a local function, we'd just put `const` and be done with it. - If `typeconst` existed for global variables, that would work too. Memoization.jl uses generated functions, which causes other problems. And it feels like the wrong solution too.
at the global scope
It makes the macro expansion much more palatable
Codecov Report
@@ Coverage Diff @@
## master #70 +/- ##
===========================================
- Coverage 100.00% 90.81% -9.19%
===========================================
Files 1 1
Lines 43 98 +55
===========================================
+ Hits 43 89 +46
- Misses 0 9 +9
Continue to review full report at Codecov.
|
src/Memoize.jl
Outdated
end | ||
end | ||
|
||
const _brain = Dict() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A global like this is problematic because of precompilation. Eg. if you have a package with
module X
@memoize foo(x) = x
end
I believe you'll find that after using Memoize, X
, Memoize.brain
is empty.
Hi Peter, this is an impressive list of fixes! I love all the tests and README additions. For cache invalidation, from an API perspective, I believe we only really need:
I like the direction you took with ...
global var"__memoize_cache_foo(x::Int,y)" = Dict()
... then on redefinition, we can just Cache emptying is the first problem to solve. If you're interested in implementing that (or another precompilation-compatible solution), I would appreciate if that was a small, self-contained PR starting from #59 (you can choose the target on github). It's really best to split big PRs into chunks. It's so much easier to review and merge a small PR! |
Good catch regarding precompilation. I think my most recent commits fix this using something similar to your suggestion, but using only one variable holding the dictionary instead of one variable per method. I'll split this up. |
Reflecting a little bit on the proposed solution, and other potential ways to simplify #71:
I'm not convinced this approach will work. It doesn't detect overwrites when variables are named differently. For example, the string Defining a method overwrites a previous method precisely when their type signatures match, so it seems best to use the type signatures (e.g.
I think that the first solution would likely be easier to read than #71, and I'm not sure anyone will notice the slowdown of linear search versus whatever julia uses internally to solve the "find equivalent type" problem. We might also simplify #71 a bit by calling a more standard form of |
If this is a concern because there was only one global dict in the Memoize package, #71 has been amended to use a separate dictionary in each package that uses memoize. If, however, this was a concern because of the interactions between precompilation and rehashing dictionaries; whatever problems we have with the global dictionary that Memoize creates, we'll also have with the dictionaries/caches the global dict is attempting to store, and that might be a separate issue. |
That's true, but to me that's basically a negligible problem, affecting a vanishingly small number of real-world scenarios. It's also interesting to think about what Revise will do with that. Does Revise delete global variables when they are deleted in the code? I don't know.
I can't think of any code I've ever seen that would do something like that. Can you? People might write for T in [:Int, :Vector]
@eval @memoize f(::$T) = ...
end but that would work fine, because the $ happens before the |
Since the list is global, wouldn't you be stuck with the same precompilation problems as before?
I like that... if it works! 🙂 Memoize.jl is such a julia puzzle to figure out.
I don't understand. For local variables, I don't think we need to support explicit user-side cache invalidation, so this looks fine to me (as long as the cache can be GC'ed once the function returns)
👍
That's new to me, what are those interactions? |
After attempting to implement 2, I suspect it might be impossible to avoid introspection: We need to find the module in which the overwritten method was defined. I can't think of a way to do it without calling |
Yes, with what I suggested we need introspection too, as I said:
Like I said, I don't think the name-dependent behaviour is problematic, but if you can make it work with similar complexity, I will be happy to do the right thing and use types. |
This PR picks up where #59 left off (thanks @cstjean for all your work on this repo!). If we remove
eval
and use one cache per method, then we need to be able to look up caches by their methods. Types can't be hashed by equivalence classes (otherwise I think Julia dispatch might be faster, considerhash(Val{A} where {A}) != hash(Val{B} where B)
). Therefore, we need to use Julia's method lookup to find methods to find their cache. We use a global dictionary in the Memoize.jl module to mapMethod
objects to their caches. Since this was already a big change, this PR also makes several related changes.The key changes made by this PR
eval
. (fixes Removeeval
from at-memoize macro #48)(::T)
can now be memoized, and they useT
as their key)__Key__
and__Value__
in the cache constructor expression scope, to support more specific dictionary types.In summary, now you can do: