【问题标题】:How to apply properties of an object to functions?如何将对象的属性应用于函数?
【发布时间】:2020-04-09 05:01:01
【问题描述】:

我想对一些对象属性应用不同的函数。假设我有这个对象:

const person = {
  name: 'John',
  age: 30,
  friends: [],
};

我有一些功能要应用于这些属性:

const upperCase = str => str.toUpperCase() //for the name

const add10 = int => int + 10 // for the age

const addFriend = (value,list) => [...list,value] // adding a friend

这应该是结果:

const person = {
  name: 'JOHN',
  age: 40,
  friends: ['Lucas']
}

使用函数式编程和无点实现这一目标的最佳方法是什么,如果您可以包含使用 Ramda 的示例,我将不胜感激,谢谢!

【问题讨论】:

  • 在对象的成员上调用每个函数。
  • Ramda 和 Folktale 在函数式编程的方法上非常不同。这并不是说它们是关于“不同”的函数式编程,而是它们是关于它的不同方面。 Ramda 为您提供函数式风格的辅助函数,例如 R.mapR.flipR.compose 等。而 Folktale 实现了诸如 Functor、Monad、Applicative、Grupoid 等代数类型。两者并不是真正可互换的,但可以使用在一起。
  • @MaihanNijat 我知道我可以调用对象上的每个函数,一次一个,但是有没有办法我可以同时应用所有这些函数,并保持函数纯净?
  • @DavidSttivendAngel 为所有这些函数创建一个包装函数,并且只调用一次。
  • @MaihanNijat 我想我知道如何通过定期通话来做到这一点。我更新了问题,所以答案也包括免费的。谢谢

标签: javascript functional-programming ramda.js folktale


【解决方案1】:

像往常一样,customcommander 给出了一个很好的答案。 Ramda 的evolve 正是为此而设计的,在简单的情况下,它可以提供完全无意义的答案。

Ramda 的作者之一,我非常喜欢它的功能。但应该注意的是,构建自己的许多版本非常容易,包括这个版本,特别是如果您处于只关心更新对象根目录中的键的简单情况下。 (Ramda 递归到嵌套的规范对象中,这至少涉及更多。)

所以,如果你手边没有 Ramda,你可以像这样轻松编写这个函数的自定义版本:

const evolve = (spec, keys = Object .keys (spec)) => (obj) => 
  Object .entries (obj) .reduce ( 
    (a, [k, v]) => ({...a, [k]: keys .includes (k) ? spec [k] (v) : v})
    , {}
  )

const upperCase = str => str.toUpperCase() //for the name
const add10 = int => int + 10 // for the age
const addFriend = (value) => (list) => [...list,value] // adding a friend
const person = {name: 'John', eys: 'blue', age: 30, friends: [], hair: 'red'}

console .log (
  evolve ({name: upperCase, age: add10, friends: addFriend('Lucas')}) (person)
)

这远非性能最高的代码。 Rich Snapp 的一篇优秀文章解释了为什么以及如何修复它。但我个人会坚持这一点,除非这成为我的应用程序的瓶颈。

当然,正如customcommander 也指出的那样,您的每个帮助都有Ramda 函数:toUpperaddappend(比prepend 更接近上述实现。)

关于评论中提出的 VLAZ 主题的更完整讨论可以在https://stackoverflow.com/a/33130194/1243641 中找到。

【讨论】:

  • 不只是 reduce-spread-antipattern,concat 用作 push/unshift 也是有害的,因为它将数组视为持久数据结构。
  • @bob:但除非它对我来说是一个实际的性能问题,否则我会接受它作为权衡,以便尽可能多地使用表达式而不是语句。 (或者你有避免这种情况的基于表达式的方法吗?)有很好的工具可以告诉我这样的事情是我的应用程序的瓶颈。但是我什至不得不使用一个已经有好几年了。即使以这种风格写作,我通常也能达到我的速度目标,而减速几乎总是由于 IO,而不是这种结构。也许有一天我会为此诅咒自己。
  • 在 99% 的情况下,性能损失可能可以忽略不计。但是,我认为将数组视为命令式数据结构是一种很好的工程,即局部突变不仅可以,而且是默认值。我使用了几个允许突变并且仍然可以组合的库函数。一如既往,这是一种权衡。 Objects 则不同,因为它们在与镜头一起使用时具有固有的持久性..
  • @bob:我不遵循您对数组和对象的不同处理的基本原理。我两者都用镜头。我当然有时会在函数边界内使用数据结构的突变,但通常最终会感觉有点脏。我尝试合并表达式而不是语句代码的次数越多,我就越有可能接受这种性能不佳的模式,除非我看到可衡量的危害。
  • Object 在与镜头一起使用时本质上是持久的,因为只有属性本身及其根路径是在更新时创建的,而其他所有内容都是共享的。对于数组,即使只更新了一个元素,也必须创建整个 Array。这是因为数组本质上是非持久的。这有意义吗?希望。
【解决方案2】:

使用 Ramda,您正在查看 evolve 函数:

根据转换函数,通过递归地演化对象的浅表副本来创建新对象。通过引用复制所有非原始属性。

您可能需要动态定义“转换”函数;我敢打赌,您不想将“卢卡斯”添加为每个人的朋友。希望这足以让您入门。

const transform =
  evolve(
    { name: toUpper
    , age: add(10)
    , friends: prepend('Lucas')
    });
    
    
console.log(

  transform(person)

);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script>const {evolve, toUpper, add, prepend} = R;</script>
<script>
const person =
  { name: 'John'
  , age: 30
  , friends: []
  };
</script>

无点样式不是唯一的方法

⚠️

请注意,我已经能够实现这种无点样式,因为我可以对每个转换函数进行硬编码。如果您需要即时制作转换对象,那么无点样式可能很快就无法阅读。

【讨论】:

  • 谢谢是我一直在寻找的
猜你喜欢
  • 2021-09-01
  • 1970-01-01
  • 2022-01-10
  • 2020-12-24
  • 2023-01-20
  • 2019-09-12
  • 2017-08-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多