If a function f: (X => Y) | undefined
is possibly undefined, and x: X
is defined, then we can use optional-chaining operator ?.
to apply f
to x
:
f?.(x) // ok, no problem if `f` is undefined
But when f: X => Y
is defined and x: X | undefined
is possibly undefined, there does not seem to be any syntax to "map" the f
over the "optional" x
:
f(?.x) // not valid syntax; Unclear what to do when `x` is undefined
I could try to implement pipeTo
to swap the order of f
and x
, and then make it work with ?.
again, as follows:
function opt<X>(x: X | undefined): { pipeTo<Y>(f: (a: X) => Y): Y } | undefined {
return typeof x === 'undefined' ? undefined : {
pipeTo<Y>(f: (a: X) => Y): Y {
return f(x)
}
}
}
which I could then use as opt(x)?.pipeTo(f)
, for example:
function square(n: number): number { return n * n }
for (const x of [42, undefined, 58]) {
console.log(opt(x)?.pipeTo(square))
}
Is there any less cumbersome standard solution for applying a certainly existing f
to a possibly undefined x
?
Clarification: "cumbersome" := anything that forces me to write down the subexpression x
twice, or to clutter the scope with meaningless helper-variables.
CodePudding user response:
This looks like array, so here's an array implementation for you
function nonNullable<T>(v: T | null | undefined): v is T {return v != null}
class OneOrZeroArray<T> extends Array<T> {
pipe<V>(mapper: (v: T) => V) {
return this.map(mapper).filter(nonNullable)
}
pop(): T | undefined {
return super.pop();
}
static from<T>(item?: T | null | undefined): OneOrZeroArray<T> {
let a = new OneOrZeroArray<T>();
if (item != null) a.push(item);
return a;
}
}
function opt<T>(v: T | null | undefined) {return OneOrZeroArray.from(v)}
function square(n: number): number { return n * n }
for (const x of [42, undefined, 58]) {
console.log(
opt(x).pipe(square).pop()
)
}
CodePudding user response:
It's like you're looking at the Nullable<T>
type operation (defined as
type Nullable<T> = T | null | undefined
) as a functor and want to perform the functor fmap
action on it, to turn a function f
of the form (x: X) => Y
into another function of the form (x?: Nullable<X>) => Nullable<Y>
. I don't think there's any built-in functionality which behaves this way, nor could I speak authoritatively about its presence in third-party libraries, but you could write it yourself easily enough:
const fmapNullable = <X, Y>(f: (x: X) => Y) =>
(x?: Nullable<X>): Nullable<Y> => x == undefined ? undefined : f(x);
and then use it:
function square(n: number): number { return n * n };
for (const x of [42, undefined, 58]) {
console.log(fmapNullable(square)(x)?.toFixed(1))
// "1764.0";
// undefined;
// "3364.0"
}
Syntactically I don't think you can get a notation as terse as the optional chaining operator (?.
); TypeScript isn't Haskell, after all. You could shorten "fmapNullable
", but you'll still be applying a function, so at best you'd have something like $_$(square)(x)
. Oh well!