如何实现可设置且可检索的状态而不会发生突变或重新分配?

时间:2019-05-17 07:32:42

标签: javascript functional-programming

在编写代码时,有几条规则可以很好地遵循:

  • 代码更易于阅读,并可以推断何时不进行重新分配;许多短毛猫建议尽可能使用const
  • 代码也更易于阅读,并能说明对象何时不发生突变。如果您在代码的一部分中定义了一个对象,了解您可以在其他地方自由引用该对象将很有帮助,它将完全相同。

在大多数情况下,这些规则都很好,并且可以完全遵循它们。但是在实现同时具有setter和getter功能(这是编程中非常常见的模式)的模块时,是否可以同时遵循它们?例如:

const module = (() => {
  // Reassignment, but no mutation:
  let savedData;
  return {
    getData: () => savedData,
    setData: (newData) => savedData = newData
  };
})();

module.setData('foo');
console.log(module.getData());

const module = (() => {
  // Mutation, but no reassignment:
  const savedData = { theKey: null };
  return {
    getData: () => savedData.theKey,
    setData: (newData) => savedData.theKey = newData
  };
})();

module.setData('foo');
console.log(module.getData());

我想不出如何在不更改或重新分配某处的情况下实现这样的事情-但应该避免这种情况。我要寻找的是可能的吗,还是我必须选择一个,而只是耸耸肩挑选一个,这仅仅是生活中的事实?

如果我调用了带有数据的模块以最初对其进行设置,而从不再次设置其数据,则可以,但是这种方法非常不灵活,不能满足大多数有用的用例。

const makeDataHolder = (savedData) => {
  return {
    getData: () => savedData,
  };
};

const fooData = makeDataHolder('foo');
console.log(fooData.getData());

我并不一定要寻找一种遵循 all 功能编程原理的解决方案(尽管,如果存在,将会很有趣),但是这个普遍的问题可能是熟悉的给那些使用函数式编程的人。

3 个答案:

答案 0 :(得分:4)

首先,在突变和重新分配之间观察到的对偶是完全正确的。同样,通过使用不可变的结构来实现具有可变数据结构的事物也是可行的,但是每次都会对其进行重新分配。

现在,有一点要记住:函数式编程不能神奇地消除对突变的需求。但是,它可以做的是将突变移动到代码库的边界,从而保持代码本身干净且无突变。

“将变异推离代码”的一个示例是使用数据库。数据库本质上是可变的,这很好。没有人会认为数据库是可变的,也没有人声称不应在函数式编程中使用数据库。只要可变性仅保留在数据库中,并且处理数据库的代码本身不包含任何突变,就可以。如果您不想将数据保留在某些数据库存储中,而是保留在内存中(例如出于性能原因),则可以使用Redis之类的内存中存储,并且代码保持不变。同样,只要您将重新分配和变更保留在代码库本身之外,就可以遵循函数式编程的原理。

将程序看作是某些处理单元,在“边缘”具有I / O,可以从生产者那里获取数据,并将处理后的数据带给消费者:

enter image description here

您的程序没有理由需要自己进行任何变异。它从输入端获取一些数据,对其进行处理,然后将其发送到输出端。当然,该过程可以交织在一起。例如,在处理输入的过程中,有时每隔一段时间会将某些内容发送到日志(即输出)。关键是,所有突变都发生在“边缘”(写入数据库/日志/文件系统,向其他API发送http请求等)。如果您觉得程序在某个时候需要getter和setter,则需要重新考虑设计,或者需要将getter / setter功能推到外部层,例如数据库(设置字段)。函数编程中不允许使用,但完全可以更新数据库记录)。

此外,许多编程语言不强制不变性,但仍鼓励使用函数式编程风格。有时,迭代编程和使用可变数据的方法被证明比该功能更具性能。只要不变性被隐藏在程序的其余部分之外,并保持本地化到代码的特定部分,就不会造成太大的伤害。例如,Scala(我知道)中foldLeft的所有内部实现都是可变的。但这不是问题,因为foldLeft方法的用户无法访问可变数据,因此可变性不会暴露在外部。几乎所有排序算法都一样。它们以可变的方式实现,就地替换了数组的元素,但是这些方法的用户看不到。内部实现使用可变性的事实不会降低代码的可读性或推理性,因为它在其余代码中不可见。

答案 1 :(得分:2)

  

实现同时具有setter和getter功能的模块

从根本上说与以下观点不一致:

  

对象不会发生突变

“设定者”(通常被理解)总是突变。

但是,在purely functional programming languages中,就像Haskell一样,您可以使用状态monad,它允许您编写外观和感觉非常像具有getter和setter的命令式代码的纯功能代码。

如果您google it,也有很多教程介绍如何在JavaScript中实现和使用状态monad。

答案 2 :(得分:0)

我认为,这里的模块就像一个有状态的闭包。修改状态恰好是您的目的。刻薄,设定!为此专门介绍了运算符。无论如何,只要您想自己维护一个状态,纯函数规则将不可避免地被破坏。