Open hanazuki opened 8 months ago
I've achieved the following with monocle + fp-ts (not fp-ts/optic yet) with the following two functions:
export const optionProp = <O, K extends keyof O>(
k: K,
): (<R>(
l: Lens.Lens<R, O>,
) => Lens.Lens<R, O.Option<Exclude<O[K], undefined>>>) =>
Lens.composeLens(
Lens.lens(
(data) =>
pipe(
data[k],
// Note: hasOwnProperty would probably be better here, but this is what I use in my codebase
O.fromPredicate(
(x): x is Exclude<O[K], undefined> => x !== undefined,
),
),
O.fold(
() => (state) => {
const cloned = Object.assign({}, state);
delete cloned[k];
return cloned;
},
(v) => (state) => ({
...state,
[k]: v,
}),
),
),
);
export const non = <T>(
eq: Eq.Eq<T>,
a: T,
): (<U>(l: Lens.Lens<U, O.Option<T>>) => Lens.Lens<U, T>) =>
Lens.composeIso(
Iso.iso(
O.getOrElse(() => a),
O.fromPredicate((x) => !eq.equals(x, a)),
),
);
The combination of these allows for pretty flexible use, and allows removal of properties, which your example doesn't. As far as I know there's no support for what you're doing built into either library.
@kalda341 Thank you for sharing your code.
I've implemented optionProp/non based on your idea and it works great.
I realized the key point is that Lens<S, Option<A>>
and Optional<S, A>
are different things, and also learned handling property removal in the TypeScript type system is a bit tricky.
export const optionProp = <S extends object, Key extends keyof S>(key: Key):
Optic.Lens<S, O.Option<Exclude<S[Key], undefined>>> =>
Optic.lens(
(s) => O.liftPredicate((a): a is Exclude<S[Key], undefined> => a !== undefined)(s[key]),
(a) => (s) =>
O.match({
onNone: () => {
const { ...s1 } = s
delete s1[key]
return s1
},
onSome: (a) => ({ ...s, [key]: a }),
})(a)
)
export const non = <T>(
a: T,
): Optic.Iso<O.Option<T>, T> =>
Optic.iso(
O.getOrElse(() => a),
O.liftPredicate(x => x !== a),
)
type Counters = { [key: string]: { counter: number } }
const c: Counters = { foo: { counter: 1 } }
const _bar = Optic.id<Counters>().compose(optionProp('bar')).compose(non({ counter: 0 })).at('counter')
š Feature request
Current Behavior
This may be just a newbie question as I'm not familiar with fp-ts or effect-ts library, or the notion of Optics in general...
I want to read and write a property in a nested object that may not exist, as illustrated in the following snippet.
Desired Behavior
Can this be achieved by composing the existing optics in the library, instead of implementing
keyWithDefault
by myself?Suggested Solution
Add an overload of the
key
function that takes a fallback value and returns a lens.Who does this impact? Who is this for?
I suppose using an Object as a dictionary/map is idiomatic in TypeScript/JavaScript and this is useful in many situations.
Describe alternatives you've considered
The proposed function can be easily implemented on the user side as shown in the code snippet. (edited the code; Actually, I happened to know this might not be as easy to implement properly as I think...)
Additional context
Your environment