[英]Flattening a nested Lazy List that may or not have children with FP-TS or Ramda?
我刚刚了解了提升和应用程序,并且在尝试理解这些结构的过程中,我正在尝试实现一个真实的用例。
我有一个惰性列表(数组),这意味着在加载它之前我无法获取项目或其子项的数量。 获取节点并加载它是异步的,与其嵌套的子节点(如果有)相同。
所以如果我有下面的结构:
[{title:"test1",children:[]},{title:"test2",children:[{title:"test2_1",children:[]}]}]
对于每个孩子,我不知道他们是否有孩子,直到我加载节点并检查孩子的数量。
我怎么能用 FP 检查整个列表(不管它如何嵌套):
-通过当时加载和检查每个节点。 当我们找到匹配或用完节点时停止。
或者
- 然后加载所有节点(可能将其嵌套到 Rights() 和 Lefts() 中),展平为单个列表,然后使用谓词按标题折叠项目,如下例所示。
这是我所知道的适用于非嵌套数组中元素的第一次匹配:
[{title:"test1"},{title:"test2"},{title:"test3"}] //structure we are loading
const find= (l,f)=>l.foldMap(x=>First(f(x)?Right(x):Left()),First.empty())
const nodes = await getNodes() //not gonna even put in a type just to illustrate that getting and loading the nodes is async.
const list = List(await load(nodes)) //not gonna even put in a type just to illustrate that getting and loading the nodes is async.
console.log(find(list,x=>x.title==='test3').fold(x=>x).fold(console.error,x=>x))
编辑:当前命令式工作代码:
这是一个共享点代码,它将从 globalNav 获取所有嵌套导航节点。 重要的是我想了解如何使用应用程序将其转变为更可取的 FP 实现。
GetNavigationNodeChildren = node => node.get_children();
GetNavigationNodeRoot = spCtx => spCtx.get_web()
.get_navigation().get_topNavigationBar();
ExecQuery = spCtx => resource => {
return new Promise((res, rej) => spCtx.executeQueryAsync(
() => res(resource),
(s, a) => rej({ s, a }),
));
};
LoadResource = spCtx => resource => (spCtx.load(resource) ? resource : resource);
LoadAndExec = spCtx => async resource => {
LoadResource(spCtx)(resource )
await ExecQuery(spCtx)(resource )
return resource
}
getAll = spCtx=> async resource=> {
return {node:await LoadAndExec(spCtx)(resource),children:await hasChildren(c)(resource.get_children())}
}
hasChildren = spCtx => async resource => {
LoadResource(spCtx)(resource )
await ExecQuery(spCtx)(resource )
return Promise.all(resource.get_count()>0?resource.get_objectData().$1G_0.map(await getAll(spCtx)):[])
}
c=new SP.ClientContext()
root=GetNavigationNodeRoot(c)
await LoadAndExec(c)(root)
all=await hasChildren(c)(root)
PS:看...这个想法是学习和理解FP,如果您只想说我不需要更改代码/我让它变得复杂...那真的不是重点问题。
您似乎想要检索嵌套根导航节点的所有子节点:
import * as T from 'fp-ts/Task'
// Something like this
interface NavigationNode {
readonly title: string
}
declare const getNavChildren: (node: NavigationNode) => T.Task<NavigationNode[]>
const flattenNavNode = (node: NavigationNode): T.Task<readonly NavigationNode[]> => {
// ... do something with getNavChildren to flatten the node
}
Task<A>
就是() => Promise<A>
,即返回 Promise 的函数。 这代表了异步的副作用。
你可以实现flattenNavNode
的一种方法是这样的:
import * as RA from 'fp-ts/ReadonlyArray'
import {flow, pipe} from 'fp-ts/function'
const flattenNavNode = (node: NavigationNode): T.Task<readonly NavigationNode[]> =>
pipe(
getNavChildren(node), // 1
T.chain(T.traverseArray(flattenNavNode)), // 2
T.map(flow(RA.flatten, RA.prepend(node))) // 3
)
pipe
通过多个函数传递一个值( pipe(a, ab, bc)
等价于bc(ab(a))
)。 让我们看一下这个函数管道(为简洁起见,我省略了一些readonly
):
获取导航节点的子节点。 我们现在有一个Task<NavigationNode[]>
。
这是递归部分。 我们想要得到所有深度的所有孩子,所以我们必须从原始节点中展平每个孩子。
获取孩子的孩子将是异步的并返回一些Task
,但是孩子的数组已经从第 1 步开始包裹在一个Task
中,所以我们使用chain
:
declare const chain: <A, B>(f: (a: A) => Task<B>) => (ma: Task<A>) => Task<B>
这类似于在数组上使用flatMap
。
在T.chain
我们有一个NavigationNode[]
。 您可能会考虑使用RA.map(flattenNavNode)
来获取每个节点的子节点,但这会导致Task<NavigationNode[]>[]
( Array<Task<...>>
),我们需要返回一个Task
直接来自chain
。
T.traverseArray
允许我们返回一个Task<NavigationNode[][]>
( Task<Array<...>>
):
declare const traverseArray: <A, B>(f: (a: A) => Task<B>) => (as: readonly A[]) => Task<readonly B[]>
这会并行执行Task
( T.traverseArray(f)(xs)
类似于Promise.all(xs.map(f))
),这是 fp-ts 中的默认设置。 T.traverseSeqArray
将按顺序遍历数组,这可能不是您想要的。
这是traverse
的一种专门且更高效的变体,来自Traversable
。
看起来我们快完成了——我们需要一个Task<NavigationNode[]>
,并且我们有一个Task<NavigationNode[][]>
。 但是,我们还没有将原始node
包含在这个数组中,我们还需要将结果展平。
首先,我们使用T.map
在Task
内的NavigationNode[][]
上工作。 使用flow
,这是从左到右的函数组合,我们首先RA.flatten
数组数组,然后RA.prepend
原始节点到扁平数组。
另一种解决方法是使用 (rose) Tree
。
type Forest<A> = Array<Tree<A>>
interface Tree<A> {
readonly value: A
readonly forest: Forest<A>
}
fp-ts 甚至附带了unfoldTreeM
,它允许您在 monad 的上下文中构建树:
import {pipe} from 'fp-ts/function'
import type {Tree} from 'fp-ts/Tree'
const navNodeToTree = (node: NavigationNode): T.Task<Tree<NavigationNode>> =>
unfoldTreeM(
T.Monad // use the Task monad
)(
node, // root node
// Type annotations not necessary but I added them for clarity
(node: NavigationNode): T.Task<[value: A, forest: A[]]> =>
pipe(
getChildren(node),
T.map(children => [node, children])
)
)
然后你可以把树弄平:
import * as A from 'fp-ts/Array'
import * as RA from 'fp-ts/ReadonlyArray'
import {flow} from 'fp-ts/function'
import type {Forest} from 'fp-ts/Tree'
const flattenForest: <A>(forest: Forest<A>) => readonly A[] = RA.chain(
({value, forest}) => [value, ...flattenForest(forest)]
)
// (node: NavigationNode) => T.Task<readonly NavigationNode[]>
const flattenNavNode = flow(
// make the tree
navNodeToTree,
T.map(
flow(
// make a forest out of the tree
// equivalent to x => [x]
// I’m using the Array instead of the ReadonlyArray module because
// Forest is defined as a mutable, not readonly, array for some reason
A.of,
// flatten the forest
flattenForest
)
)
)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.