函数式 Programming/Optic 概念采用部分 object 和 returns 一个 "filled in" object 使用镜头和遍历?

Functional Programming/Optic concept that takes a partial object and returns a "filled in" object using lenses and traversals?

(Edit 我正在使用 monocle-ts,但如果 monocle-ts 不可能(因为作者甚至说它只是Scala 的原始 Monocle),但如果在任何语言的另一个光学包中有一些东西,我愿意将这些想法移植到 TypeScript。)

假设我有一个助手类型 Partial<A>,它代表一条记录,该记录有一些或全部,但没有 non-members,类型为 A。 (所以 if A = { foo: number, bar: string } then Partial<A> = { foo?: number, bar?: string })(Edit 这是 Typescript 的 built-in 部分实用程序类型。)

我从

开始
interface Parent {
  xs: Child[]
}

interface PartialParent {
  partialxs: Partial<Child>[]
}

declare function fillInTheGaps(x: Partial<Child>):Child

假设我组成了一个镜头,遍历组成了(composedTraversal),让它从PartialState聚焦到partialxs,然后作为一个数组遍历它。这将是 Traversal<PartialState, Partial<Child>>.

假设我有一个 declare const fn = (x:Partial<Child>):Partial<Child> 然后我可以将 fn 应用到所有 children 和 composedTraversal.modify(fn)(partialState) 这将产生一个新的 PartialState fn 应用于所有 partialxs.

是否有一些概念可以让我“扩展”或“转换”这种遍历到不同的东西,这样我就可以组合镜头和遍历并使用 fillInTheGaps 这样我就可以传入 PartialState 并取回 State?

忽略我的语法是 TypeScript,我添加了 monocle-scala 标签,因为如果这个概念存在,我想它在 Monocle 库中,我可以将这些知识转化为我正在使用的库。

编辑 引发这个问题的问题是我在 Redux 应用程序中有一个表单输入,用户在其中输入数据但大多数都不是必需的。输入在 compile-time 处未知(它们是从 RESTful API 查询重试的)所以我不能将模型表示为

interface Model {
  foo?: string[]
  bar?: string[]
}

相反,它表示为

interface Model {
  [index:string]: string[]
}

我还可以从 RESTful 服务器获取默认模型。所以我将它们建模为 Parent(来自服务器的内容)和 Partial<Parent>(代表用户在应用程序中的输入)。

在进行一些计算之前,我需要为缺少的道具折叠默认值。这是我上面提到的 fillInTheGaps 函数。

我的愿望是通过我的代码中的类型来强制执行它的功能,并且因为我已经编写了很多光学器件,所以可以重用其中的一些。我实际上编写了一个镜头和遍历来对这些数据执行其他操作。 myLens.compose(myTraversal).modify(fn) 需要一个 Partial<State> 和 returns 一个 Partial<State> 但我希望将它们组合成一个函数,该函数接受部分和 returns 整体。

我显然可以只写 const filler: (Partial<State>):State = myLens.compose(myTraversal).modify(fillInTheGaps),然后在上面扔一个 //@ts-ignore,并且知道它会起作用,但这似乎,嗯,脆弱。

我想,你可能想要的是 Polymorphic Traversal or PTraversal<S, T, A, B>.

A Traversal<S, A>说,“如果我有一个函数A => A,我可以使用modify得到一个函数S => S,它使用原始函数修改所有的 A 出现在 S".

相比之下,一个PTraversal<S, T, A, B>说,“如果我有一个函数A => B,我可以使用modify来获得一个函数S => T”,这将转换所有SB 中的 A,生成 T.

助记,PTraversal的类型参数为:

  • S PTraversal
  • 的来源
  • T PTraversal
  • 的“修改”来源
  • A目标PTraversal
  • B PTraversal
  • 的“修改”目标

PTraversal 很有用,因为它们可以让您编写如下内容:

PTraversal<Array<A>, Array<B>, A, B>

让您在更改元素类型的同时遍历 Array


在您的具体情况下,您提到了具有两个功能:

declare function fillInTheGaps(x: Partial<Child>):Child
declare function fn(x: Partial<Child>):Partial<Child>

这些可以组合在一起产生一个函数:

function transformAndFill(x: Partial<Child>): Child {
   return fillInTheGaps(fn(x)); 
}

然后你需要写一个 PTraversal<PartialState, State, Partial<Child>, Child>.

这将支持与 Lens 组合以创建新的 PTraversal,其方式与 Traversal 的方式大致相同。

这个 应该 是可行的,我认为从你的问题来看,如果你可以将 PartialState 中的每个 Partial<Child> 转换为 Child 你应该可以做出 State.


PTraversal 存在于 Monocle(Scala 库)中,但不幸的是它看起来不像是 monocle-ts:所以你很遗憾必须编写很多光学库代码以支持这一点。