Ramda — 根据允许列表切换对象的属性

Posted

技术标签:

【中文标题】Ramda — 根据允许列表切换对象的属性【英文标题】:Ramda — Toggle properties of an object based on an allow list 【发布时间】:2021-11-14 08:07:30 【问题描述】:

我有以下情况,一个对象和一个数组:

const currenObject = 
  errors: false,
  values: false,
  warnings: false,
;

const template = ['values', 'warnings'];

如何映射currentObject 以及template 数组中是否存在属性以将相应的属性更改为true。所以上面的例子应该产生:

const currenObject = 
  errors: false,
  values: true,
  warnings: true,
;

这对于 vanilla javascript 来说是微不足道的,但目标是使用 Ramda JS 来实现它。 提前谢谢你。

【问题讨论】:

【参考方案1】:

其他答案假设您要将相关值设置为true

但标题暗示您想要切换它们当前的布尔值。这是一个这样做的版本,如果我将输入的一个目标值切换为以 true 开头,这将更加明显。

const toggleFields = compose (evolve, compose (mergeAll, map (objOf (__, not))))

const currentObject = 
  errors: false,
  values: true,
  warnings: false,
;

const template = ['values', 'warnings'];

console .log (toggleFields (template) (currentObject))
// => 
//      errors: false,  // ignored
//      values: false,  // toggled from true
//      warnings: true  // toggled from false
//    
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>
<script>const compose, evolve, mergeAll, map, objOf, __, not = R </script>

我们将['values', 'warnings'] 变成

evolve (
  values: not,
  warnings: not
)

not 是 Ramda 的布尔切换函数,evolve 接受一个配置对象映射名称到转换器函数并返回一个函数,该函数接受一个对象并克隆它,通过适当的函数转换任何指定的属性。然后我们简单地用我们的源对象调用这个函数。

如果我们碰巧遇到字符枯竭,我们可以使用 Ramda curried,二进制合成函数 o,代替古老的 compose 本身:

const toggleFields = o (evolve, o (mergeAll, map (objOf (__, not))))

根据口味,我们也可以将objOf (__, not) 替换为flip (objOf) (not)

推导

这个版本看起来很密集。我原来是这样写的:

const fn = (template) => (currentObject) => evolve (pipe (
  map (objOf (__, not)),
  mergeAll
) (template)) (currentObject)

但是在这里,如果我们稍微斜视一下,我们可以看到这个形式:

(x) => (y) => f (g (x)) (y)

x 代表 templatey 代表 currentObjectf 代表 evolveg 代表其他所有内容。

而当我们有一个最终参数只应用在主体的末尾时,我们可以简单地消除它,所以和

(x) => f (g (x))

但这只是fg 的组合。所以我们可以这样写

compose (f, g)

然后用原来的代入我们得到

compose (
  evolve,
  pipe (map (objOf (__, not)), mergeAll) 
)

我通常不喜欢在管道中混合composepipe。我将compose 用于单行,pipe 用于更长的功能;对我来说,这开始看起来像一条线。所以我们交换mapmergeAll的顺序并切换到compose,得到这个:

