您在这里有几个问题-
- 安全地访问不确定对象上的深层嵌套数据
- 使用功能技术对复杂数据数组进行排序
- 安全地并且不可变地>>更新不确定对象上的深层嵌套数据] >>
1。
使用.map
和.chain
是低级的,当语法变得痛苦时,应添加其他抽象-
const state =
{ tree:
{ type: "sequoia"
, children: [ "a", "b", "c" ]
}
}
recSafeProp(state, [ "tree", "type" ])
.getOrElse("?") // "sequoia"
recSafeProp(state, [ "tree", "foobar" ])
.getOrElse("?") // "?"
recSafeProp(state, [ "tree", "children", 0 ])
.getOrElse("?") // "a"
recSafeProp(state, [ "tree", "children", 1 ])
.getOrElse("?") // "b"
recSafeProp(state, [ "tree", "children", 99 ])
.getOrElse("?") // "?"
我们可以轻松实现recSafeProp
,并在此过程中发明了自己的便利功能safeProp
–
const { Nothing, fromNullable } =
require("data.maybe")
const recSafeProp = (o = {}, props = []) =>
props.reduce // for each props as p
( (r, p) => // safely lookup p on child
r.chain(child => safeProp(child, p))
, fromNullable(o) // init with Maybe o
)
const safeProp = (o = {}, p = "") =>
Object(o) === o // if o is an object
? fromNullable(o[p]) // wrap o[p] Maybe
: Nothing() // o is not an object, return Nothing
2。
Comparison
模块。您似乎对功能模块很熟悉,所以让我们直接进入->const { empty, map } =
Comparison
const prioritySort =
map(empty, record => record.priority || 0)
// or...
const prioritySort =
map(empty, ({ priority = 0}) => priority)
myarr.sort(prioritySort)
如果您想要不可变的排序–
const isort = (f, [ ...copy ]) =>
copy.sort(f)
const newArr =
isort(origArr, proritySort)
这里是Comparison
模块。在this Q&A中引入。如果您有兴趣查看如何以实用的方式制作复杂的分拣机,请在此处进行检查。
const Comparison =
{ empty: (a, b) =>
a < b ? -1
: a > b ? 1
: 0
, map: (m, f) =>
(a, b) => m(f(a), f(b))
, concat: (m, n) =>
(a, b) => Ordered.concat(m(a, b), n(a, b))
, reverse: (m) =>
(a, b) => m(b, a)
, nsort: (...m) =>
m.reduce(Comparison.concat, Comparison.empty)
}
const Ordered =
{ empty: 0
, concat: (a, b) =>
a === 0 ? b : a
}
合并分类器–
const { empty, map, concat } =
Comparison
const sortByProp = (prop = "") =>
map(empty, (o = {}) => o[prop])
const sortByFullName =
concat
( sortByProp("lastName") // primary: sort by obj.lastName
, sortByProp("firstName") // secondary: sort by obj.firstName
)
data.sort(sortByFullName) // ...
可分类分类器–
const { ..., reverse } =
Comparison
// sort by `name` then reverse sort by `age` –
data.sort(concat(sortByName, reverse(sortByAge)))
功能原理–
// this...
concat(reverse(sortByName), reverse(sortByAge))
// is the same as...
reverse(concat(sortByName, sortByAge))
以及–
const { ..., nsort } =
Comparison
// this...
concat(sortByYear, concat(sortByMonth, sortByDay))
// is the same as...
concat(concat(sortByYear, sortByMonth), sortByDay)
// is the same as...
nsort(sortByYear, sortByMonth, sortByDay)
3。
此处到达的最低悬挂果实为Immutable.js。如果以后有更多时间,我将展示针对特定问题的lo-fi方法。
您的解决方案对我来说似乎太过分了。
函数式编程涉及很多事情,并且没有简洁的定义,但是如果您的主要工作单元是纯函数,并且您不对数据进行突变,那么您就可以成为函数式程序员。有时使用Either
monad有一些强大的好处。但是这段代码看起来更像是试图将Either
放到毫无意义的地方。
下面是编写此代码的一种建议方法。但是在此过程中,我必须做出一些假设。首先,当您讨论从服务器获取数据时,我假设它必须使用Promises
,async-await
或更好的替代方法(例如Tasks
或Futures
)以异步模式运行。我进一步假设您提到mocked_firewall
的地方就是您进行实际的异步调用的地方。 (您的示例代码将其视为可以在path
中查找结果的对象;但是,我无法真正模仿模拟真实服务。)还有一个假设:fold(() => log(...), x => log(x))
位什么都不是必不可少的,仅是您的代码已完成应做的演示。
考虑到所有这些,我编写了一个版本,其中有一个对象type
映射到data
和state
到新状态的函数,以及一个中央函数fetchData
,它需要类似[ C0](或真的像我对它的更改以返回Promise)并返回一个接受mocked_firewall
并返回新的state
的函数。
看起来像这样:
state
// State-handling functions
const handlers = {
tree: (data, state) => ({... state, tree: data}),
table: (data, state) => ({... state, table: {...data, records: prioritySort (data .records)}})
}
// Main function
const fetchData = (firewall) => (state) =>
firewall (state)
.then ((data) => (handlers [data .type] || ((data, state) => state)) (data, state))
// Demonstrations
fetchData (mocked_firewall) ({path: 'foo', table: null, tree: null})
.then (console .log) // updates the tree
fetchData (mocked_firewall) ({path: 'bar', table: null, tree: null})
.then (console .log) // updates the table
fetchData (mocked_firewall) ({path: 'baz', table: null, tree: null})
.then (console .log) // server returns type we can't handle; no update
fetchData (mocked_firewall) ({path: 'qux', table: null, tree: null})
.then (console .log) // server returns no `type`; no update
.as-console-wrapper {min-height: 100% !important; top: 0}
您会注意到这不会更改<script>
// Dummy functions -- only for demonstration purposes
const prioritySort = (records) =>
records .slice (0) .sort (({priority: p1}, {priority: p2}) => p1 - p2)
const mocked_firewall = ({path}) =>
Promise.resolve({
foo: {type: "tree", children: [{
type: "table",
records: [{id: 1, priority: 15}, {id: 2, priority: 3}]
}]},
bar: {
type: 'table',
records: [{id: 1, priority: 7}, {id: 2, priority: 1}, {id: 3, priority: 4}]
},
baz: {type: 'unknown', z: 45},
}[path] || {})
</script>
;而是返回该状态的新对象。我看到它被标记为state
,据我了解,这不是Vue的工作方式。 (实际上,这是我实际上并未真正使用Vue的原因之一。)您可以轻松地更改处理程序以使用vue
之类的内容来更新state
,甚至跳过返回值。但是,不要让任何FP狂热者抓住您这样做;请记住,函数式程序员非常熟悉“关键算法技术,例如递归和前提条件。” 1
。
您还标记了此tree: (data, state) => {state.tree = data; return state}
。我是Ramda的创始人之一,也是忠实的粉丝,但我看到Ramda仅在边缘提供帮助。例如,我包括了您提到但未提供的ramda.js
的原始版本。 Ramda版本可能会更好,例如
prioritySort
类似地,如果您不想改变状态,我们可以使用Ramda版本重写处理程序函数,可能会简化一点。但这将是次要的。对于主要功能,我看不到使用Ramda函数可以改善的任何内容。
有一个很好的可测试性参数,我们不仅应该将const prioritySort = sortBy (prop ('priority'))
传递给主函数,还应该传递firewall
对象。这完全取决于您,但这确实使更容易独立地模拟和测试零件。如果您不想这样做,则完全可以像这样在主函数中内联它们:
handlers
但是最后,我发现原始文档更容易阅读,无论是原样还是作为主要函数的另一个参数提供的处理程序。
1
原始报价来自
const fetchData = (firewall) => (state) =>
firewall (state)
.then ((data) => (({
tree: (data, state) => ({... state, tree: data}),
table: (data, state) => ({
... state,
table: {...data, records: prioritySort(data .records)}
})
}) [data .type] || ((data, state) => state)) (data, state))
,但我最擅长James Iry出色的
Verity Stob。