记忆一个咖喱函数
Posted
技术标签:
【中文标题】记忆一个咖喱函数【英文标题】:Memoize a curried function 【发布时间】:2019-06-02 06:47:16 【问题描述】:const f = (arg1) => (arg2) => /* returns something */
是否可以记住 f 关于 2 个参数,即:
f(1)(2);
f(1)(3); // Cache not hit
f(4)(2); // Cache not hit
f(1)(2); // Cache hit
【问题讨论】:
我猜你应该添加一个不同的层来缓存调用...... 查看reselect
库。应该做你想做的开箱即用。
为什么需要currified,似乎是一个不必要的抽象。
【参考方案1】:
这可能不是规范的记忆功能。
需要缓存其结果的函数被赋予一个cache
函数,用于存储和检索以前的结果:
const sum = memo(cache => a => b => cache(`$a+$b`, () => a + b));
// ^^^^^ ^^^^^^^^^^^ ^^^^^^^^^^^
// A B C
A — cache
函数由 memo
函数提供。(如果需要,memoized 函数可以选择不缓存某些结果。) em>
B — 结果的唯一键。 (例如cache['1+2'] = 3
)
C — 返回结果的 thunk。(因此我们可以在计算之前检查是否已经拥有它。)
这支持柯里化和非柯里化函数,也支持将函数作为值返回的函数。
memo
函数可以实现如下:
const memo = fn =>
const ns = Symbol();
const cache = (key, thunk) => cache[ns][key] ??= thunk();
cache[ns] = ;
return fn(cache);
;
我非常喜欢使用logical nullish assignment 操作符来管理缓存:
a ??= answer()
评估右侧的表达式并将其分配给a
当且仅当 a
尚未定义。然后返回a
的值:
const answer = () => (console.log('returning the answer'), 42);
let a;
a ??= answer();
//=> LOG: returning the answer
//=> 42
a ??= answer();
//=> 42
a ??= 40;
//=> 42
我使用了一个符号来隐藏cache
函数上的实际缓存集。枚举对象属性时不返回符号:
const foo = ;
const key1 = Symbol();
const key2 = 'bar';
foo[key1] = 42;
foo[key2] = 41;
Object.keys(foo);
//=> ['bar']
Object.entries(foo);
//=> [['bar', 41]]
演示
// curried memoized function
const sum = memo(cache => a => b =>
cache(`$a+$b`,
() => (console.log(`computing $a+$b…`), a+b)));
console.log(sum(1)(2));
console.log(sum(1)(2));
console.log(sum(1)(2));
// non-curried memoized function
const mul = memo(cache => (a, b) =>
cache(`$a*$b`,
() => (console.log(`computing $a*$b…`), a*b)));
console.log(mul(2, 3));
console.log(mul(2, 3));
console.log(mul(2, 3));
// function-returning function
const deferred_sum = memo(cache => a => b =>
cache(`$a+$b`,
() => (console.log(`defer computing $a+$b…`), () => a+b)));
console.log(deferred_sum(1)(2)());
console.log(deferred_sum(1)(2)());
console.log(deferred_sum(1)(2)());
<script>
const memo = fn =>
const ns = Symbol();
const cache = (key, thunk) => cache[ns][key] ??= thunk();
cache[ns] = ;
return fn(cache);
;
</script>
【讨论】:
【参考方案2】:您可以将Map
作为缓存,并为所有以下参数使用嵌套映射。
此缓存适用于任意数量的参数,并重用之前调用的值。
它通过一个柯里化函数和一个可选的Map
来工作。如果未提供映射,则创建一个新映射,用作返回闭包的所有其他调用或最终结果的基本缓存。
内部函数接受一个参数并检查该值是否在地图中。
如果不是,调用curried函数并检查返回值
如果是函数,则在函数上创建一个新闭包和一个新映射,
如果没有函数获取结果,
作为地图新元素的值。
最终从地图中返回值。
const cached = (fn, map = new Map()) => arg =>
const inCache = map.has(arg);
const hint = inCache ? 'in cache' : 'not in cache';
console.log(arg, hint);
if (!inCache)
const value = fn(arg);
const result = typeof value === 'function' ? cached(value, new Map()) : value;
map.set(arg, result);
return map.get(arg);
;
const f = a => b => c => a * b * c; // the original curried function
const g = cached(f); // its cached variant
console.log(g(1)(2)(5)); // not not not 10
console.log(g(1)(3)(4)); // in not not 12
console.log(g(4)(2)(3)); // not not not 24
console.log(g(1)(2)(6)); // in in not 12
console.log(g(4)(2)(3)); // in in in 24
.as-console-wrapper max-height: 100% !important; top: 0;
【讨论】:
请您详细说明一下您的答案(解释)很难理解所有这些功能是如何工作的;) @robe007,你是说f = ...
吗?那是一个带有一个参数的函数并返回一个带有另一个参数的函数,并返回相同的然后返回结果。基本上它是对参数的闭包。
不错!我在这里使用了类似的技术-> github.com/tonix-tuft/react-suspense-async-effect
又是一个好人!我刚刚注意到可以使用对象
而不是地图来完成(使用.hasOwnProperty(arg)
进行存在测试)。您使用new Map()
有什么特别的原因,还是一种解决方案与另一种解决方案一样好?
@cars10m,在地图中,键可以是任何类型,因为对象只知道字符串和符号。【参考方案3】:
您不能将 map 传递给每个函数。 你可以这样做:
const memoize = fn =>
const cache = ;
return (...args) =>
const curriedFn = fn(...args);
return (...next) =>
const key = // generate your key
if (key in cache) return cache[key];
return (cache[key] = curriedFn(...next));
【讨论】:
【参考方案4】:有趣的问题——你可以为每个函数设置独立的缓存。外部函数上的缓存将保存函数。每个内部函数都可以获得自己的独立缓存。因此调用f(10)(1)
后跟f(10)(2)
将导致调用内部函数的缓存版本。再次调用f(10)(1)
会命中两个缓存:
function getCachedF()
// outer cache holds functions keyed to argument
let outer_memo =
const f = (arg1) =>
if (!outer_memo.hasOwnProperty(arg1))
// Create inner function on outer cache
// each inner function needs its own cache
// because it will return different values
// given different outer function calls
let inner_memo =
console.log("outer cache miss")
outer_memo[arg1] = (arg2) =>
// just a normal memoized function
// cache is simple key:value pair
if (!inner_memo.hasOwnProperty(arg2))
console.log("inner cache miss")
inner_memo[arg2] = arg1 + arg2
return inner_memo[arg2]
return outer_memo[arg1]
return f
let f = getCachedF()
// both caches miss
console.log("3+5", f(3)(5))
// cached result
console.log("3+5", f(3)(5))
// only inside cache hit
console.log("3+8", f(3)(8))
// inside cache only hits if both args are the same
console.log("10+8", f(10)(8))
另一种选择是使用单个缓存,其键是两个参数的组合,但始终必须调用内部函数。
【讨论】:
以上是关于记忆一个咖喱函数的主要内容,如果未能解决你的问题,请参考以下文章