You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Unfortunately, because of an implementation detail in sum-types and function subtyping, nullaryOrUnaryConstructor is unsafe if it's nullary. Here's an example:
typeSum=Member<"Nullary">constSum=create<Sum>()// Error as expectedconstsum1=Sum.mk.Nullary("foo")// No error as expected because of function subtyping, however...constwithFoo=<A>(f: (x: string)=>A): A=>f("foo")constsum2=withFoo(Sum.mk.Nullary)// Type of `v` is `null`, but the value is `"foo"`const[_k,v]=serialize(sum2)
At the heart of sum-types' design is the use of proxies. This allows us to have the consumer define their type and not repeat themselves on the constructors until they need them at runtime, unlike some other libraries in this space.
The downside of this approach is that we know very little at runtime. This includes knowledge about whether or not a sum is nullary. We currently use arguments to figure out if a called constructor is nullary, and if so to supply null, which is needed at the point of serialisation.
Unfortunately, as per the above example, if a nullary constructor is called with some other value - say "foo" - then we'll think it's a non-nullary constructor and leave the string in the value position. This creates an unsafe mismatch between the types and the runtime values which is exposed when we serialise.
We need to know at runtime whether a constructor is nullary or not.
We could make all constructors take arguments explicitly, including null in the case of nullary constructors. This is simple and safe, but also quite ugly. Sum.mk.Nullary() becomes Sum.mk.Nullary(null).
We could leverage types to push consumers down two constructor objects, either mk or mkNullary (hopefully with a better name). With this approach we'd know at the point of construction which constructor object our constructor was called on, and provide a null as necessary in the latter case without ever checking arguments.
I don't know if there's some more exotic approach we could take in which nullary constructors aren't even functions. That'd be ideal but it's hard to envisage how that could work safely.
The text was updated successfully, but these errors were encountered:
Consider how subtyping applies to function parameters. This for example typechecks:
It's desirable (subjectively) to write pointfree code. Where fp-ts and sum-types meet that might look like this:
Unfortunately, because of an implementation detail in sum-types and function subtyping,
nullaryOrUnaryConstructor
is unsafe if it's nullary. Here's an example:At the heart of sum-types' design is the use of proxies. This allows us to have the consumer define their type and not repeat themselves on the constructors until they need them at runtime, unlike some other libraries in this space.
The downside of this approach is that we know very little at runtime. This includes knowledge about whether or not a sum is nullary. We currently use
arguments
to figure out if a called constructor is nullary, and if so to supplynull
, which is needed at the point of serialisation.Unfortunately, as per the above example, if a nullary constructor is called with some other value - say
"foo"
- then we'll think it's a non-nullary constructor and leave the string in the value position. This creates an unsafe mismatch between the types and the runtime values which is exposed when we serialise.We need to know at runtime whether a constructor is nullary or not.
We could make all constructors take arguments explicitly, including
null
in the case of nullary constructors. This is simple and safe, but also quite ugly.Sum.mk.Nullary()
becomesSum.mk.Nullary(null)
.We could leverage types to push consumers down two constructor objects, either
mk
ormkNullary
(hopefully with a better name). With this approach we'd know at the point of construction which constructor object our constructor was called on, and provide anull
as necessary in the latter case without ever checkingarguments
.I don't know if there's some more exotic approach we could take in which nullary constructors aren't even functions. That'd be ideal but it's hard to envisage how that could work safely.
The text was updated successfully, but these errors were encountered: