Home > Blockchain >  Optional chaining on function arguments
Optional chaining on function arguments

Time:10-28

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!

Playground link to code

  • Related