使用 FP-TS 或 Ramda 展平可能具有或不具有 children 的嵌套惰性列表?
Flattening a nested Lazy List that may or not have children with FP-TS or Ramda?
我刚刚了解了 lift 和 applicatives,在我试图理解这些结构的过程中,我正在尝试实现一个真实的用例。
我有一个惰性列表(数组),这意味着在我加载它之前我无法获取项目的数量或它们的 children。获取节点并加载它是异步的,与其嵌套 children(如果有)相同。
所以如果我有以下结构:
[{title:"test1",children:[]},{title:"test2",children:[{title:"test2_1",children:[]}]}]
对于 children 中的每一个,我不知道它们是否有 children,直到我加载节点并检查 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().G_0.map(await getAll(spCtx)):[])
}
c=new SP.ClientContext()
root=GetNavigationNodeRoot(c)
await LoadAndExec(c)(root)
all=await hasChildren(c)(root)
PS: 看...这个想法是学习和理解 FP,如果你要说的只是我不需要改变 code/I 让它变得复杂,请... 这真的不是问题的重点
您似乎想要检索嵌套根导航节点的所有子节点:
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>
,也就是说,return是一个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
s):
获取导航节点的子节点。我们现在有一个 Task<NavigationNode[]>
.
这是递归部分。我们想要得到所有深度的所有个子节点,所以我们必须从原始节点中拉平每个子节点。
获取子项的子项将是异步的并且 return 一些 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<...>>
) 我们需要 return 一个 Task
直接来自 chain
.
T.traverseArray
允许我们 return 一个 Task<NavigationNode[][]>
(Task<Array<...>>
) 而不是:
declare const traverseArray: <A, B>(f: (a: A) => Task<B>) => (as: readonly A[]) => Task<readonly B[]>
这会并行执行 Task
s(T.traverseArray(f)(xs)
类似于 Promise.all(xs.map(f))
),这是 fp-ts 中的默认设置。 T.traverseSeqArray
会依次遍历数组,这可能不是你想要的。
这是来自 Traversable
.
的 traverse
的一种特殊且更有效的变体
看起来我们快完成了——我们需要一个 Task<NavigationNode[]>
,我们有一个 Task<NavigationNode[][]>
。但是,我们并没有把原来的node
包含在这个数组中,我们还需要把结果压平
首先,我们使用T.map
来处理Task
中的NavigationNode[][]
。使用 flow
,这是从左到右的函数组合,我们首先 RA.flatten
数组的数组,然后 RA.prepend
原始节点到展平的数组。
另一种方法是使用(玫瑰)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
)
)
)
我刚刚了解了 lift 和 applicatives,在我试图理解这些结构的过程中,我正在尝试实现一个真实的用例。
我有一个惰性列表(数组),这意味着在我加载它之前我无法获取项目的数量或它们的 children。获取节点并加载它是异步的,与其嵌套 children(如果有)相同。
所以如果我有以下结构:
[{title:"test1",children:[]},{title:"test2",children:[{title:"test2_1",children:[]}]}]
对于 children 中的每一个,我不知道它们是否有 children,直到我加载节点并检查 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().G_0.map(await getAll(spCtx)):[])
}
c=new SP.ClientContext()
root=GetNavigationNodeRoot(c)
await LoadAndExec(c)(root)
all=await hasChildren(c)(root)
PS: 看...这个想法是学习和理解 FP,如果你要说的只是我不需要改变 code/I 让它变得复杂,请... 这真的不是问题的重点
您似乎想要检索嵌套根导航节点的所有子节点:
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>
,也就是说,return是一个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
s):
获取导航节点的子节点。我们现在有一个
Task<NavigationNode[]>
.这是递归部分。我们想要得到所有深度的所有个子节点,所以我们必须从原始节点中拉平每个子节点。
获取子项的子项将是异步的并且 return 一些
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<...>>
) 我们需要 return 一个Task
直接来自chain
.T.traverseArray
允许我们 return 一个Task<NavigationNode[][]>
(Task<Array<...>>
) 而不是:declare const traverseArray: <A, B>(f: (a: A) => Task<B>) => (as: readonly A[]) => Task<readonly B[]>
这会并行执行
Task
s(T.traverseArray(f)(xs)
类似于Promise.all(xs.map(f))
),这是 fp-ts 中的默认设置。T.traverseSeqArray
会依次遍历数组,这可能不是你想要的。这是来自
的Traversable
.traverse
的一种特殊且更有效的变体看起来我们快完成了——我们需要一个
Task<NavigationNode[]>
,我们有一个Task<NavigationNode[][]>
。但是,我们并没有把原来的node
包含在这个数组中,我们还需要把结果压平首先,我们使用
T.map
来处理Task
中的NavigationNode[][]
。使用flow
,这是从左到右的函数组合,我们首先RA.flatten
数组的数组,然后RA.prepend
原始节点到展平的数组。
另一种方法是使用(玫瑰)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
)
)
)