(Edit I'm using monocle-ts, but if it's not possible with monocle-ts (since the author even says it's just a partial port of the original Monocle for Scala) but if there is something in another optics package for any language, I'm open to porting those ideas to TypeScript.)
Suppose I have a helper type Partial<A>
such that it represents a record that has some or all, but no non-members, of type A
. (So if A = { foo: number, bar: string }
then Partial<A> = { foo?: number, bar?: string }
) (Edit This is Typescript's built-in Partial utility type.)
I begin with
interface Parent {
xs: Child[]
}
interface PartialParent {
partialxs: Partial<Child>[]
}
declare function fillInTheGaps(x: Partial<Child>):Child
Suppose I have composed a lens and traversal composed (composedTraversal
) so that it focuses on partialxs
from PartialState
and then traverses over it as an array. This would be a Traversal<PartialState, Partial<Child>>
.
Posit also that I have a declare const fn = (x:Partial<Child>):Partial<Child>
then I can apply fn
to all children with composedTraversal.modify(fn)(partialState)
which will yield a new PartialState
with fn
applied to all of the partialxs
.
Is there some concept that lets me "broaden" or "transform" this traversal into something different so that I could compose the lens and traversal and use fillInTheGaps
so I can pass in the PartialState
and get back a State
?
Ignoring that my syntax is TypeScript, I've added the monocle-scala tag because if this concept exists I imagine it's in the Monocle library and I can translate that knowledge to the library I'm using.
Edit The problem motivating this question is I have a form input in a Redux app where a user inputs data but most is not required. The INPUTs are not known at compile-time (they're retried from a RESTful API query) so I cannot represent the model as
interface Model {
foo?: string[]
bar?: string[]
}
Instead, it is represented as
interface Model {
[index:string]: string[]
}
I can also fetch a default model from the RESTful server. So I've modeled these as Parent
(what comes from the server) and Partial<Parent>
(what represents the user's input in the app).
Before doing some computations, I need to fold in defaults for the missing props. This is my fillInTheGaps
function referenced above.
The desire was to enforce what this does via types in my code and, because I have a lot of optics already written, reuse some of that. I actually have a lens and traversal written to perform other operations on this data. myLens.compose(myTraversal).modify(fn)
takes a Partial<State>
and returns a Partial<State>
but I was hoping to compose these to end up with a function that takes the partial and returns the whole.
I could obviously just write const filler: (Partial<State>):State = myLens.compose(myTraversal).modify(fillInTheGaps)
and then throw a //@ts-ignore
above it and know it would work, but that seems, uh, fragile.
I think, what you might want is a Polymorphic Traversal or PTraversal<S, T, A, B>
.
A Traversal<S, A>
says, "If I have a function A => A
, I can use modify
to obtain a function S => S
that uses the original function to modify all of the
A
s that appear in S
".
By comparison, a PTraversal<S, T, A, B>
says, "if I have a function A => B
, I can use modify
to obtain a function S => T
", this converts all of the A
s in S
to B
, producing a T
.
Mnemonically, the type parameters of PTraversal
are:
S
the source of the PTraversal
T
the "modified" source of the PTraversal
A
the target of the PTraversal
B
the "modified" target of the PTraversal
PTraversal
s are useful, because they let you write things such as the following:
PTraversal<Array<A>, Array<B>, A, B>
Letting you traverse over an Array
while changing the type of the elements.
In your specific case, you've mentioned having two functions:
declare function fillInTheGaps(x: Partial<Child>):Child
declare function fn(x: Partial<Child>):Partial<Child>
These can be composed together to produce a single function:
function transformAndFill(x: Partial<Child>): Child {
return fillInTheGaps(fn(x));
}
You'll then need to write a PTraversal<PartialState, State, Partial<Child>, Child>
.
This would support composing with a Lens
to make a new PTraversal
in much the same way that Traversal
did.
This should be doable, I think from your question that if you can convert every Partial<Child>
in PartialState
to a Child
you should be able to make a State
.
PTraversal
exists in Monocle (the Scala library), but unfortunately it doesn't look like it's made it into monocle-ts
: So you would unfortunately have to write quite a lot of optics library code in order to support this.