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
代表 template
,y
代表 currentObject
,f
代表 evolve
,g
代表其他所有内容。
而当我们有一个最终参数只应用在主体的末尾时,我们可以简单地消除它,所以和
(x) => f (g (x))
但这只是f
和g
的组合。所以我们可以这样写
compose (f, g)
然后用原来的代入我们得到
compose (
evolve,
pipe (map (objOf (__, not)), mergeAll)
)
我通常不喜欢在管道中混合compose
和pipe
。我将compose
用于单行,pipe
用于更长的功能;对我来说,这开始看起来像一条线。所以我们交换map
和mergeAll
的顺序并切换到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);
<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"></script>
【讨论】:
再次,很好的解释 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
最接近的是lensProp
和over
的组合,但这会涉及到镜头的奇妙世界,也许是矫枉过正。这确实表明 Ramda 可能缺少一个功能。我们必须考虑一下。
@ScottSauyet 我在镜头问题上被撕裂了,选择了简单。我很乐意帮助发现更多使 ramda 变得更有用的方法^^
由于 javascript 的动态类型,我看不到两者的统一类型,特别是因为数组是数字索引的,而对象是字符串索引的。但实际上,javascript 是松散的,允许使用混合类型访问数组和对象。另外,我可以看到对象需要函数为a -> b
,但如果你想强制执行同构数组,函数必须为a -> 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 — 根据允许列表切换对象的属性的主要内容,如果未能解决你的问题,请参考以下文章