compose (evolve, compose (mergeAll, map (objOf (__, not)))

香草

作为 Ramda 的创始人和主要作者,我实际上是经常建议人们尝试将它应用到他们不需要的地方的人。但是在这里我觉得这个版本比我想出的第一个香草JS版本要干净得多:

const toggle = (template) => (currentObject) =>
  Object .fromEntries (
    Object .entries (currentObject) 
      .map (([k, v]) => [k, template .includes (k) ? !v : v] )
  )

这并不是坏事,我不会仅仅为了这个微小的改进而引入 Ramda。但如果我已经在使用 Ramda,那么 Ramda 版本似乎是一个改进。

【讨论】:

我喜欢这个答案中的眯眼练习。练习查看函数或过程的“形状”很有用。就像俄罗斯方块一样,每一个俄罗斯方块都捕捉到了一种独特的本质,并决定了它与其他作品的契合度。 非常详尽的解释斯科特。谢谢您的帮助。 Ramda 很棒,探索起来也很有趣。【参考方案2】:

template 映射到[key, T] 的数组,使用R.fromPairs 转换为对象,然后使用R.evolve 创建更新后的对象:

const  pipe, map, of, append, T, fromPairs, evolve  = R;

const fn = pipe(map(pipe(of, append(T))), fromPairs, evolve);

const currenObject = 
  errors: false,
  values: false,
  warnings: false,
;

const template = ['values', 'warnings'];

const result = fn(template)(currenObject);

console.log(result);
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous" referrerpolicy="no-referrer"&gt;&lt;/script&gt;

【讨论】:

再次,很好的解释 Ori。对我来说,这是一种新方法——可以说统一接口。我用香草js做了类似的事情。再次感谢您的回答。【参考方案3】:

带模块

这是解决问题的基于模块的方法 -

// obj.js

function get (t, k) 
  return t[k]


function set (t, k, v) 
  return  ...t, [k]: v 


function update (t, k, f) 
  return set(t, k, f(get(t, k)))


export  get, set, update 

我们建立了与对象交互的通用方式。现在我们可以使用update 轻松编写toggle -

// main.js

import  update  from "./obj.js"

function toggle (t, k) 
  return update(t, k, (v = false) => !v)


const input =
   errors: false, values: false, warnings: false 
  
const output =
  ["values", "warnings"].reduce(toggle, input)

console.log(output)

  errors: false,
  values: true,
  warnings: true

展开下面的sn-p,在浏览器中验证结果-

function get (t, k) 
  return t[k]


function set (t, k, v) 
  return  ...t, [k]: v 


function update (t, k, f) 
  return set(t, k, f(get(t, k)))


function toggle (t, k) 
  return update(t, k, v => !v)


const input =
   errors: false, values: false, warnings: false 
  
const output =
  ["values", "warnings"].reduce(toggle, input)

console.log(output)

使用 ramda

如果您更喜欢 Ramda,有确切(或接近)等价物 -

obj module ramda
obj.get R.prop
obj.set R.assoc
obj.update R.evolve

知道了这一点,我们可以选择直接使用 ramda 或者我们可以使用 ramda 本身来实现obj 模块 -

// obj

import  assoc, prop, evolve  from "ramda"

function get (t, k) 
  return prop(k, t)


function set (t, k, v) 
  return assoc(k, v, t)


function update (t, k, f) 
  return evolve( [k]: f , t)


export  get, set, update 

这里的区别是obj 的用户不知道 ramda 正在幕后使用。这是设计自定义模块的有用方法,可以更有效地适合您的程序或模式,同时利用其他库或模式的优势。

【讨论】:

非常好!我可能会争辩说,Ramda 与您的update 最接近的是lensPropover 的组合,但这会涉及到镜头的奇妙世界,也许是矫枉过正。这确实表明 Ramda 可能缺少一个功能。我们必须考虑一下。 @ScottSauyet 我在镜头问题上被撕裂了,选择了简单。我很乐意帮助发现更多使 ramda 变得更有用的方法^^ 由于 javascript 的动态类型,我看不到两者的统一类型,特别是因为数组是数字索引的,而对象是字符串索引的。但实际上,javascript 是松散的,允许使用混合类型访问数组和对象。另外,我可以看到对象需要函数为a -&gt; b,但如果你想强制执行同构数组,函数必须为a -&gt; a,从而导致冲突。当然,支持两者的adj 的实现很容易,但是在这种情况下输入它似乎几乎是不可能的。 你可能是对的。自从我写了这篇文章后,我没有再考虑过这个问题,但回想起来,我认为没有充分的理由去追求它。我可能会尝试获得一个等效的简单功能,尽管这项任务不需要镜头。感谢您的反馈! 任何时候,我都非常感谢我们的讨论【参考方案4】:

给定:

const obj =  errors: false, values: false, warnings: false ;
const tpl = ['values', 'warnings'];

您可以通过以下方式从tpl 中挑选obj 中的属性:

pick(tpl, obj);
//=> "values": false, "warnings": false

并将它们设置为true

map(T, pick(tpl, obj));
//=> "values": true, "warnings": true

剩下的就是将该对象合并到原始对象中:

const toggle = curry((t, o) => mergeRight(o, map(T, pick(t, o))));

toggle(tpl, obj);
//=> "errors": false, "values": true, "warnings": true

【讨论】:

以上是关于Ramda — 根据允许列表切换对象的属性的主要内容,如果未能解决你的问题,请参考以下文章

根据匹配的对象属性从列表创建列表

Python:根据属性对对象列表进行排序

如何根据对象的属性对对象列表进行排序?

如何根据对象的属性对对象列表进行排序?

设计模式学习篇-state状态模式

如何使用Ramda将对象数组转换为列表