fp-ts: 'evolve' Breaks w/ Partial Structs

🐛 Bug report

evolve, as currently defined, exhibits strange behavior re: partial fields

Current Behavior

Given:

interface A {
  a?: 'a';
  b?: 'b';
  c?: 'c';
}

const a: A = {};
const b = F.pipe(
  a,
  evolve({
    a: fromNullable,
    b: fromNullable,
    c: fromNullable,
  })
);
runtime value of b = {}

Expected behavior

runtime value of b = {
  a: O.none,
  b: O.none,
  c: O.none
}

Reproducible example

TS Playground

Suggested solution(s)

New implementation for evolve (keeping the same type signature)

TS Playground

Additional context

Originally posted by @kalda341 here

Your environment

Which versions of fp-ts are affected by this issue? Did this work in previous versions of fp-ts?

Software Version(s)
fp-ts 2.11.5
TypeScript 4.5.4

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Reactions: 1
  • Comments: 15 (9 by maintainers)

Most upvoted comments

I think a lot of fp-ts users would agree w/ you that js’s treatment and use of undefined is frustrating - that’s why we’ve avoided it in favor of Option, and to some extent pretended it wasn’t there (thus this issue)

Oh wow I didn’t know about ‘exact optional property types’. That could even motivate the use of Option instead of undefined in these cases - a missing key really isn’t an undefined value at all. Imo that flag is proof that some developers want to treat ‘missing key’ as separate from ‘undefined’

Maybe it makes sense to conditionally use a type of Option<A | undefined> or Option<A> depending on whether that property is enabled. Something like this, maybe? Playground (excludedOptionalPropertyTypes enabled), Playground (excludedOptionalPropertyTypes disabled)