gcanti / monocle-ts

Functional optics: a (partial) porting of Scala monocle
https://gcanti.github.io/monocle-ts/
MIT License
1.05k stars 52 forks source link

optional: prop for partial keys #168

Open waynevanson opened 3 years ago

waynevanson commented 3 years ago

🚀 Feature request

An export from the Optional module called partial.

This request is in progress, I will advise when it's ready.

Current Behavior

There is no way to set a value on a deeply partial key.

Desired Behavior

Suggested Solution

const partial =
  <A, P extends keyof A>(prop: P) =>
  <S>(optional: o.Optional<S, A>): o.Optional<S, NonNullable<A[P]>> => ({
    getOption: pipe(optional, o.prop(prop), o.fromNullable).getOption,
    set: (b) => (s) =>
      pipe(
        optional.getOption(s),
        O.fold(
          () => ({} as A),
          (a) => a,
        ),
        (a) => Object.assign({}, a, { [prop]: b }),
        optional.set,
      )(s),
  })

const options = pipe(
  o.id<Config.ProjectConfig>(),
  o.prop("globals"),
  partial("webdriver"),
  partial("options"),
  partial("capabilities"),
)

Who does this impact? Who is this for?

Describe alternatives you've considered

Additional context

kalda341 commented 3 years ago

This is something that would be very useful to me. Ideally I would avoid this by having a better datastructure, but this isn't always achievable. I currently get around this limitation using Lens.fromNullable, but that doesn't exist within the new experimental API.

waynevanson commented 3 years ago

I actually think it needs to be Lens<S, Option<A>>, so then the laws hold up. That way when it's None we can force it to be the value inside of Some<A>.

Optional seems to ignore stuff when it's not there, which has its own use cases, and I don't think that should be messed with.

volkanunsal commented 2 years ago

I have a different solution for this. It's a bit simpler.

import * as L from 'monocle-ts/Lens';

function defaultValue<S, T>(value: T) {
  return L.compose<S, T>(
    L.lens(
      (s) => (s ? (s as any as T) : value),
      (a) => () => a as any as S
    )
  );
}

function modDeps<T>(deps: string[]) {
  return pipe(
    L.id<T>(),
    L.prop('deps'),
    defaultValue([]),
    L.modify((s) => ...)
  );
}