Here is some code I am working with:
/** Explains why there is no value */
export interface None {
'is none because': string;
// Includes spaces to decrease the chance of confusion with a non-None member
}
/** Represents either a value or a reason for its absence. */
export type Optional<a> = a | None;
/** Function to check if a possible value actually exists */
export function isSome<a>(optValue: Optional<a>): optValue is a {
return !('is none because' in optValue);
}
/** Function to check if a possible value is absent */
export function isNone<a>(optValue: Optional<a>): optValue is None {
return 'is none because' in optValue;
}
/** Handles optional values by considering both possibilities */
export function outOfOptional<a, r>(value: Optional<a>, haveSome: (some: a) => r, haveNone: (reason: string) => r): r {
if (isNone(value)) {
return haveNone(value['is none because']);
}
else {
return haveSome(value);
}
}
However, I have encountered two issues with this setup, both related to the automatic deduction of generic types in these utility functions.
Firstly, isSome
does not seem to function as expected as a typeguard:
if (isSome(value)) {
// 'value' here still retains its original type of a | None
}
It appears that the default deduction here assumes isSome<Optional<a>>
, which is incorrect (isSome<Optional<a>>
should receive an argument of
Optional<Optional<a>></code, not what it's currently receiving). If I explicitly use <code>isSome<a>
, it works, but I would prefer not to do so.
On the other hand, isNone
does work correctly, which is why it is used in outOfOptional
: 'value' has type 'a' in the 'else' block.
In addition, consider this example usage of outOfOptional
:
export function outOfBothOptional<a, b, r>(
one: Optional<a>, another: Optional<b>,
haveBoth: (one: a, another: b) => r,
haveFirst: (one: a) => r,
haveSecond: (another: b) => r,
haveNone: (none: string) => r
): r {
return outOfOptional(
one,
haveOne => outOfOptional(
another,
haveAnother => haveBoth(haveOne, haveAnother),
() => haveFirst(haveOne)
),
() => outOfOptional(
another,
haveSecond,
haveNone
)
);
}
Both 'haveOne' and 'haveAnother' are inferred as 'a | None' instead of 'a', causing errors even though they shouldn't be. It seems that outOfOptional
is being treated as
outOfOptional<Optional<a>>
, which is incorrect for the same reasons mentioned earlier. The first argument is 'Optional<a>', not 'Optional<a> | None' or 'Optional<Optional<a>>' as implied by the deduction. When the first argument is 'Optional<a>', the generic type of outOfOptional
must be 'a', not 'Optional<a>'
Why does TypeScript see this as a valid, and even more valid, deduction? Is there a way to use 'a | None' as an optional type without needing to specify generic types explicitly in the related functions?