Búsqueda de sitios web

javascript - Encadenamiento opcional en los argumentos de la función


Si una función f: (X => Y) | undefined puede estar indefinida, y x: X está definida, entonces podemos usar el operador de encadenamiento opcional ?. para aplicar f a x:

f?.(x)          // ok, no problem if `f` is undefined

Pero cuando f: X => Y está definido y x: X | undefined posiblemente es indefinido, no parece haber ninguna sintaxis para "aplicar" f sobre el x "opcional":

f(?.x)          // not valid syntax; Unclear what to do when `x` is undefined

Podría intentar implementar pipeTo para intercambiar el orden de f y x, y luego hacer que funcione con ?. nuevamente, de la siguiente manera:

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)
        }
    }
}

que luego podría usar como opt(x)?.pipeTo(f), por ejemplo:

function square(n: number): number { return n * n }

for (const x of [42, undefined, 58]) {
  console.log(opt(x)?.pipeTo(square))
}

¿Existe alguna solución estándar menos engorrosa para aplicar un f ciertamente existente a un x posiblemente indefinido?


Aclaración: "cumbersome" := cualquier cosa que me obligue a escribir la subexpresión x dos veces, o a llenar el ámbito con variables auxiliares sin sentido.

Respuesta #1

Es como si estuvieras mirando la operación del tipo Nullable<T> (definida como

type Nullable<T> = T | null | undefined

) como un funtor y quieres realizar la acción de funtor fmap sobre él, para convertir una función f de la forma (x: X) => Y en otra función de la forma (x?: Nullable<X>) => Nullable<Y>. No creo que exista ninguna funcionalidad incorporada que se comporte de esta manera, ni podría hablar con autoridad sobre su presencia en bibliotecas de terceros, pero podrías escribirla tú mismo lo suficientemente fácil:

const fmapNullable = <X, Y>(f: (x: X) => Y) =>
    (x?: Nullable<X>): Nullable<Y> => x == undefined ? undefined : f(x);

y luego úsalo:

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"
}

Sintácticamente no creo que puedas obtener una notación tan concisa como el operador de encadenamiento opcional (?.); TypeScript no es Haskell, después de todo. Podrías abreviar "fmapNullable", pero aún estarías aplicando una función, así que en el mejor de los casos tendrías algo como $_$ (square)(x). ¡Qué le vamos a hacer!

Enlace de Playground al código

Respuesta #2

Esto parece un arreglo, así que aquí tienes una implementación de arreglo para ti

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()
  )
}