And here's an argument for why you want your non-varying arguments to come first in your function signature.
Many languages have the concept of "partial application", where supplying a function with fewer arguments than it expects doesn't raise an error, but instead returns a function that is waiting for the remaining arguments.
For example, imagine if you had a function add(x,y)
and add(1)
didn't throw an error, but returned a function that was waiting for y
and returned 1 + y
when you supplied it with that single argument, e.g.,
def add(x, y):
return x + y
add_one = add(1) # Error!
add_one(10) == 11
Some languages have this behavior built-in, e.g., Haskell and F#, while others have ways of supporting it, e.g., Python via functools.partial
(https://docs.python.org/3/library/functools.html#functools.partial)
With that we can make the above code work:
import functools
def add(x, y):
return x + y
add_one = functools.partial(add, 1)
add_one(10) == 11 # Actually works!
What does this have to do with memoization? Well, sometimes we don't want to memoize every argument, as in the arrays of weights and values in the 01 Knapsack problem.
We can "freeze" the weights + values and memoize the remainder, though.
import functools
def knapsack01(values, weights, num_items, capacity):
pass
values = [...]
weights = [...]
knapsack01_memo = memoize(functools.partial(knapsack01, values, weights))
Now calling knapsack01_memo(num_items, 100)
(for example) only uses those arguments as keys in the memo cache.