首页 > 解决方案 > 如何扩充现有函数,使其懒惰地解开其参数?

问题描述

我有一堆常规的实用功能。我想将这些函数转换为接受包含在函数中的参数的函数(以在使用值时引入副作用)。

// Some utility function:
const pick = (takeLeft, left, right) => 
  takeLeft ? left : right;

// Some wrapper with a side-effect
const Watched = x => () => {
  console.log(`Value ${x} is used`);
  return x;
};

// I want this to log:
//   Value true is used
//   Value L is used
const myPick = runWithWatchedValues(
  pick,
  Watched(true), Watched("L"), Watched("R")
);

我正在寻求帮助实施runWithWatchedValues,或者有人向我解释为什么它不能完成。

尝试

在调用内部函数之前解包值的问题:

// An incorrect attempt: (also logs "R")
const runWithWatchableValues = (f, ...args) => 
  f(...args.map(w => w()));

我测试了使用apply和一些特殊的 getter 调用该函数是否可以工作,但事实证明这完全一样。

// Another incorrect attempt: (also logs "R")  
const runWithWatchableValues = (f, ...watched) => {
  const args = watched.reduce(
    (acc, get, i) => Object.defineProperty(acc, i, { get }),
    []
  );

  return f.apply(null, args);
};

我目前的解决方案是手动重写实用程序函数。IE:

const pick = (takeLeft, left, right) => 
  takeLeft ? left : right;

const pickW = (takeLeft, left, right) => 
  takeLeft() ? left() : right();

// Correctly logs true and L
pickW(Watched(true), Watched("L"), Watched("R"));

当有像 ramda 或 lodash 这样有据可查且维护良好的库时,我宁愿不维护自己的实用函数库……</p>

问题

我开始觉得我想要的只是语言无法做到的……但我希望我错了!

“这是一个 x/y 问题!”</h2>

可能是这样,但我想保持简单。这是我真正在做的事情(使用 knockout.js):

const pick = (takeLeft, left, right) =>
  takeLeft ? left : right;

const takeLeft = ko.observable(true);
const left = ko.observable("L");
const right = ko.observable("R");

// BROKEN: The easy, but wrong implementation:
const myPick = ko.pureComputed(
  () => pick(takeLeft(), left(), right())
);

console.log(
  "Naive approach:",
  myPick(),                     // Right: "L"
  myPick.getDependenciesCount() // Wrong: 3
);

// The dependency on `right` will mean that updating
// it will cause myPick to re-evaluate, even though we
// already know its return value won't change.


// FIXED: The manual fix:
const pickObs = (takeLeft, left, right) =>
  takeLeft() ? left() : right();

const myCorrectPick = ko.pureComputed(
  () => pickObs(takeLeft, left, right)
);

console.log(
  "With manual rewrite:",
  myCorrectPick(),                     // Right: "L"
  myCorrectPick.getDependenciesCount() // Right: 2
);

// Changing `right` doesn't do anything. Only once `takeLeft`
// is set to `false`, a dependency on `right` will be created
// (and the dependency on `left` will be removed).
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

标签: javascriptfunctionarguments

解决方案


理论上是否可以编写runWithWatchableValues并获得所需的结果?

不,参数不会在 JavaScript 中延迟传递。在将值传递给包装函数之前,您需要严格评估函数,这就是您要避免的。

是否有另一种自动化方式(Babel/构建步骤?)您可以考虑避免手动重写 pick 以使用包装值?

当然,您可以编写自己的编译器来执行此操作(看起来相对容易),但我怀疑是否存在执行此操作的现有 babel 插件。惰性求值通常是没有用的,大多数函数在任何情况下都会使用它们的所有参数。


推荐阅读