Typescript: Propuesta: Obtener el tipo de cualquier expresión con typeof

Creado en 25 ene. 2016  ·  157Comentarios  ·  Fuente: microsoft/TypeScript

Implementación de trabajo para esta propuesta

Pruébalo: npm install yortus-typescript-typeof

Ver la diferencia: aquí .

Escenario del problema

La inferencia de tipos de TypeScript cubre muy bien la mayoría de los casos. Sin embargo, quedan algunas situaciones en las que no hay una forma obvia de hacer referencia a un tipo anónimo, aunque el compilador pueda inferirlo. Algunos ejemplos:

Tengo una colección fuertemente tipada pero el tipo de elemento es anónimo/desconocido, ¿cómo puedo hacer referencia al tipo de elemento? (#3749)
// Mapping to a complex anonymous type. How to reference the element type?
var data = [1, 2, 3].map(v => ({ raw: v, square: v * v }));
function checkItem(item: typeof data[0] /* ERROR */) {...}

// A statically-typed dictionary. How to reference a property type?
var things = { 'thing-1': 'baz', 'thing-2': 42, ... };
type Thing2Type = typeof things['thing-2']; // ERROR

// A strongly-typed collection with special indexer syntax. How to reference the element type?
var nodes = document.getElementsByTagName('li');
type ItemType = typeof nodes.item(0); // ERROR
Una función devuelve un tipo local/anónimo/inaccesible, ¿cómo puedo hacer referencia a este tipo de devolución? (#4233, #6179, #6239)
// A factory function that returns an instance of a local class
function myAPIFactory($http: HttpSvc, id: number) {
    class MyAPI {
        constructor(http) {...}
        foo() {...}
        bar() {...}
        static id = id;
    }
    return new MyAPI($http);
}
function augmentAPI(api: MyAPI /* ERROR */) {...}
Tengo una interfaz con una forma anónima compleja, ¿cómo puedo referirme a los tipos de sus propiedades y subpropiedades? (#4555, #4640)
// Declare an interface DRY-ly and without introducing extra type names
interface MyInterface {
    prop1: {
        big: {
            complex: {
                anonymous: { type: {} }
            }
        }
    },

    // prop2 shares some structure with prop1
    prop2: typeof MyInterface.prop1.big.complex; // ERROR
}

¿Por qué necesitamos hacer referencia a tipos anónimos/inferidos?

Un ejemplo es declarar una función que toma un tipo anónimo como parámetro. Necesitamos hacer referencia al tipo de alguna manera en la anotación de tipo del parámetro; de lo contrario, el parámetro deberá escribirse como any .

Soluciones actuales

Declare una variable ficticia con un inicializador que deduzca el tipo deseado sin evaluar la expresión (esto es importante porque no queremos efectos secundarios en el tiempo de ejecución, solo escriba la inferencia). Por ejemplo:

let dummyReturnVal = null && someFunction(0, ''); // NB: someFunction is never called!
let ReturnType = typeof dummyReturnVal;           // Now we have a reference to the return type

Esta solución tiene algunos inconvenientes:

  • muy claramente una chapuza, poco claro para los lectores
  • contaminación del identificador (debe introducir una variable como dummyReturnValue )
  • no funciona en contextos ambientales, porque requiere una declaración imperativa

Solución propuesta

_(Nota: esta solución ya se sugirió en el n.° 4233, pero ese problema está etiquetado como 'Necesita propuesta', y hay varios otros problemas estrechamente relacionados, por lo que se trata de un problema separado)._

Permita que el operando de typeof sea una expresión arbitraria. Esto ya está permitido por typeof expr en una posición de valor como if (typeof foo() === 'string') . Pero esta propuesta también permite una expresión arbitraria cuando typeof se usa en una posición de tipo como una consulta de tipo, por ejemplo, type ElemType = typeof list[0] .

Esta propuesta ya se alinea estrechamente con la redacción actual de la especificación:

Las consultas de tipos son útiles para capturar tipos anónimos generados por varias construcciones, como literales de objetos, declaraciones de funciones y declaraciones de espacios de nombres.

Entonces, esta propuesta solo está extendiendo esa utilidad a las situaciones actualmente no atendidas como en los ejemplos anteriores.

Sintaxis y Semántica

La semántica es exactamente como ya se indicó en la especificación 4.18.6 :

El operador 'typeof' toma un operando de cualquier tipo y produce un valor del tipo primitivo String. En posiciones donde se espera un tipo, 'typeof' también se puede usar en una consulta de tipo (sección 3.8.10) para producir el tipo de una expresión.

La diferencia propuesta se relaciona con la sección 3.8.10 citada a continuación, donde se eliminaría el texto tachado y se agregaría el texto en negrita:

Una consulta de tipo consta de la palabra clave typeof seguida de una expresión. La expresión se procesa como una expresión de identificador (sección 4.3) o una expresión de acceso a la propiedad (sección 4.13) , una expresión unaria , cuyo tipo ampliado (sección 3.12) se convierte en el resultado. De manera similar a otras construcciones de tipeo estático, las consultas de tipo se borran del código JavaScript generado y no agregan sobrecarga de tiempo de ejecución.

Un punto que debe enfatizarse (que pensé que también estaba en la especificación pero no puedo encontrarlo) es que las consultas de tipo no evalúan su operando. Eso es cierto actualmente y seguiría siendo cierto para expresiones más complejas.

Esta propuesta no introduce ninguna sintaxis novedosa, solo hace que typeof sea menos restrictivo en los tipos de expresiones que puede consultar.

Ejemplos

// Mapping to a complex anonymous type. How to reference the element type?
var data = [1, 2, 3].map(v => ({ raw: v, square: v * v }));
function checkItem(item: typeof data[0]) {...} // OK: item type is {raw:number, square:number}

// A statically-typed dictionary. How to reference a property type?
var things = { 'thing-1': 'baz', 'thing-2': 42, ... };
type Thing2Type = typeof things['thing-2']; // OK: Thing2Type is number

// A strongly-typed collection with special indexer syntax. How to reference the element type?
var nodes = document.getElementsByTagName('li');
type ItemType = typeof nodes.item(0); // OK: ItemType is HTMLLIElement

// A factory function that returns an instance of a local class
function myAPIFactory($http: HttpSvc, id: number) {
    class MyAPI {
        constructor(http) {...}
        foo() {...}
        bar() {...}
        static id = id;
    }
    return new MyAPI($http);
}
type MyAPI = typeof myAPIFactory(null, 0); // OK: MyAPI is myAPIFactory's return type
function augmentAPI(api: MyAPI) {...} // OK

// Declare an interface DRY-ly and without introducing extra type names
interface MyInterface {
    prop1: {
        big: {
            complex: {
                anonymous: { type: {} }
            }
        }
    },

    // prop2 shares some structure with prop1
    prop2: typeof (<MyInterface>null).prop1.big.complex; // OK: prop2 type is {anonymous: {type: {}}}
}

Discusión de pros y contras

En contra : Mala estética sintáctica. Se han sugerido sintaxis alternativas que abordan casos individuales en #6179, #6239, #4555 y #4640.

Para : Otras sintaxis pueden verse mejor para sus casos específicos, pero todas son diferentes entre sí y cada una solo resuelve un problema específico. Esta propuesta resuelve los problemas planteados en todos esos temas, y el desarrollador no necesita aprender ninguna sintaxis nueva.

En contra : una expresión en una posición de tipo es confusa.

Para : TypeScript ya sobrecarga typeof con dos significados, como consulta de tipo, ya acepta una expresión en una posición de tipo y obtiene su tipo sin evaluarlo. Esto simplemente relaja las restricciones sobre lo que puede ser esa expresión para que pueda resolver los problemas planteados en este problema.

En contra : se podría abusar de esto para escribir consultas largas y enormes de tipo multilínea.

Para : No hay una buena razón para hacer eso en una consulta de tipo, pero hay buenas razones para permitir expresiones más complejas. Esto es básicamente la habilitación frente a la dirección de Martin Fowler.

Impacto del diseño, preguntas y trabajo adicional

Compatibilidad

Este es un cambio puramente compatible con versiones anteriores. Todo el código existente no se ve afectado. El uso de las capacidades adicionales de typeof es opcional.

Rendimiento

Mirando la diferencia, puede ver que los cambios son muy pequeños. El compilador ya conoce los tipos que se consultan, esto solo se los muestra al desarrollador. Esperaría un impacto insignificante en el rendimiento, pero no sé cómo probarlo.

Estampación

Configuré VS Code para usar una versión de TypeScript con esta propuesta implementada como su servicio de lenguaje, y todo el resaltado de sintaxis y la inteligencia son impecables hasta donde lo he probado.

Pueden aparecer expresiones complejas en archivos .d.ts

El operando typeof podría ser cualquier expresión, incluido un IIFE, o una expresión de clase completa con cuerpos de métodos, etc. No puedo pensar en ninguna razón para hacer eso, simplemente ya no es un error, incluso dentro un archivo .d.ts ( typeof se puede usar, y es útil, en contextos ambientales). Entonces, una consecuencia de esta propuesta es que "las declaraciones no pueden aparecer en contextos ambientales" ya no es estrictamente cierto.

Los tipos recursivos se manejan de manera robusta.

El compilador parece tener ya toda la lógica necesaria para lidiar con cosas como esta:

function foo<X,Y>(x: X, y: Y) {
    var result: typeof foo(x, y); // ERROR: 'result' is referenced in its own type annotation
    return result;
}
Puede consultar el tipo de retorno de una función sobrecargada

No es ambiguo; elige la sobrecarga que coincide con la expresión de la consulta:

declare function foo(a: boolean): string;
declare function foo(a: number): any[];
type P = typeof foo(0);    // P is any[]
type Q = typeof foo(true); // Q is string
Suggestion Too Complex

Comentario más útil

Abrió un PR en #17961.

Todos 157 comentarios

Para cualquiera que quiera una forma rápida de jugar con esto en VS Code con intellisense, etc., aquí hay un repositorio de juegos .

tipo P = tipo de foo (0); // P es cualquiera[]
tipo Q = tipo de foo (verdadero); // Q es una cadena

Creo que usar tipos como argumento en lugar de valores es una sintaxis más válida.

type P = typeof foo(number);    // P is any[]
type Q = typeof foo(boolean); // Q is string

Es más claro que no se llama a la función, porque proporciona tipos y no valores como argumentos. El otro punto es que es menos ambiguo. Algunas personas usarán typeof foo(false) , mientras que otras usarán typeof foo(true) . Si tiene tipos como argumentos, las personas solo pueden escribir typeof foo(boolean) .

@tinganho exactamente!
Aunque todavía podemos escribir typeof foo("abc") con #5185
aquí "abc" es el tipo de cadena singleton

@tinganho He estado pensando en su idea y veo algunas cosas que prefiero de esta propuesta, y otras cosas que prefiero de su sugerencia. Su sugerencia es buena por las razones que dio (una sintaxis más simple, más clara, menos ambigua, se parece menos a una llamada de función). Lo que prefiero de mi propuesta es que no introduce ninguna sintaxis novedosa, por lo que no agrega ninguna complicación al analizador/verificador, y también admite escenarios más complejos en los que no tiene nombres de tipos simples para los argumentos.

Estaba pensando, ¿qué pasaría si hubiera una forma muy abreviada de escribir algo como su sintaxis foo(number) pero usando la mecánica de análisis de expresiones existente? Entonces, como experimento, introduje una nueva expresión: _unary as_. Simplemente puede escribir as T y esa es la abreviatura de (null as T) . Básicamente estás diciendo, _'No me importa el valor, pero quiero que la expresión tenga el tipo X'_.

Con este cambio (que implementé en el repositorio de juegos ), puede escribir algo mucho más cercano a la sintaxis sugerida, pero aún se analiza como una expresión ordinaria:

type P = typeof foo(as number);    // P is any[]
type Q = typeof foo(as boolean); // Q is string

let prop2: typeof (as MyInterface).prop1.big.complex; // prop2 type is {anonymous: {type: {}}}

Esto fue solo un experimento rápido. Una sintaxis equivalente podría ser (pero no he implementado esto):

type P = typeof foo(<number>);    // P is any[]
type Q = typeof foo(<boolean>); // Q is string

let prop2: typeof (<MyInterface>).prop1.big.complex; // prop2 type is {anonymous: {type: {}}}

La segunda sintaxis podría denominarse _aserción de tipo nulo_, con la expresión <T> como abreviatura de (<T> null) .

@yortus , la semana pasada estuvimos hablando de esta propuesta. perdon por no publicar antes. el consenso fue 1. Tenemos el problema de no poder hacer referencia a algunos tipos, por ejemplo, el retorno de una función o el tipo de instancia de una expresión de clase. y 2. agregar expresiones en una posición de tipo no es algo con lo que nos sintamos cómodos.

La propuesta de @tinganho también fue una de las que hablamos. Creo que es más apetecible, aunque probablemente sería más complicado de implementar. Agregar un nuevo operador unario o usar la sintaxis de conversión no es tan elegante como solo usar los nombres de tipo.

Discutido durante bastante tiempo en el slog de hoy. "Aceptar relaciones públicas" aquí es "Aceptar relaciones públicas suponiendo que la implementación no resulte demasiado loca"

La propuesta de @tinganho se ve bastante bien (en relación con las otras opciones, al menos) y nos gustaría ver una RP tentativa que implemente esto.

Lo complicado es que no queremos tener una ruta de código completamente separada para resolver el tipo de retorno de f(number) vs f(0) , pero el algoritmo de resolución de sobrecarga está totalmente integrado con la suposición de que está trabajando con un conjunto de _expresiones_ en lugar de un conjunto de _tipos_. Pero creemos que con un pequeño truco esto debería ser sencillo.

El plan básico de ataque sería:

  • Expanda la gramática al analizar typeof para permitir cosas que parecen llamadas a funciones, acceso a propiedades y acceso a propiedades indexadas
  • Al analizar los argumentos de una llamada de función en una consulta de tipo (llamémoslos _psuedocalls_ / _psuedoarguments_), use la función parseType . Esto creará un TypeNode , pero establecerá una marca en el nodo que indica que fue un tipo analizado en el contexto de una consulta de tipo
  • En el comprobador, checkExpression comprueba esta bandera y llama a getTypeFromTypeNode en lugar del procesamiento de expresión normal

@mhegazy , @RyanCavanaugh no estoy seguro de cuántos casos críticos discutió el equipo, así que ¿puedo mencionar algunos aquí para aclararlos? He enumerado un montón de ejemplos a continuación y he comentado cada uno con lo que creo que debería ser el resultado de la operación typeof , con signos de interrogación en los casos cuestionables.


notación de indizador
var data = [1, 2, 3];
const LOBOUND = 0;
type Elem1 = typeof data[0];        // number
type Elem2 = typeof data[999999];   // number or ERROR?
type Elem3 = typeof data[1+2];      // ERROR or number?
type Elem4 = typeof data[LOBOUND];  // ERROR or number?

var tuple: [number, string] = [123, 'abc'];
type Elem4 = typeof tuple[0];       // number or number|string?
type Elem5 = typeof tuple[1];       // string or number|string?
type Elem6 = typeof tuple[999999];  // number|string or ERROR?

const ABC: 'a-b-c' = 'a-b-c';
let dict = { 'a-b-c': 123, 'd-e-f': true };
type Prop1 = typeof dict['a-b-c'];  // number
type Prop2 = typeof dict['d-e-f'];  // boolean
type Prop3 = typeof dict[ABC];      // ERROR or number or any?

Notación de tipo de retorno de función
// A simple function
declare function f1(n: number): string[];
type Ret1 = typeof f1(number);      // string[]
type Ret2 = typeof f1(0);           // ERROR or string[]?

// An asynchronous function that either accepts a callback or returns a Promise
declare function f2(n: number): Promise<string[]>;
declare function f2(n: number, cb: (err?: any, result?: string[]) => void): void;
type Ret3 = typeof f2(number);                    // Promise<string[]>
type Ret4 = typeof f2(number, any);               // void
type Ret5 = typeof f2(number, Function);          // ERROR: Function not assignable to callback
type Ret6 = typeof f2(number, (err: any, result: string[]) => void); // void
type Ret7 = typeof f2(number, (...args) => any);  // void

// A special function-like object
interface Receiver {
    (data: string[]): void;
    transmogrify(): number[];
}
declare function f3(n: number, receiver: Receiver): Promise<void>;
declare function f3(n: number, callback: (err?: any, result?: string[]) => void): void;
type Ret8 = typeof f3(number, Receiver);           // Promise<void>
type Ret9 = typeof f3(number, any);                // ambiguous? or picks first overload?
type Ret10 = typeof f3(number, Function);          // ERROR
type Ret11 = typeof f3(number, (...args) => any);  // void since not assignable to Receiver

// A function with parameter destructuring
interface CartesianCoordinate {/***/}
interface PolarCoordinate {/***/}
declare function f4({ x: number, y: number }): CartesianCoordinate;
declare function f4({ r: number, t: number }): PolarCoordinate;
type Ret12 = typeof f4(any);        // ambiguous? or picks first overload?
type Ret13 = typeof f4({x;y});      // CartesianCoordinate
type Ret14 = typeof f4({r;t});      // PolarCoordinate
type Ret15 = typeof f4({x;r;t;y});  // ambiguous? or picks first overload?

// Type-ception: is there anything wrong with typeof-in-typeof?
declare function f5(n: number, receiver: Receiver): Promise<void>;
declare function f5(n: number, callback: (err?: any, result?: string[]) => void): void;
function myCallback(err, result) {/***/}
var myReceiver: Receiver;
type Ret16 = typeof f5(number, typeof myReceiver); // Promise<void>
type Ret17 = typeof f5(number, typeof myCallback); // void


Extraer parte de una clase/tipo de interfaz

Es decir, los typeof (<MyInterface> null).prop1.big.complex; ejemplos anteriores.

Entiendo de los comentarios anteriores que esto está fuera del alcance y no será compatible. ¿Es eso correcto?

notación de indizador
var data = [1, 2, 3];
const LOBOUND = 0;
type Elem1 = typeof data[0];        // number
type Elem2 = typeof data[999999];   // number
type Elem3 = typeof data[1+2];      // ERROR, only literals allowed here
type Elem4 = typeof data[LOBOUND];  // number when const resolution is done, otherwise any

var tuple: [number, string] = [123, 'abc'];
type Elem4 = typeof tuple[0];       // number
type Elem5 = typeof tuple[1];       // string 
type Elem6 = typeof tuple[999999];  // number|string

const ABC: 'a-b-c' = 'a-b-c';
let dict = { 'a-b-c': 123, 'd-e-f': true };
type Prop1 = typeof dict['a-b-c'];  // number
type Prop2 = typeof dict['d-e-f'];  // boolean
type Prop3 = typeof dict[ABC];      // number when const resolution work is done, otherwise any

Notación de tipo de retorno de función
// A simple function
declare function f1(n: number): string[];
type Ret1 = typeof f1(number);      // string[]
type Ret2 = typeof f1(0);           // error, 0 is not a type

// An asynchronous function that either accepts a callback or returns a Promise
declare function f2(n: number): Promise<string[]>;
declare function f2(n: number, cb: (err?: any, result?: string[]) => void): void;
type Ret3 = typeof f2(number);                    // Promise<string[]>
type Ret4 = typeof f2(number, any);               // void
type Ret5 = typeof f2(number, Function);          // ERROR: Function not assignable to callback
type Ret6 = typeof f2(number, (err: any, result: string[]) => void); // void
type Ret7 = typeof f2(number, (...args) => any);  // void

// A special function-like object
interface Receiver {
    (data: string[]): void;
    transmogrify(): number[];
}
declare function f3(n: number, receiver: Receiver): Promise<void>;
declare function f3(n: number, callback: (err?: any, result?: string[]) => void): void;
type Ret8 = typeof f3(number, Receiver);           // Promise<void>
type Ret9 = typeof f3(number, any);                // picks first overload
type Ret10 = typeof f3(number, Function);          // ERROR
type Ret11 = typeof f3(number, (...args) => any);  // void since not assignable to Receiver

// A function with parameter destructuring
interface CartesianCoordinate {/***/}
interface PolarCoordinate {/***/}
declare function f4({ x: number, y: number }): CartesianCoordinate;
declare function f4({ r: number, t: number }): PolarCoordinate;
type Ret12 = typeof f4(any);        // picks first overload
type Ret13 = typeof f4({x;y});      // CartesianCoordinate
type Ret14 = typeof f4({r;t});      // PolarCoordinate
type Ret15 = typeof f4({x;r;t;y});  // picks first overload

// Type-ception: is there anything wrong with typeof-in-typeof?
declare function f5(n: number, receiver: Receiver): Promise<void>;
declare function f5(n: number, callback: (err?: any, result?: string[]) => void): void;
function myCallback(err, result) {/***/}
var myReceiver: Receiver;
type Ret16 = typeof f5(number, typeof myReceiver); // Promise<void>
type Ret17 = typeof f5(number, typeof myCallback); // void

Tengo curiosidad por lo que sucede aquí:

const number = "number";
type Ret3 = typeof f2(number); // What happens here?

@SaschaNaz buena pregunta. Una situación parecida:

class MyClass { foo; bar; }
declare function f(inst: MyClass): number;
type Ret = typeof f(MyClass);     // number (presumably)

En este caso, tiene sentido en typeof f(MyClass) que el MyClass _type_ se considere antes que el MyClass _value_ (es decir, la función constructora). El primero lleva a Ret = number , el segundo llevaría a algo como error: MyClass is not a type .

¿Se aplicaría la misma lógica a un nombre que hiciera referencia tanto a un _tipo_ como a un _valor constante_? En su ejemplo, eso significaría que el tipo number siempre tendría prioridad sobre el valor constante number . ¿Alguna idea @RyanCavanaugh?

Correcto, resolveríamos esto bajo la semántica habitual de una expresión de tipo (como si hubieras escrito var x: [whatever] ). Entonces podría tener typeof f(MyClass) refiriéndose a invocar f con el lado de la instancia, y typeof f(typeof MyClass) refiriéndose a invocar f con la función constructora.

Entonces, el ejemplo de @SaschaNaz se refiere sin ambigüedades a number como un _tipo_, no como el _valor constante_, ¿verdad?

const number = "number";
type Ret3 = typeof f2(number); // Promise<string[]>

@RyanCavanaugh , ¿puede confirmar que el tercer grupo de casos de uso está fuera del alcance? por ejemplo, del OP:

// Declare an interface DRY-ly and without introducing extra type names
interface MyInterface {
    prop1: {
        big: {
            complex: {
                anonymous: { type: {} }
            }
        }
    },

    // prop2 shares some structure with prop1
    prop2: typeof (<MyInterface>null).prop1.big.complex; // OK: prop2 type is {anonymous: {type: {}}}
}

Este caso de uso (con cualquier sintaxis) no será compatible en este momento, ¿no es así?

Creo que esto debería cubrirse permitiendo expresiones this .

   prop2: typeof this.prop1.big.complex;

Creo que lo const debería resolverse con otro typeof .

type Ret3 = typeof f2(typeof number); // typeof number is string so error here

... mientras que esto bloquearía typeof data[LOBOUND] .

@mhegazy esa es una gran idea re typeof this . Me acabo de dar cuenta de que esto ya funciona en la implementación bifurcada que hice para esta propuesta. Bueno, funciona para las clases. Para las interfaces no hay error, pero el tipo this siempre se infiere como any . Salida actual del impl bifurcado:

class MyClass {
    prop1: {
        big: {
            complex: {
                anonymous: { type: {} }
            }
        }
    };

    prop2: typeof this.prop1.big.complex; // prop2 type is {anonymous: {type: {}}}
}

interface MyInterface {
    prop1: {
        big: {
            complex: {
                anonymous: { type: {} }
            }
        }
    };

    prop2: typeof this.prop1.big.complex; // prop2 type is any
}

¿Es posible inferir this dentro de las declaraciones de la interfaz o esta función seguirá estando limitada a las clases?

Quiero hacer dos puntos sobre # 6179 y Angular.

// A factory function that returns an instance of a local class
function myAPIFactory($http: HttpSvc, id: number) {
    class MyAPI {
        constructor(token: string) {...}
        foo() {...}
        bar() {...}
        static id = id;
    }
    return MyAPI;
}
type MyAPIConstructor = typeof myAPIFactory(null, 0); // OK: MyAPI is myAPIFactory's return type
function augmentAPI(api: MyAPIConstructor) {...} // OK
  1. El número de parámetros puede ser grande. Digamos 15 parámetros. Al mismo tiempo, no hay sobrecargas, y solo para las sobrecargas se necesitan los parámetros en typeof . Entonces, para este caso, ¿podemos pensar en una sintaxis como la siguiente?

    type MyAPI = typeof myAPIFactory(...);
    
  2. La función de fábrica no suele asignarse a una variable global propia. Se utiliza una expresión de función:

    angular.module('app').factory('MyAPI', function($http: HttpSvc, id: number) { /*...*/ });
    

    Eso es lo que suele parecer. Como se puede ver, typeof no se puede usar aquí en absoluto.

@thron0 mi intuición es que algo que tiene más de unos pocos parámetros pero _también_ devuelve un tipo anónimo va a ser muy raro. ¿Qué piensas?

Se volverá raro cuando Angular 1 se vuelva raro, no antes.

Básicamente, lo que describiste es lo que es una _fábrica_ angular típica:

una cosa que tiene más de unos pocos parámetros pero también devuelve un tipo anónimo

Es una función que toma dependencias como sus parámetros y crea una instancia de un _servicio_. Se podría pensar que una gran cantidad de parámetros puede ser una molestia, pero la cuestión es que esas fábricas nunca se llaman directamente. El contenedor DI los llama. Se llama a la fábrica cuando algo requiere su valor de retorno (el servicio) como una dependencia. Y se llama solo una vez ya que los servicios siempre son únicos en Angular. Sin embargo, un servicio puede ser cualquier cosa, por lo que si necesitamos un comportamiento que no sea singleton, la fábrica puede devolver un constructor (o una función de fábrica). Al igual que en este ejemplo de código:

angular.module('app').factory('MyAPI', function($http: HttpSvc, id: number) {
    class MyAPI {
        constructor(token: string) {...}
        foo() {...}
        bar() {...}
        static id = id;
    }
    return MyAPI;
});

Así es como se ve mi solución actual para esta situación:

class MyAPI {
    // dependencies (1)
    protected $http: HttpSvc;
    protected id: number;

    constructor(token: string) {...}
    foo() {...}
    bar() {...}
    // Static properties cannot be used with this approach because:
    // 1) this class is a global variable so it can be shared by different 
    // instances of the DI container,
    // 2) the id property isn't available here as it's initialized in the subclass
    //// static id0 = id;
}

angular.module('app').factory('MyAPI', function($http: HttpSvc, id: number) { // (2)
    return class extends MyAPI {
        $http = $http; // (3)
        id = id;
    };
});

// this type is needed for type annotations in the services that require MyAPI as a dependency
type MyAPIConstructor = typeof MyAPI;

angular.module('app').factory('someOtherService', function(MyAPI: MyAPIConstructor) {
    var api = new MyAPI('qwerty');
    /* ... */
});

Como puedes ver, es totalmente feo y doloroso. Tengo que enumerar las dependencias tres veces. Si tengo más dependencias, a veces es más sencillo escribir la clase de la forma habitual (dentro de la función de fábrica) y declarar una interfaz para sus consumidores.

Hola, quiero proponer dejar intacta la sintaxis/semántica de typeof y, en su lugar, implementar un álgebra de acceso de tipo simple:

Imaginemos la extensión de sintaxis de tipos:

type          ::=  ... |  literalType | type typeAccess | guarded
guarded    ::= "type" id
literalType   ::= stringLiteral | numberLiteral | symLiteral | booleanLiteral 
typeAccess    ::= typeField | typeSubscript | typeCall
typeField     ::= "." id
typeSubscript ::= "[" type "]"
typeCall      ::= "(" [type {"," type}] ")"

Cada identificador en el alcance se puede vincular simultáneamente a las dos entidades:

  • tipo de tiempo de compilación
  • valor de tiempo de ejecución

el type guard extrae solo el primero. Por lo tanto

class A {}
var a: A;

es equivalente a

class A {}
var a: type A;

en este caso si tuviéramos una clase

class ABC {
    a: number;
    b(x: number): string;
    c:string[];
    d:[number, string];
}

entonces podríamos escribir

var a: (type ABC).a; // number
var b: (type ABC).b(number); // string
var c: (type ABC).c[number]; // string
var d: (type ABC).c[0]; // number

También cubre la fusión de entidades de _tipo_ y entidades de _valor_ bajo el mismo identificador en el ámbito léxico.

interface SAME {
    x: number;
}
namespace SAME: {
    export type x = boolean;
    export var x: string;
}

var a: SAME.x   // boolean
var b: (type SAME).x  // number
var b: (typeof SAME).x  // string

@RyanCavanaugh , @sandersn , ¿puede examinar esta idea?

¿Cómo capturas el tipo de expresión con tu propuesta?

El jueves 19 de mayo de 2016 a las 12:26 Anatoly Ressin, [email protected] escribió:

Hola, quiero proponer dejar intacto el tipo de sintaxis/semántica y
en su lugar, implemente un álgebra de acceso de tipo simple:

Imaginemos la extensión de sintaxis de tipos:

tipo ::= ... | tipoliteral | tipo tipoAcceso | guardado
vigilado ::= id de "tipo"
tipoLiteral ::= cadenaLiteral | numeroLiteral | literalsim | literal booleano
typeAccess ::= typeField | tipoSubíndice | tipoLlamar
campotipo ::= "." identificación
typeSubscript ::= "[" type "]"
typeCall ::= "(" [tipo {"," tipo}] ")"

Cada identificador en el alcance se puede vincular simultáneamente a los dos
entidades:

  • tipo de tiempo de compilación
  • valor de tiempo de ejecución

el _type_ guard extrae solo el primero. Por lo tanto

clase A {}
un: un

es equivalente a

clase A {}
R: tipo A

en este caso si tuviéramos una clase

clase ABC {
un número;
b(x: número): cadena;
cuerda C[];
d:[número, cadena];
}

entonces podríamos escribir

var a: (tipo ABC).a; // numerovar b: (escriba ABC).b(numero); // stringvar c: (tipo ABC).c[número]; // stringvar d: (escriba ABC).c[0]; // número

También cubre la fusión de entidades de _tipo_ y entidades de _valor_ bajo el
mismo identificador en ámbito léxico.

interfaz MISMO {
x: número;
}espacio de nombres MISMO: {
tipo de exportación x = booleano;
exportar var x: cadena;
}
var a: IGUAL.x // booleanvar b: (tipo IGUAL).x // númerovar b: (tipo de IGUAL).x // cadena

@RyanCavanaugh https://github.com/RyanCavanaugh , @sandersn
https://github.com/sandersn ¿puedes examinar esta idea?


Estás recibiendo esto porque estás suscrito a este hilo.
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/6606#issuecomment-220378019

Su ejemplo me recuerda que un tipo de expresión punteada y tipo de propiedad tipo #1295 es una propuesta bastante similar. Excepto que uno es más estático y el otro más dinámico.

También me gusta la idea de omitir la palabra clave typeof.

   prop2: this.prop1.big.complex;

@mhegazy en su ejemplo, está usando typeof en un this-expression :

prop2: typeof this.prop1.big.complex;

Ya que podemos hacer esto hoy:

someProp: this;

En mi opinión, una extrapolación de la sintaxis anterior para más propiedades punteadas es:

someProp: this.prop1.prop2.prop3;

Y no:

someProp: typeof this.prop1.prop2.prop3;

Y para continuar con la extrapolación, ¿por qué no omitir typeof por completo?

someProps: foo(number)

¿Por qué no omitir typeof todos juntos?

Una de las razones por las que puedo pensar es que sería ambiguo si hace referencia al lado de la instancia de una clase o al lado estático:

class C {}

// Does 'x' have the instance type of 'C',
// or does it have the shape of its constructor?
let x: C;

También me gustaría hacer referencia al tipo de una propiedad en una interfaz dentro de otra interfaz. Publiqué mi ejemplo como #10588, que ahora está cerrado a favor de este ticket.

Sería bueno escribir algo así:

interface Foo {
  bar: string;
}

interface Box<T> {
  container: T;
}

interface FooWithBoxedProps {
  bar: Box<Foo.bar>; // OR: bar: Box<typeof Foo.bar>;
}

que simplemente se traduciría a esto:

interface Foo {
  bar: string;
}

interface Box<T> {
  container: T;
}

interface FooWithBoxedProps {
  bar: Box<string>;
}

Bueno, algunos de los escenarios mencionados anteriormente ahora se pueden expresar con _tipos de acceso indexados_ (#11929). Simplemente combine esta función con la expresión estándar typeof y obtendrá algo como esto:

interface MyInterface {
    prop1: {
        big: {
            complex: {
                anonymous: { type: {} }
            }
        }
    },

    // prop2 shares some structure with prop1
    prop2: MyInterface["prop1"]["big"]["complex"];
}

¡y funciona! (actualmente en mecanografiado@siguiente)

Creo que lo siguiente natural sería tener algo similar pero para las funciones, algunos "tipos de llamadas a funciones" como este:

interface FunctionLike{
    (arg1 : number, arg2 : string) : {a : number; b : string;}
}

type ReturnType = FunctionLike(number, string); // {a : number; b : string;} 

entonces podríamos combinarlo naturalmente con el operador typeof

interface BigThing {
    testString: string;
    testNumber: number;
    testBoolean: boolean;
}

function getSmallThing(thing:BigThing){
    return {
        testString : thing.testString,
        testBoolean : thing.testBoolean
    }
}

type SmallThing = typeof getSmallThing(BigThing) // {testString: string; testBoolean : boolean}

Y parece que esta es la sintaxis que ya se propuso al principio de este hilo (@tinganho). :)
Pero sería una característica más general que se compone muy bien con el operador typeof actual, al igual que los tipos de acceso indexados_ ahora.

Sin embargo, aún quedan algunas preguntas/dudas:

  • ¿Esta función manejará otros escenarios como los _tipos de acceso indexados_? Si no, ¿vale la pena implementarlo?
  • Supongo que en la mayoría de los casos sería bueno no necesitar especificar ningún tipo de argumento, ya que el escenario más común es "Solo quiero obtener el tipo de esta función que no tiene otras sobrecargas". un nuevo operador específico para esto estaría bien (por ejemplo typeofreturn o returnof )

Parece que esto es técnicamente factible. Puede requerir la refactorización de la inferencia de argumento de tipo/resolución de sobrecarga para que no necesite resolver los argumentos. No creo que los genéricos representen demasiado problema adicional. Una pregunta que debe hacerse es qué sucede si ninguna sobrecarga coincide con la llamada.

Creo que no es obvio si esta característica es deseable. Si una función devuelve un tipo anónimo muy elaborado, ¿no sería más útil como autor nombrar el tipo, para que no tenga que ser referenciado de esta manera? Admito que esto se basa solo en una preferencia estilística. Supongo que no estoy seguro de cuán conveniente será usar esto en la práctica. Sin embargo, creo que está bien para el acceso a los elementos.

tipo P = tipo de foo (0); // P es cualquiera[]
tipo Q = tipo de foo (verdadero); // Q es una cadena

Escriba esta discusión sobre permitir que los parámetros sean referidos por sus tipos, sugeriría permitir lo mismo para las funciones reales también.
Contexto: me gustaría escribir una función para map sobre objetos:
function map<T, F extends Function>(fn: F, obj: T): { [P in keyof T]: typeof F(T[P]) }
La razón por la que me gustaría poder referirme a la función por su tipo F (aparte de solo su nombre fn ) sería para casos en los que su nombre podría no estar disponible, por ejemplo, I' Me gustaría poder decir type MapFn<T, F extends Function> = { [P in keyof T]: typeof F(T[P]) } .

Algo que probablemente sería mucho más fácil de implementar (y aún muy útil) sería:

const myFunc = () => ({ x: 10 });
type myType = returnof myFunc;    // { x: number; }

En teoría, también podrían encadenarse si quisiera obtener el tipo algunos niveles de profundidad. ¿Alguna idea?

Editar: Me acabo de dar cuenta de que @mpawelski lo mencionó anteriormente 😄

@dehli Sin embargo, eso no funcionaría para funciones sobrecargadas. O funciones genéricas donde se usa un parámetro de tipo en el tipo de retorno.

@JsonFreeman ¿No podrían las funciones sobrecargadas solo OR los diversos tipos de devolución? ¿Y las funciones genéricas podrían requerir que especifique el tipo?

Sería posible, pero no estoy seguro de cuán útil sería. Parece que la gente quiere algo más sofisticado.

Realmente espero que esto se implemente pronto, ya que es un verdadero dolor.

Solución alterna

Como la última solución ya no funciona, actualmente uso esto:

// 0 argument function
export default function returnof<T>(fn: () => T): T;

// 1 argument function
// If no ambiguity simply infer the return type
export default function returnof<A, T>(fn: (a: A) => T): T;

// 1 argument function, with possible overload for the argument type.
// Explicitly set the type and use the correct overload
export default function returnof<A, T>(fn: (a: A) => T, a: A): T;

// ...

No necesita especificar argumentos si no hay sobrecarga de su función.

const hello = (arg: number) => 'World'

const helloReturnValue = returnof(hello)
type helloReturnType = typeof helloReturnValue // string

Funciones sobrecargadas

declare function hello(): void;
declare function hello(a: number): number;

const helloReturnValue = returnof(hello)
type helloReturnType = typeof helloReturnValue // void

const helloReturnValue = returnof(hello, 42)
type helloReturnType = typeof helloReturnValue // number

Todas las definiciones están aquí:
https://github.com/kube/returnof/blob/master/lib/returnof.d.ts

Acabo de crear un pequeño paquete NPM para empaquetarlo fácilmente sin contaminar su proyecto.

Si se agregan genéricos opcionales como se propone en https://github.com/Microsoft/TypeScript/issues/2175 , permitirá una solución alternativa simple solo declarativa:

type Return<T extends () => S, S> = S

Y utilízalo así:

type helloReturnType = Return<typeof hello>

@mhegazy @JsonFreeman ¿ Algún plan para esta característica?

Hola a todos,
Tengo una posible solución alternativa a este problema (lo siento si ya se sugirió y me la perdí): la propuse en el número 13949. No sabía sobre el operador typeof en ese momento, pero creo que mi solución es más general. Básicamente, introduzca una nueva sintaxis, algo así como =MyType , que se puede usar donde quiera que use MyType , pero en lugar de declarar que el objeto es del tipo MyType , crea un tipo denominado MyType del tipo inferido del objeto. Por ejemplo, así es como lo usaría para crear un componente Vue.

function createData(){
  return <=MyData>{
    dataProp1: <string>null
  }
}

function method1(){
  let self: MyComponent = this;
  console.log(self.dataProp1);
  self.method2();
}

function method2(){
  let self: MyComponent = this;
  //dostuff
}

type MyComponent = MyData & MyMethods;

let componentOptions = {
  data: createData,
  methods: <=MyMethods>{method1, method2}
}
//todo: register component...

Tenga en cuenta que la función createData también podría escribirse

function createData(): =MyData {
  return {
    dataProp1: <string>null
  }
}

Encontré un trabajo realmente divertido que se generaliza a cualquier expresión:

const varWithRightType = (false as true) && some.deep.access()
type MyType = typeof varWithRightType;

EDITAR: DEJEN DE REÍRSE DE MÍ ESTO ES SERIE ESCRITURA A MÁQUINA

@johnfn esto parece divertido para casos simples :), pero si su función requiere algunos parámetros, debe realizar un trabajo adicional como este:

const stateProps = (false as true) && mapStateToProps({} as any);

Estoy usando un trabajo diferente, que es especialmente útil para React & Redux, cuando trato de usar la inferencia de tipo para obtener el tipo de accesorios de la función mapStateToProps . Entonces ya no hay necesidad de declarar y mantener manualmente la interfaz de Props inyectados por Redux connect que es tedioso de mantener y propenso a errores.
Esta solución también manejará muy bien cualquier firma de función dada para otros casos de uso.

Ejemplo de caso de uso del operador typeof "React & Redux":

El siguiente ejemplo intenta mostrar un caso de uso común de proyecto real para el uso del operador typeof para derivar un tipo de una declaración de función que sería extremadamente útil si se agregara a TypeScript.

import { returntypeof } from 'react-redux-typescript';
import { RootState } from '../../store';
...

const mapStateToProps = (state: RootState) => ({
  currencies: CurrencyRatesSelectors.getCurrencies(state),
  currencyConverter: storeState.currencyConverter,
});

const stateProps = returntypeof(mapStateToProps); 
type Props = typeof stateProps & typeof dispatchToProps;
type State = {};

class CurrencyConverterContainer extends React.Component<Props, State> {
...

Fuente: https://github.com/piotrwitek/react-redux-typescript-patterns#react-connected-components

¡La solución de @kube y su paquete https://github.com/kube/returnof parecen funcionar como se esperaba! 👍

Hm. Desearía que returnof ayudara a escribir map() basado en tuplas desde un archivo .d.ts , pero dada su dependencia del lenguaje de expresión ( const no se puede usar en contextos ambientales ), me temo que mi caso de uso con eso sería al menos más complicado.

Editar: parece que los genéricos opcionales ya se han fusionado , lo que significa que la versión de lenguaje de tipo declarativo de @kube ( type Return<T extends () => S, S> = S -> type helloReturnType = Return<typeof hello> ) sería viable. :D

Corrección: no, el soporte fusionado para valores genéricos predeterminados no parece permitir especificar parte de los genéricos mientras permite que otros recurran a sus valores inferidos; Return<typeof hello> solo arroja el error Generic type 'Return' requires 2 type argument(s). .

@ tycho01 Sí, me decepcionó que no resolviera el problema.

Ya abrí un problema relacionado con la inferencia de tipos genéricos opcionales :

14400

Por lo general, puede obtener el tipo de retorno de una función a través de una combinación de una función ambiental ficticia y una inferencia de tipo:

ambiente function returnTypeOf<RT>(fn:(...rest:any[])=>RT):RT {return void 0};
localmente var r = returnTypeOf(someFunction); obtiene valor undefined

Pero si luego desea reutilizar ese tipo de retorno, debe capturarlo... así que estamos de regreso donde comenzamos:

type RT = typeof r;

Sería mucho más fácil si inicialmente extendiéramos el concepto de typeof para permitir returntypeof o incluso mejor inferir este uso de typeof fn(a,b,c,...) , que luego podría capturar diferentes tipos de retorno de firma . Esta comprensión de tipo retorno ya la realiza TS internamente.

typeofexpression () sería una especie de recursividad de tipo retorno mezclada con operaciones de tipo: por ejemplo

type E = typeofexpression (f(1) + g("x"))

es

type E = typecomprehensionof (typeof f(1) + typeof g("x"))

que podría entenderse como uno de

type E = typecomprehensionof (string + string) es decir string
type E = typecomprehensionof (string + number) es decir string
type E = typecomprehensionof (number + number) es decir number

Lo difícil que es esto internamente y los costos de rendimiento son desconocidos para mí.

EDITAR ------------------------------------------------- ------------

Quería agregar... esto es especialmente importante para cualquier persona que tenga que usar Function.bind, Function.apply, Function.call porque actualmente devuelven el tipo any y esto significa que tienen que escribir constantemente- anotados para asegurarse de que no se caigan del proceso de verificación de tipo.

Ser capaz de hacer referencia al tipo de retorno del argumento de la función sería... felicidad...

@poseidonCore

Ser capaz de hacer referencia al tipo de retorno del argumento de la función sería... felicidad...

Me gusta más el typeof <expression> actual, y el problema del tipo de retorno ya está cubierto en #12342.

Solo estaba usando typeofexpression y typecomprehensionof como un medio para diferenciar esos usos en la explicación. Preferiría typeof (expression) como sintaxis real.

Mi punto era que comprender el tipo de una expresión requeriría comprender el tipo de retorno de una función... f(1) también es una expresión. #12342 está relacionado de esta manera. No son mutuamente exclusivos.

Ya podemos hacer typeof para las variables y, dado que una expresión es una operación sobre variables y funciones, el siguiente requisito es poder devolver el tipo de una función... y luego comprender el resultado según las reglas de tipo. .

En realidad, buen punto. El problema # 12342 quiere es una forma de acceder al tipo de devolución como una especie de propiedad para tipos genéricos, por lo que no entendí bien la relación.

¿Qué tal usar expresiones para la llamada de la función typeof , como en la propuesta, pero usar tipos directamente en lugar de parámetros?

P.ej

function myFunction<T>(param1: T, param2: string, param3: number): T & {test: string} {
  // ...
}

type ResultType = typeof myFunction({hello: string}, string, number)
// ResultType is: {hello: string} & {test: string}

Tenga en cuenta que esto no impide que nadie use tipos de variables de alcance local, usando typeof dentro de las llamadas, es decir:

type ResultType = typeof myFunction(typeof obj, string, number)
// ResultType is: typeof obj & {test: string} 

Esto parece un poco mejor que la propuesta original, ya que funciona en un contexto ambiental y parece más flexible en general. Para mí, también deja más claro que en realidad no estamos llamando a la función, solo tratando de devolver el tipo de su valor de retorno.

Cómo lo hice para mi caso simple:

interface IAlertMessage {
  addedAt: number;
  text: string;
  type: "error" | "warning" | "info" | "success";
}

declare let alertMessageInterface: IAlertMessage;

const messages: IAlertMessage[] = [];

function addMessage(text: string, type: typeof alertMessageInterface.type): void {
  messages.push({addedAt: new Date().getTime(), text, type});
}

addMessage("something", "info"); // <- OK - and also has intellisense for the second parameter (after typing first " of the second parameter, press Ctrl+Space in VSCode)
addMessage("something", "fatal"); // <- Compilation error : error TS2345: Argument of type '"fatal"' is not assignable to parameter of type '"error" | "warning" | "info" | "success"'.

Lo anterior también funciona con miembros de la propia interfaz:

declare let myIface: MyInterface;

interface MyInterface {
    prop1: {
        big: {
            complex: {
                anonymous: { type: {} }
            }
        }
    },
    prop2: typeof myIface.prop1.big.complex.anonymous;
}

La ventaja de usar declare let ... en lugar de type ... para mí es que no produce nada en el archivo de salida .js.

@varadero esto no resuelve el problema en cuestión, que es recuperar el tipo de retorno de una llamada de función. Todo lo que está haciendo es recuperar el tipo de una propiedad de un tipo, que es una característica admitida desde TypeScript 2.2, creo.

Ese es en realidad el tipo de una propiedad de un valor (falso). El compilador no le permite usar typeof con tipo o interfaz "pura", solo funciona en valores.

¡Sería genial tener esto!

dado que type KEYOF<T extends any[]> = keyof T[0] ya existe, typeof T[0]('something') también funcionaría con esta implementación, suponiendo que function myFunc<T, K extends KEYOF<T>>(type: K)>{ } y myFunc([(r: string) => r]) (devolvería una cadena para typeof T[0]('something') ) ?

Esto sería poderoso junto con this polimórficos.

¡Es hora de que esto suceda!

Al volver a leer este hilo, traté de comprender lo que estamos haciendo con la sintaxis de typeof y por qué.

Evidentemente, en el caso trivial typeof foo ya implementado, da el tipo de foo . (Me imagino que pet is Fish es inmune al uso de una sintaxis especial).

En mi lectura actual de lo que debería hacer esta palabra clave en la propuesta actual, la palabra clave en sí no hace más que lo que hace ahora y, de hecho, la propuesta actual no está relacionada con la palabra clave typeof .

Menciono esto porque es importante en el caso que mencioné anteriormente, donde estamos aplicando una función almacenada como un tipo (ya sea a través type o como genérico), en lugar de como un valor.

Dado eso, supongo que, mientras que typeof fn<A>(string) necesita typeof para elevar la variable de nivel de expresión fn para usar en el nivel de tipo, Fn<A>(string) por otro lado , con Fn como un genérico que contiene una función, no requeriría eso y, por lo tanto, podría 'aplicarse' para obtener su tipo de retorno apropiado aquí sin necesidad de typeof .

En esta interpretación, verificaríamos las llamadas a funciones después de cualquier tipo de función potencial: además typeof fn(...) / typeof fn<...>(...) también Fn(...) / Fn<...>(...) , si ni siquiera funciona literal ((foo: Foo) => Bar)(Baz) / + genéricos. De lo contrario, el plan de ataque debería permanecer intacto.

Tal vez estoy malinterpretando cómo ven esto, tal vez las funciones almacenadas en tipos ni siquiera se habían considerado (ya que no encontré ninguna mención de ellas). De cualquier manera, pensé que valdría la pena confirmarlo.

Sin embargo, si anunciar la aplicación de funciones se convierte en otra sobrecarga semántica para typeof , además de eliminar la ambigüedad de los constructores de clase y elevar las variables de nivel de expresión al nivel de tipo (aparte del nivel de expresión typeof ), las cosas parecen gradualmente más complicados, como ya se indicó en algunas preguntas anteriores en este hilo.

Editar: un tipo genérico ya puede devolver un tipo de función, que también podría tener genéricos. Esto significa que otra permutación sería GetFn<A><B>() , con el primer conjunto genérico perteneciente a la invocación de tipo genérico, el último perteneciente a la invocación de función. Sin embargo, no GetFn<A><B><C>() , aunque GetFn<A><B>()<C>() también sería legítimo. Sin embargo, la conclusión anterior permanece sin cambios: cualquier tipo puede contener una función y, por lo tanto, (potencialmente) aplicarse como una función.

Edición 2: me acabo de dar cuenta de que habría una desafortunada ambigüedad en X<Y>() . Ahora, X sería un tipo que devuelve un tipo de función.

  • Si X no es genérico, está claro: <Y> pertenece a la llamada de función.
  • Si X es genérico y requiere el parámetro, esto también está claro: pertenece al tipo y a la función no se le pasa ningún parámetro de tipo.
  • Sin embargo, si X tiene un genérico opcional, se vuelve ambiguo.

    • En una interpretación, a X se le pasa un parámetro de tipo, mientras que a la función no.

    • En otra interpretación, se deja que X use su parámetro de tipo predeterminado, mientras que <Y> parametrizaría la llamada de función en su lugar.

Todavía no estoy seguro de cuál sería la mejor solución aquí.

  • Una opción sería forzar las invocaciones de tipo confiando completamente en los parámetros de tipo predeterminados para usar explícitamente el <> vacío en lugar de permitir omitirlo. Sin embargo, esto sería un cambio radical.
  • Una alternativa sería imponer tal restricción en la invocación de funciones de nivel de tipo; como esto sería nuevo, no se introduciría ningún cambio importante. Sin embargo, este enfoque sería poco elegante ya que introduciría una asimetría entre la sintaxis para la invocación de funciones entre los lenguajes de expresión y tipo, lo que puede hacerlo menos intuitivo.

@icholy : sí, estas ediciones solo se suman más a tu punto, lo sé.

Edición 3: está bien, esta característica ahora supera la parte superior de mi lista de deseos. Ninguna otra actualización se acerca ni remotamente en términos de impacto para la inferencia.

wat

@icholy : en resumen, si la 'función' está en un tipo/genérico, ¿aún escribiríamos typeof ?

Si se hace esto para que interactúe correctamente con las sobrecargas, en realidad le brinda funciones "variádicas" de forma gratuita (solo están procesadas en lugar de tener> 1 aridad), ya que puede definir recursivamente el tipo de retorno de una función en términos de la aplicación de una de sus sobrecargas, con alguna sobrecarga del caso base. Así es como se hacen las cosas en Haskell, y sería una característica maravillosa tenerla en TypeScript.

@masaeedu : eso suena interesante. Y sí, las sobrecargas son definitivamente lo que hace que esta propuesta sea tan interesante: podrían usarse para hacer coincidir patrones en diferentes opciones, incluidas las alternativas any . Comprobaciones de tipo como esa hasta ahora no eran posibles en el nivel de tipo todavía.

Reconozco que he usado Ramda más que Haskell. Pero a partir de ese trasfondo, pensé que curry normalmente no se combinaba bien con funciones variádicas, ya que curry necesitaría 'saber' si devolver el resultado u otra función para tratar con argumentos adicionales.

¿Podría tal vez mostrar algún pseudocódigo sobre cómo vería que esta idea funciona para funciones variadas como Object.assign (omitiendo detalles como & frente a Overwrite como usé en mi PoC por R.mergeAll )?

@ tycho01 He estado jugando con un código como este:

interface Pop<TVarStack extends VarStack> {
    (a: TVarStack["head"]): Pop<TVarStack["tail"]>
}

interface VarStack<THead = any, TTail extends (void | VarStack) = any> {
    head: THead
    tail: TTail
    <TNew>(a: TNew): VarStack<TNew, this>
    (): (a: Pop<this>) => any
}

// Figure out implementation later :)
let stack: VarStack<void, void>;
const pop = stack(new Date())(10)("Bob's")("your")("uncle")()

// a, b, c, d, and e are well-typed
pop(a => b => c => d => e => {
    // Need this because can't figure out how to encode base-case in Pop recursion
    return stack
})

VarStack es un tipo de función "variádica" en cierto sentido, en el sentido de que puede asignarle un número arbitrario de argumentos heterogéneos y registrará fielmente la información de tipo asociada mediante la recursividad en el nivel de tipo. Desafortunadamente, TypeScript no tiene un buen soporte para la transformación y la coincidencia de patrones en el nivel de tipo, de la misma manera que lo hace Haskell.

Si tuviéramos acceso a typeof , podría resolver el problema del caso base para Pop , ya que returnof(overloadedFunction(T)) esencialmente me daría una forma de hacer patrones. coincidencia en el nivel de tipo.

@tycho01 No puedo hacerlo precisamente por Object.assign , porque como dije, esto solo funciona para funciones curry, pero intentaré crear una función curry assign para ti que funcione más o menos de la misma manera

Me gustaría que generalizaran un poco el álgebra tipo, de modo que, por ejemplo (a: string) => (b: number) => void fuera solo azúcar por Func<string, Func<number, void>> , y (a: string, b: number) => void fuera solo azúcar por Arity<number, Arity<string, void>> , o algo como eso. Entonces solo necesitaríamos herramientas de uso general para transformar tipos a fin de obtener muchas características interesantes de manera emergente.

Disculpas a todos por el triple post. @ tycho01 Aquí está el curry, "variadic" assign :

interface Assign<TCurr extends {} = {}> {
    <TAdd extends {}>(a: TAdd): Assign<TCurr & TAdd>
    (): TCurr
}

let assign: Assign = undefined // implement somehow
const result = assign({ foo: "bar" })({ baz: 42 })()

@masaeedu : Ja, así que supongo que es como tener una función reductora :), aunque esa parte podría volverse más difícil si también hubiera argumentos no variádicos (?).

Me gusta la idea; Definitivamente no he estado pensando tanto en la dirección de las interfaces.
Sin embargo, si podemos cambiar el JS para los tipos, tal vez le pida a TC39 que simplemente cambie Object.assign a un mergeAll no variable. :D

No es como si mi iteración basada en incrementos funcionara con funciones en la práctica hasta ahora (#17086)... pero de cualquier manera estoy con ustedes, esta propuesta tendría un impacto mayor que cualquier otra que haya visto.

@ tycho01 Creo que es poco probable que obtenga mucha tracción si solicita que las funciones variádicas se conviertan en funciones de aridad fija, ya que hay un costo de tiempo de ejecución para las funciones procesadas o la aplicación repetida que JS (o al menos los motores y el código JS existentes) probablemente no esté muy optimizado para. Creo que lo que necesitamos en cambio es que TypeScript haga que sea igualmente fácil construir y deconstruir tipos para funciones arity> 1 recursivamente. Supongo que todo esto vuelve al #5453.

Otros pueden encontrar interesante saber que c ++ tiene una característica muy similar: decltype (expresión)

¿Esta característica llegará alguna vez a TypeScript?
Prefiero mucho la forma propuesta original donde el argumento es una expresión, pero la intención del equipo de TS de tener una sintaxis especial con tipos en posiciones de expresión simplemente complica todo y retrasa su llegada al idioma.
Tenemos que mantener este tema.

Estaría muy contento si pudiéramos implementar incluso un subconjunto de esta propuesta. A saber, este único:
type A = typeof anotherVariable . Podría hacer todo con solo esta línea. interface B extends A , <B & A> : B & A , etc.

Estoy viendo muchos casos de uso para este tipo de cosas con React. Cuando crea un componente de orden superior, es mucho más difícil insinuar en la declaración de clase del componente que actualmente es un HOC.

Lo que terminé haciendo es crear dos clases. Una clase extiende React.Component y agrega todos los métodos adicionales, mientras que la clase dentro de la función HOC manipula el render() .

@blindbox type A = typeof anotherVariable ya funciona:

var data = [1, 2, 3].map(v => ({ raw: v, square: v * v }));

function checkItem(item: typeof data) {
    // item is Array<{ raw: number; square: number; }>
}

@Igorbek Vaya, tienes razón.

Bien, descubrí por qué no funcionó en mi extremo.
esto no funcionará

interface B {
  thisIsB: boolean
}

const typeTest = (type: any) => {
  return 1 as any as App & B
}
type A = typeof typeTest(1)
declare const aVal: A; // aVal's type isn't of type App & B, but something else.

Sin embargo, esto funcionará.

interface B {
  thisIsB: boolean
}

const typeTest = (type: any) => {
  return 1 as any as App & B
}
const typeTestVal = typeTest(1)
type A = typeof typeTestVal
declare const aVal: A; // aVal's type is of type App & B!

Sí, entonces el problema es que no se puede usar en ciertos casos de uso, como cuando necesitas usar argumentos de tipo genérico.

function someFunctionWithComplexReturnTypeThatUsesGenerics<T>(item: T) { ... }

// it is impossible to work this around because there's no chance to create a fake variable that would be parameterized by T
function use<T>(item: typeof someFunctionWithComplexReturnTypeThatUsesGenerics<T>(item)) { ... }

@Igorbek :

Prefiero mucho la forma propuesta original donde el argumento es una expresión, pero la intención del equipo de TS de tener una sintaxis especial con tipos en posiciones de expresión simplemente complica todo y retrasa su llegada al idioma.

Entonces, desde que escribieron eso, hemos obtenido literales para cadenas y números, lo que significa que las cosas se están volviendo un poco más confusas; eso funcionaría de cualquier manera. La matriz literal y los objetos se ven similares a sus notaciones de tipo, hasta ahora todo bien.

Entonces también tenemos valores/tipos almacenados, por ejemplo, variables, genéricos, otros tipos. Para que las cosas se vuelvan útiles, no deberíamos tener que conformarnos con una parte de estas aquí.
Lo que constituye un buen argumento para su enfoque basado en tipos aquí es que permitir esta combinación es gratuito en el nivel de tipo. Es decir, en el nivel de tipo ya podríamos decir typeof myVar , lo que significa que si esta función 'aplicación' se agrega al nivel de tipo, automáticamente podríamos conectar tanto los tipos almacenados como las variables regulares .

Me interesaría ver sus pensamientos adicionales sobre el enfoque sugerido originalmente. Es cierto que eso podría servir para exponer un poco más al nivel de tipo: operadores basados ​​en JS (piense ! && || + - * / instanceof ), así como operadores específicos de TypeScript (operador de aserción ! ).
Lo que pasa con esos operadores JS es como... son bastante inútiles en el nivel de tipo tal como está, ya que permitirles operar en literales para producir los tipos de resultados literales correspondientes actualmente se considera fuera del alcance ( ref ) -- expresión -level 1 + 1 solo produce tipo number , y similar para los demás.
Con esto en mente, yo mismo he estado un poco deprimido con su propuesta basada en tipos.

¿Esta característica llegará alguna vez a TypeScript? [...] Necesitamos mantener este tema.

Sugerí esta propuesta como una solución más general a un síntoma más pequeño aquí , aunque con un éxito limitado.

@tycho01 Mis argumentos para la variación basada en expresiones de typeof son casi los mismos que los declarados originalmente por @yortus :

  • consistencia : el typeof existente (en la posición de tipo) ya está trabajando con expresiones, sin embargo, está limitado a aceptar un solo símbolo. De modo que aceptar tipos o pseudollamadas introduciría una sintaxis mucho más engorrosa que es mucho más otra rama de sintaxis (además de expresiones y tipos).

    • los usuarios no necesitan aprender nueva sintaxis

  • simplicidad : TypeScript parece tener ya todas las instalaciones necesarias para implementar esta función de manera discreta (ha sido probado por el PR)
  • integridad : las expresiones tienen al menos la misma expresividad que los tipos, ya que siempre podemos usar aserciones de tipo ( (undefined as any as <any arbitrary type can be here>) ), pero a veces el sistema de tipos carece de tipos que, sin embargo, pueden expresarse en expresiones (los tipos spread y rest se introdujeron después se habían aterrizado las expresiones correspondientes).

Gracias @tycho01 por traer ese punto a promised PR (en realidad vine aquí después de sus comentarios allí), eso realmente muestra cómo una función tan simple y genérica puede cubrir escenarios mucho más complejos de una manera muy elegante sin hornear en un comportamiento muy específico en el idioma.

Veo el typeof extendido como un verdadero cambio de juego para la expresividad del sistema de tipos, la mayoría como los tipos mapeados/ keyof hicieron eso.

@Igorbek : Gracias por elaborar, veo de dónde vienes entonces. Quizás estos dos enfoques sirvan para diferentes casos de uso entonces.

Creo que está demostrando mejor el valor actual de la propuesta original que la publicación original actual, ya que el TS actual (o, bueno, el 2.3.3 de Playground) puede hacer que muchos de los escenarios originales ya funcionen:

// I have a strongly-typed collection but the element type is anonymous/unknown, how can I reference the element type? (#3749)

// Mapping to a complex anonymous type. How to reference the element type?
var data = [1, 2, 3].map(v => ({ raw: v, square: v * v }));
declare function checkItem(item: typeof data[0]): any
// ^ no longer errors, needs no further change

// A statically-typed dictionary. How to reference a property type?
var things = { 'thing-1': 'baz', 'thing-2': 42 };
type Thing2Type = typeof things['thing-2'];
// ^ no longer errors, needs no further change

// A strongly-typed collection with special indexer syntax. How to reference the element type?
var nodes = document.getElementsByTagName('li');
type ItemType = typeof nodes.item(0);
// ^ the `.item` access works, but function applications still errors, would be fixed by either version of the proposal.

// A function returns a local/anonymous/inaccessible type, how can I reference this return type? (#4233, #6179, #6239)

// A factory function that returns an instance of a local class
function myAPIFactory($http: HttpSvc, id: number) {
  class MyAPI {
    constructor(http) {}
    foo() {}
    bar() {}
    static id = id;
  }
  return new MyAPI($http);
}
declare function augmentAPI(api: typeof myAPIFactory(HttpSvc, number) /* ERROR */): any
// ^ function applications errors, would be fixed by either version of the proposal, if using slightly different syntax.

// I have an interface with a complex anonymous shape, how can I refer to the types of its properties and sub- properties ? (#4555, #4640)

// Declare an interface DRY-ly and without introducing extra type names
type MyInterface = {
  prop1: {
    big: {
      complex: {
        anonymous: { type: {} }
      }
    }
  },
  // prop2 shares some structure with prop1
  prop2: MyInterface['prop1']['big']['complex'];
}
// ^ no longer errors after swapping `.k` access to `['k']`. `typeof` was unnecessary and counter-productive -- MyInterface isn't exposed on the expression level.

Supongo que estos ejemplos ya no son un gran argumento para los enfoques de expresión o tipo: la mayoría son obsoletos, y cualquiera de los dos habilitaría la aplicación de funciones triviales.

Es cierto que la aplicación de función basada en el nivel de tipo, tal como se centró en tinganho, el equipo de TS y yo mismo, podemos exponer las variables de expresión a través typeof , aunque, como notó, el nivel de tipo ciertamente tiende a retrasarse con respecto a la funcionalidad expuesta en la expresión. nivel. Esta aplicación de función en sí misma, así como la sintaxis extendida que mencionaste, son obviamente ejemplos principales, y definitivamente me encantaría que se aborden. Quizás gran parte de la brecha actual podría incluso abordarse aquí.

Del mismo modo, un enfoque de expresión primero también permitiría inyectar tipos usando <MyType> whatever o whatever as MyType , aunque como typeof en el enfoque de tipo, parecería una ocurrencia tardía: preguntas sobre la aplicación de funciones almacenadas en tipos/genéricos permanecen, lo que probablemente compense la mayor parte del valor agregado real de ese enfoque basado en tipos (aunque no se menciona allí): inferencia precisa para funciones de orden superior, así como, digamos, condicionales basados ​​​​en tipos como en ese hilo promised .* Peor aún, a diferencia del caso de uso del OP, estos no tienen soluciones alternativas.

Creo que veo dónde chocan las ideas: las propuestas actuales tienen puntos de vista contradictorios sobre si la palabra clave typeof cambiaría su expresión al nivel de valor, frente a (en mi interpretación) dejar typeof como está , pero exponiendo la sintaxis de la aplicación de función en el nivel de tipo.

Sin embargo, en mi opinión, la contradicción es algo accidental. No ignoraría la legitimidad de ninguno de los casos de uso; si se implementaran ambos, podría ver una palabra clave adicional que evita el conflicto de semántica. Honestamente, me es indiferente si las palabras clave terminarán de una forma u otra; solo quiero escribir mierda.

*: Me acabo de dar cuenta de que probablemente utilizarías funciones de orden superior haciendo referencia a funciones en parámetros por sus nombres de parámetros, en lugar de capturar sus tipos en genéricos.
Parece que, de hecho, las cosas se pueden convertir en ambas direcciones: typeof ayuda a pasar del nivel de valor al nivel de tipo, mientras que declare let y: x; ayuda a pasar del nivel de valor al nivel de tipo. Poco elegante como la solución del OP, pero sí.
Sin embargo, si los tipos de función se introdujeran a través de, por ejemplo, genéricos explícitos, supongo que esto no ayudaría: estarían en el nivel de tipo sin una forma de moverlos.
Si eso suena como si esperara anticipadamente cubrir las bases de la funcionalidad, probablemente se deba a que gran parte de mi progreso en los desafíos de escritura sin resolver se bloqueó en este complemento (con una mención notable a 5453). Pero si podemos resolver estas cosas, valdrá la pena.

Editar: he pensado en algunos casos teóricos que podrían excluirse en un enfoque basado en expresiones ahora, aunque todavía no se me han ocurrido ocurrencias prácticas:

  • funciones pasadas a través de un genérico proporcionado explícitamente.
  • funciones devueltas por funciones de parámetros, para ser capturadas en un genérico, es decir (usando la sintaxis de la propuesta basada en expresiones) declare function f<G extends (...args: any[]) => R, R extends <T>(foo: T) => Bar<T>>(g: G): typeof R(baz); // error following the expression-based proposal: R is not exposed at the expression level . Por supuesto, estos podrían calcularse si supiera y pudiera proporcionar los tipos de parámetro de G , pero afaik no hay forma de obtener esta información hasta ahora (a menos que tal vez # 14400?). Supongo que esta categoría incluiría funciones que operan en las funciones de fábrica utilizadas en AngularJS mencionadas anteriormente.

@Igorbek No entiendo lo que esperas con este fragmento:

function use<T>(item: typeof someFunctionWithComplexReturnTypeThatUsesGenerics<T>(item)) { ... }

Parece una definición circular.

@masaeedu lo siento, quise decir:

(cambiar a f en lugar de someFunctionWithComplexReturnTypeThatUsesGenerics )

function use<T>(item: typeof f<T>(undefined as any as T)) { ... }

Solo para elaborar, eso es lo que actualmente no se puede solucionar introduciendo una variable falsa:

const _typeofItem = f<T>(undefined as any as T); // no T here
function use<T>(item: typeof _typeofItem) { ... }

Por cierto, me acabo de dar cuenta de que la propuesta actual entra en conflicto con el operador de tipo de consulta de tipo de miembro T[K] ya que typeof tiene mayor prioridad:

  • typeof x[K] actualmente significa (typeof x)[K] donde K _es_ un tipo
  • si se toma la variación basada en expresiones, se introduciría una ambigüedad:

    • typeof x[k] significa (typeof x)[k] donde k es un tipo; o

    • typeof x[k] significa typeof (x[k]) donde k es una expresión

De hecho, preferiría typeof (x[k]) porque son semánticamente equivalentes, pero seguro que es un cambio importante.

Cuando usé las herramientas de desarrollo de Chrome, obtuve que el operador miembro tiene mayor prioridad. ¿Dónde encontraste eso?

image

@Igorbek : sí, esa parece ser la razón por la que type Thing2Type = typeof things['thing-2']; de la publicación original ya funciona.

@dehli : está comparando el nivel de expresión JS con el nivel de tipo TS; no son lo mismo. Uno se ejecuta en navegadores/nodos, el otro en el compilador TS.

Personalmente, me parece extraño que TypeScript resuelva el nivel de tipo typeof con una prioridad más alta que el acceso de tipo indexado. Casi me parece un error, la verdad. (Me pregunto si ese caso realmente se prueba).

@tycho01 Te tengo . Supuse que TS usaría el mismo orden de precedencia que JS.

@dehli los niveles de expresión son los mismos, sí. En el nivel de tipo, es otra cosa con una sintaxis similar, pero devolviendo un tipo en lugar de una cadena.

Supongo que la precedencia podría ser el resultado de las limitaciones que habían previsto que tuviera. Admitiré que esas consideraciones pueden no parecer tan sensatas si se va a ampliar su función.

En otra nota, si se implementaran ambas versiones de la propuesta, los corchetes servirían como una forma efectiva de mantener explícito en qué nivel estaríamos para eso también. Tengo problemas para encontrar cosas que no suenen como compromisos, pero sí.

@Igorbek ¿Es este el caso de uso principal restante para typeof en expresiones arbitrarias? Si obtenemos #14400, eso nos daría returnof usando tipos simples definidos por el usuario si no me equivoco.

14400 no nos dará returnof en su forma genérica:

type Return<T extends () => R, R = any> = R;

// overloaded function
declare function f(item: number): number;
declare function f(item: string): boolean;

type fType1 = Return<typeof f>; // ??
type fType2 = typeof f(1); // number
type fType3 = typeof f('a'); // boolean

// generic function
declare function g<T>(item: T): T;

type gType1 = Return<typeof g>; // ??
type gType2 = Return<typeof g<number>>; // not supported syntax
type gType3 = typeof g(1); // number
type gType4 = typeof g<number>(1); // number
type gType5 = typeof g('a' as 'a'); // 'a'

No estoy al tanto de todos los casos de uso restantes, parece que aún no se han descubierto, pero los más imbatibles y atractivos para mí son:

  • _tipos mapeados condicionales_ que son mucho más poderosos que los que podríamos obtener con #12424, porque typeof usaría una resolución de sobrecarga estándar; @tycho01 ha mostrado un ejemplo elegante en tipos promised PR #17077
  • obtener tipos de retornos de funciones/métodos en el contexto de tipos genéricos (como mostré antes)

@masaeedu : Buena pregunta. La versión corta es más o menos lo que dijo @Igorbek ; Para obtener más detalles sobre los desafíos concretos resueltos, encontrará una breve lista en mi lista de 'características principales necesarias' en el n.º 16392, donde he tratado de vincular los desafíos no resueltos con las propuestas que podrían habilitarlos.

Esto incluye:

  • funciones de fábrica como las utilizadas, por ejemplo, por AngularJS, vea los tres hilos vinculados en la publicación original aquí. - de manera realista, esto debería estar bien dado el nuevo ReturnType incorporado.
  • Dadas las entradas conocidas, puede calcular repentinamente los tipos de devolución exactos para cosas como reduce / map / filter / find , cualquier cosa que implique iteración y controles. stdlib.d.ts se beneficiaría allí, al igual que las librerías FP como Ramda/Lodash. En la práctica, es posible que no se conozcan todas las entradas (más matrices en forma de lista que tuplas), pero las operaciones map y filter en objetos también podrían escribirse mejor que solo Partial . Esto es necesario para escribir mejor las bibliotecas de administración de estado como redux (consulte https://github.com/piotrwitek/react-redux-typescript/issues/1) y ngrx de Angular, que hasta entonces se ven obligados a mantener su API de usuario de bajo nivel. porque sin una operación map bien tipificada, no hay forma de que calculen el resultado deseado en el nivel de tipo a partir de los datos de entrada de DRY'er.
  • Actualmente, los tipos de composición de funciones tampoco pueden tener en cuenta los tipos de devolución dependientes de la entrada.
  • De repente, también sería viable comenzar a escribir cosas como lentes. - Supongo que técnicamente esto solo está bloqueado aquí en las operaciones de edición con tipos de devolución dependientes de la entrada. que es un lujo la verdad. Sin embargo, alterar las tuplas todavía necesita (parte de) #5453.
    También permite:
  • operar en literales booleanos a nivel de tipo, lo que hasta ahora ha sido imposible
  • desenvolviendo tipos para flatMap -como operaciones como esa propuesta promised
  • agregando restricciones a la entrada de tipo/función, el tema de #15347 y una idea que obtuve del libro 'Desarrollo basado en tipos con Idris' sobre tipos dependientes. Esto podría ayudar a decir que no se permiten 0 divisores, esencialmente un truco para restar de tipos, el tema de #4183. Estoy seguro de que todavía no me he imaginado la mayoría de los casos de uso, pero eso podría hacerse con esto, por ejemplo, function div<B extends number, NotZero = { (v: '1') => 'whatever'; }({ (v: 0) => '0'; (v: number) => '1'; }(B))>(a: number, b: B) . Normalmente, el lenguaje Idris se anuncia con operaciones vectoriales/matriz de longitud segura, pero eso solo necesita esto para funciones de orden superior.
  • Una forma de expresar restricciones Union<T> / NonUnion<T> en tipos de entrada usando las restricciones anteriores + IsUnion . -- ese tipo se rompió y ya no estoy seguro de cómo hacer esto.
  • dado #5453 también, esto también ayuda a escribir funciones como curry , Function.prototype.bind , Promise.all (no es una lista exhaustiva, solo algunas funciones que otras personas plantearon como desafiantes).
  • Lógica switch propuesta en #13500, aquí resuelta a través de la coincidencia de patrones de sobrecarga de funciones
  • esos tipos condicionales mapeados que son el tema de #12424 y #17077 ( promised ), también conocidos como verificaciones de tipo de nivel de tipo. Sin embargo, eso no lo resolvería, incluso si se pudiera usar para producir literales booleanos en cualquier lugar hoy en día, hasta que este #6606 aterriza, todavía no teníamos forma de operar / hacer condicionales basados ​​​​en tales literales booleanos en el nivel de tipo todavía de todos modos.
  • comprobando la presencia del índice de cadenas en los tipos de objetos para preservar esto en todas las operaciones (como Omit , Overwrite de #12215), vea mi comentario en #12424
  • Según las verificaciones de índice de cadena anteriores, una forma de corregir el acceso a la propiedad de tipo de objeto de modo que verifique cadenas que coincidan con nombres de métodos de prototipo de objeto (por ejemplo toString , toLocaleString ) se resolverá en el índice de cadena en lugar de que con los métodos prototipo, lo que podría corregir fallas relacionadas con el acceso a objetos toString en todas las demás operaciones de tipo que anteriormente dependían del acceso a propiedades de objetos integrado con fallas.

editar: casos de uso tachados llenos por tipos condicionales (# 21496) desde el momento de la escritura.

La propuesta de @Igorbek de un enfoque de expresión primero lo haría (a costa de tal vez romper algunas aplicaciones desconocidas de parte de la lista anterior; todavía tengo que pensar en algo concreto, aunque tengo miedo de quedar arrinconado) ) además prometen cerrar la brecha entre los niveles de valor y tipo, que actualmente incluyen cosas como esta aplicación de función (este #6606), propagación/descanso (#5453), operador de aserción ! (#17370), tipo de unión resta a través de guardias de tipo (#4183, de lo contrario se puede lograr a través de las restricciones mencionadas anteriormente), y posiblemente más que no puedo pensar en la parte superior de mi cabeza.

Supongo que todo este intercambio podría generar algunas preguntas, como por qué hacer que la misma funcionalidad sea accesible de dos maneras (nivel de tipo real, nivel de expresión al que se accede incrustado en el nivel de tipo).

Editar: actualicé mi comentario de comparación anterior con un desafío potencial adicional.

Edición 2:

Es desafortunado aquí que la publicación original le hiciera creer que esto solo permitiría escribir algunos casos extremos sin alguna solución alternativa null & , mientras que en realidad esta es la característica crítica que frena a TS en este momento sin soluciones alternativas conocidas para contextos ambientales. .

Eso significa que afecta a TS escritos en archivos .d.ts separados, que es la norma no solo para stdlib.d.ts , sino también para todos los tipos de DT escritos por separado de sus proyectos JS originales, que rara vez cambiarán mientras que JS libs quiere permanecer abierto a alternativas como Flow también.

(Esto no es mucho mejor para los tipos de .ts , ya que los tipos que usan la 'solución alternativa' del OP no se pueden parametrizar como para componer tipos reutilizables de nivel superior sin influir en el nivel de expresión).

@tycho01 Gracias por la lista; hay muchos enlaces allí que necesito digerir. Realmente quiero la coincidencia de patrones en los tipos también, y #6606 es una buena solución pragmática para el problema. Sin embargo, me parece que hay una confusión cada vez mayor entre las cosas en los niveles de valor y tipo, y #6606 no mejorará las cosas en esta área.

La razón por la que se necesita esta característica es porque no tenemos forma de construir expresiones de tipo que correspondan a:

  • el tipo de retorno de un tipo de función, cuando se aplica con argumentos de ciertos tipos
  • (antes typeof K[L] ) el tipo de una propiedad en un tipo de objeto que corresponde a una clave de tipo literal de cadena
  • (posiblemente otros, necesito repasar su lista más de cerca)

Siento que, de hecho, debería ser posible construir expresiones puramente de nivel de tipo para estos, sin recurrir a mezclar construcciones de nivel de valor y expresiones de nivel de tipo. Sería bueno si tipos como (a: A) => B o A | B fueran azúcar en tipos parametrizados simples como Func<A, B> , Union<A, B> , y tuviéramos herramientas generales para manipular tipos parametrizados (HKTs, fundeps o familias tipo, etc.).

Sé que un enfoque excesivo en la solidez no es uno de los objetivos de TypeScript, pero ahora hay muchos conceptos de nivel de tipo que interactúan juntos de manera opaca. Algún tipo de formalización de lo que es un tipo, y una forma de prescribir reglas mecánicas sobre cómo un tipo interactúa con otros tipos (subtipificación, desestructuración, etc.) sería de gran ayuda.

Siento que, de hecho, debería ser posible construir expresiones puramente de nivel de tipo para estos, sin recurrir a mezclar construcciones de nivel de valor y expresiones de nivel de tipo.

Oh, sí, personalmente no tenía la intención de recurrir al uso de construcciones de nivel de valor en absoluto, esto es todo lo que estoy intentando. Si el nivel de valor fuera indispensable, yo mismo podría haber estado en el campo basado en la expresión. :PAGS

Supongo que se espera que la brecha entre los niveles de valor y tipo se reduzca (¿TC39 se mueve más rápido que TS?), Y afaik, las brechas ya tienen una propuesta de nivel de tipo sobresaliente (ver la parte inferior de mi publicación anterior).

Diablos, me doy cuenta de que construir tipos para algunas de las funciones estará fuera de la experiencia de la mayoría de los usuarios.
La forma en que veo esto es que quiero que la biblioteca estándar y las bibliotecas FP se escriban tan bien que los usuarios de TS puedan escribirlas y que la inferencia se encargue automáticamente de ellos.
TS tiene solo unos pocos operadores de nivel de tipo, pero resolver problemas reales con ellos (el ejemplo principal es Overwrite / Omit #12215) bien podría parecer poco menos que ciencia espacial para los desarrolladores web ocasionales. Diablos, nos ha llevado hasta hace poco también, y ni siquiera son prueba de prototipo/índice/símbolo todavía.

Sería bueno si tipos como (a: A) => B o A | B eran azúcar en tipos parametrizados simples como Func , Union

Podemos darle la vuelta y crear los tipos parametrizados como alias/constructores de tipos. Para una operación de tipo que toma un Foo<Bar> , no importa si su cosa se simplifica a uno, solo verifica si se ajusta a la descripción.
Esto es más o menos lo que hace stdlib.d.ts : tiene un foo[] , pero cumple con la descripción Array<Foo> y, por lo tanto, funciona con sus tipos Array.prototype .
Sin embargo, no es que eso realmente ayude:

type Union2<A, B> = A | B;
type TuplizeA<Tpl extends Union2<any, any>, A, B> = [Tpl[0], Tpl[1]];
// ^ sorry, no way to access union members through e.g. [0]
// type a = TuplizeA<1 | 2>;
type TuplizeB<Tpl extends any | any> = [Tpl[0], Tpl[1]];
// ^ sorry, no way to access union members through e.g. [0]
// type b = TuplizeB<1 | 2>;
type TuplizeC<Tpl extends Union2<A, B>, A, B> = [A, B];
type c = TuplizeC<1 | 2>;
// ^ need 3 arguments, maybe fixable with #14400
type TuplizeD<Tpl extends A | B, A, B> = [A, B];
// ^ need 3 arguments, maybe fixable with #14400
type d = TuplizeD<1 | 2>;

Entonces, um, sí, no lo he resuelto, pero me acabo de dar cuenta de que el #14400 de Kube realmente podría ayudar allí. ¡Y acabo de verte allí hoy también!
Lo mismo para las funciones en realidad, lo cual es un buen recordatorio de que # 14400 podría no solo hacer tipos de devolución de funciones allí, sino también tipos de parámetros.

Por el momento, este enfoque necesitaría usar sobrecargas, lo cual es desafortunado ya que no escala, pero sí. Para hacerlo genérico de nuevo, esencialmente usaríamos estas sobrecargas de coincidencia de patrones #6606 para activar la opción correcta para diferentes aridades.
Supongo que uno podría usar eso para convertirlos genéricamente en tipos de tupla, luego iterarlos usando mi enfoque de incremento para operarlos de alguna manera.
Para las uniones, esperaba una forma más bonita de convertir a tipos de tupla. Sin embargo, agregaría un orden arbitrario y tampoco puede pensar en una sintaxis / palabra clave bonita.

Editar: para repasar esas brechas de funcionalidad de nivel de expresión/tipo nuevamente:

  • aplicación de función: este # 6606
  • operador de aserción ! (#17370): después de este #6606 esto se resuelve
  • resta de tipo de unión a través de guardias de tipo (#4183): solo un caso general de ! arriba, con este #6606 también alcanzable a través de restricciones (por ejemplo NotZero arriba).
  • spread/rest (#5453) -- incluso si las tuplas no se pueden manipular hasta que lleguen, los ArrayLike similares sí pueden, vea las operaciones de List en mi esencia . con este # 6606 ahora creo que podríamos extraer parámetros de funciones no aplicadas, aunque extraerlos en el momento de la aplicación (es decir, para obtener sus valores de entrada precisos), todavía necesitaría 5453.

En resumen, si esta propuesta fuera a aterrizar, diría que una brecha de funcionalidad entre los niveles de expresión y tipo no sería un gran argumento para la propuesta con sabor a expresión aquí. Si 5453 también estuviera dentro, ya no podría pensar en ninguno allí. Y tenga en cuenta que, en raras excepciones, la solución alternativa indicada en la publicación original aquí seguirá siendo válida.

Ahora, un argumento que aún se puede hacer fácilmente sería que con la variante con sabor a expresión, incluso antes de que el nivel de tipo se ponga al día en los operadores de duplicación, esta funcionalidad estaría expuesta bajo la misma sintaxis que en JS (sin soluciones alternativas), reduciendo la curva de aprendizaje.

Edición 2:

Me acabo de dar cuenta de que el truco de la propuesta de expresión de traer tipos al nivel de expresión, 1 as any as MyType , lógicamente debería funcionar también para la función en sí.

Esto probablemente significa que la funcionalidad real habilitada por ambos sabores parece algo similar, las diferencias externas consisten principalmente en typeof myVar (tipo de tipo) frente a myVar (expresión de tipo) para usar variables en la aplicación de funciones; para usar tipos en ellos MyType (sabor de tipo) frente a 1 as any as MyType (sabor de expresión, alternativa declare let a: any; y luego <MyType>a ).

Los cambios AST de cualquiera de los dos también parecen bastante manejables. El tipo de expresión solo necesita el conjunto typeof para apuntar a una expresión de valor en su lugar; el tipo de tipo copiaría la sintaxis de la aplicación de la función existente ( fn<T>(arg) ) de la expresión al nivel de tipo, enganchándose a la implementación existente según lo propuesto por Ryan anteriormente.

Creo que entonces todo se reduce a lo siguiente:

Caso de expresión sabor:

  • typeof expr con sintaxis JS sin solución antes del soporte de nivel de tipo TS

Estuche para tipo sabor:

  • sin cambios importantes sobre la prioridad
  • las expresiones de valor aún se pueden capturar a través de una solución alternativa (o a través de TS si están bajo una sintaxis diferente hasta que los operadores se pongan al día)
  • MyType en lugar de 1 as any as MyType : ningún nivel de tipo en su nivel de expresión en su nivel de tipo en su nivel de expresión.

Un tema relacionado que hasta ahora no se ha tocado aquí ha sido cómo proporcionar enlaces this en esta 'aplicación' de función de nivel de tipo. Ahora, JS delega sobrescribir esto en los métodos Function.prototype , pero tal como está, carecen de una forma de manejar esto en el nivel de tipo.

Sintaxis de ejemplo aleatorio, dado un tipo de función F (this: Foo, a: number, b: string) => SomeReturnType que de otro modo podría haberse invocado como F(MyA, MyB) : F(this: MyFoo, MyA, MyB) .

Calcular el tipo de devolución sin sobrescribir el enlace this aún sería como F(MyA, MyB) , reflejando cómo el argumento de nivel de tipo this normalmente se ignora si intenta usar una función de este tipo en el nivel de expresión.

Pros de esta sintaxis de ejemplo:

  • refleja la sintaxis de la declaración

Contras de esta sintaxis de ejemplo:

  • refleja la sintaxis de la declaración

Entonces, resulta que ¡esto ya está en el idioma!

No te emociones demasiado.

@DanielRosenwasser acaba de señalarme el n. ° 12146, un error que permite que se usen llamadas de función en literales en lugar de tipos.

_5 minutos después_

Ta dah! Una horrible cosa malvada que nunca deberíamos usar en producción. Pero es tentador...

interface String {
    passthrough<T>(v: T): T;
}

// All work
type ANumber = "".passthrough(10 * 10);
type AString = "".passthrough("hello" + "world");
type AHelloWorld = "".passthrough("hello world");
type AnArbitraryThing = "".passthrough(Object.assign({hello: "world"}, {foo: "bar"}));
type ThisCraziness = "".passthrough((() => "cows are big dogs"));

~Eso hace que el Effort: Difficult en este tema parezca un poco dudoso, parece que lo hicieron por accidente.~ Leo más y me siento tonto, esto _es_ difícil de hacer bien.

Diviértete @tycho01.

@TheOtherSamP Intenté esto con TypeScript 2.4.2 y se infiere que todos esos tipos son any .

@pelotom Huh, está funcionando aquí en 2.4.2 y 2.5.0-dev.20170803. Objetivo es6 y modo estricto.

image

Aunque parece que lo acaban de arreglar, me temo que puede ser culpa mía. #17628

@TheOtherSamP No, no hay dados. Oh bien.

@pelotom Eso es extraño, está funcionando en un proyecto completamente nuevo para mí, no sé qué sería diferente en nuestras configuraciones.

@TheOtherSamP : jaja, eso es bastante divertido.
Sin embargo, en cuanto al tiempo, parece que comenzaron la solución un poco antes de tu comentario. Oh bien.

@pelotom :

Intenté esto con TypeScript 2.4.2 y se infiere que todos esos tipos son cualquiera.

Su fragmento parece funcionar en Playground (2.3.2). En una versión reciente fuera de ella ( ^2.5.0-dev.20170626 ), también tengo problemas para reproducir.

Eso hace que el Effort: Difficult en este tema parezca un poco dudoso, parece que lo hicieron por accidente allí.

Se referían a una implementación basada en tipos, lo que significaría algunos cambios, mientras que esto parece usar lenguaje de expresión (-> + , Object.assign , más llamadas a funciones).

@tycho01 Creo que todo comenzó cuando llamé la atención sobre él en #17618. Bueno, eso me enseñará a considerar semi-seriamente usarlo en producción.

Se referían a una implementación basada en tipos, lo que significaría algunos cambios, mientras que esto parece usar lenguaje de expresión (-> +, Object.assign, más llamadas a funciones).

Sí, fui un idiota y no leí todo este tema hasta después de decir eso. Es una pena, probablemente sea una mejor versión de la función, pero me gustaría que pudiéramos tenerla ahora. Empujo mucho los límites del sistema de tipos, y esto o #12424 abrirían tantas opciones.

Abrió un PR en #17961.

@yortus ¿Cubre este caso de 'typeof literal'?
TypeScript hoy no permite escribir "const x: typeof 1 = 1;"

@NN --- la propuesta original cubre todas las expresiones, pero según tengo entendido, la parte 'aprobada' solo cubre el acceso a la propiedad y los tipos de devolución de funciones.

Incluso si se permitiera typeof 1 , no estoy seguro de si daría el tipo literal ( 1 ) o el tipo más amplio ( number ).

TypeScript hoy no permite escribir "const x: typeof 1 = 1;"

¿Por qué no const x: 1 = 1; ?

@SaschaNaz Quería escribir algo como

const a = {q:1};
const b = {q:1};
const x: ReadonlyArray<typeof a> = [a,b];

Pero similar no funciona con literales:

const x: ReadonlyArray<typeof 1> = [1,2,3];

@yortus Buen punto sobre el tipo exacto. No pensé en tipos literales..

@NN---: Creo que tu ejemplo ya funciona.

@tycho01 Flow ahora tiene el tipo $Call para obtener el tipo de función de retorno https://github.com/facebook/flow/commit/ac7d9ac68acc555973d495f0a3f1f97758eeedb4

¿No sería lo mismo permitir solo typeof fn(...) que permitir typeof de una expresión arbitraria?

function fn() {
  return /** whatever expression you like */;
}
type a = typeof fn();

excepto que ahora está creando una función de tiempo de ejecución sin otro propósito que averiguar un tipo?

Realmente no. Estás haciendo una evaluación de tipo de una expresión, no una ejecución de la expresión.

@dyst5422 typeof fn() en realidad no evaluaría la expresión, solo le daría el tipo de devolución.

EDITAR: tal vez eso es lo que estabas tratando de decir, no estoy seguro. Pero creo que @sky87 estaba hablando de _definir_ una función sin ningún propósito excepto usarlo en una expresión de tipo, no _evaluarla_.

@dyst5422 , como dijo @pelotom , no quise decir que ejecutarías la función. Para elaborar más: si no permite el typeof de expresiones arbitrarias pero permite el typeof del tipo de devolución de una función, ¿qué haré para averiguar el tipo de expresión más complicada? expressions es envolverlas en una función para que pueda preguntar por su tipo de devolución. Eso crea una función en tiempo de ejecución, aunque solo para obtener su tipo de retorno, y es más repetitivo para escribir.

EDITAR: en realidad ya puedes descubrir el tipo de expresiones arbitrarias, esto es feo pero funciona

const dummy = (false as true) && /* arbitrary exp */;
type TypeOfExp = typeof dummy;

Sinceramente, no sé qué truco preferiría. Creo que lo mejor sería poder preguntar directamente por el tipo usando typeof .

Ah, te sigo ahora. Sí, creo que la forma preferible de hacerlo sería poder usarlo como

type TypeOfExp = typeof (
  false &
  "false" &
  0
)

para poder hacer arbitrariamente una evaluación del tipo de expresión

¿Será posible consultar el tipo de retorno de una invocación new ? Mi caso de uso: Quiero escribir anotaciones de tipo para una función que acepte una referencia a cualquier implementación PromiseConstructorLike (por ejemplo, $q o Bluebird) y devuelva una Promesa construida por esa implementación.

declare function wait<P extends PromiseConstructorLike>(time: number, implementation: P): typeof new implementation<void>((res: any, rej: any) => void);

const bluebirdPromise = wait(1e3, Bluebird);
// typeof bluebirdPromise should be instance of Bluebird

¿Será posible consultar los tipos de devolución sin typeof , o tendremos que null as FnType ?

interface Fn {
    (a: string): string;
    (a: number): boolean;
}
type Ret = Fn(number); // Ret = boolean
type Ret = typeof (null as Fn)(number);

Lo siento si estas preguntas ya han sido respondidas; No pude encontrarlos.

¿Cuál es el punto de new aquí, no querrías solo typeof implementation() ?

No, porque implementation() no es una llamada válida. PromiseConstructorLike solo se puede invocar a través new , según sus declaraciones de tipo. typeof implementation() es un error de tipo, al igual que (typeof implementation)['foobar'] sería un error de tipo.

Enlace del patio de recreo

¿Es posible introducir tipos genéricos inferibles como lo hizo FlowType? Al menos, puede resolver el problema de obtener el tipo de valor de retorno de las funciones.

type _ExtractReturn<B, F: (...args: any[]) => B> = B;
type ExtractReturn<F> = _ExtractReturn<*, F>;

@Cryrivers : vea #14400 para ese enfoque. Sin embargo, en realidad no resuelve el problema donde el tipo de salida depende de la entrada.

Terminé necesitando esto nuevamente hoy para insinuar qué devolvería dinámicamente una llamada a una función, seguro que desearía que tuviera prioridad.

Se agrega un operador ReturnType<T> a lib.d.ts en TS 2.8, impulsado por tipos condicionales.

Como ReturnType<T> todavía tiene que tener en cuenta los tipos de retorno que dependen de los tipos de argumentos, como referencia, aquí está la implementación del tipo $Call de Flow.

editar: lo siento @goodmind , no me había dado cuenta de que ya te habías vinculado exactamente a eso.

Actualicé mi publicación anterior de casos de uso de esta propuesta (o su tipo de interpretación de llamada) en función de las adiciones recientes de TS.
Los casos de uso de coincidencia de patrones ahora están cubiertos por #21496, dejando... los casos en los que queremos calcular tipos basados ​​en lambdas proporcionadas por el usuario, por ejemplo, curry , composición de función, map , reduce , edición de lentes basada en una lambda... lo divertido. :)

PD @thorn0 : ¡Creo que su caso de uso de Angular se puede llenar con ReturnType (#21496) ahora!

Creo que esto debería cubrirse permitiendo estas expresiones.
prop2: tipo de este.prop1.gran.complejo;

@mhegazy ¿Hay un problema separado para rastrear esto?
Es molesto que typeof funcione para locales pero no para propiedades, mientras que funciona para propiedades estáticas.

class A {
    x: number;
    static y: number;
    f() {
        const a: number = 1;
        const b: typeof a = 2; // OK
        const c: this.x = 3; // No :(
        const d: this['x'] = 3; // OK
        const e: typeof A.y = 4 // OK
    }
}

@NN --- siempre puedes usar tipos indexados para esto:

this['x']

@ cameron-martin No funciona. Patio de recreo

@ tycho01 el nuevo tipo de inferencia y condicionales son geniales, pero también súper molestos porque no funcionan para la sobrecarga de funciones. La razón dada fue que necesita algo como esto typeof para resolver el tipo de función de los posibles.

@NN solo usa const d: this['x'] = 3;

Oh bien :)

@NN--- o usar

class A {
    x: number;
    static y: number;
    f() {
        const self = this;
        const a: number = 1;
        const b: typeof a = 2; // OK
        const c: typeof self.x = 3; // OK
        const d: typeof self['x'] = 3; // OK
        const e: typeof A.y = 4 // OK
    }
}

@tsofist Soy consciente de que lo local funciona, pero me parece feo.
Es lo mismo que guardar manualmente 'esto' para la devolución de llamada 'función () {}' en lugar de usar lambda con captura implícita.

@NN

este feo

Sí, esta es solo una opción :)

Los tipos condicionales ahora hacen que esto sea en gran medida irrelevante, ya que puede escribir ReturnTypeOf<T> junto con otros alias específicos si desea validar un conjunto particular de argumentos. No pueden hacer una resolución de sobrecarga, pero no creemos que esta característica valga la complejidad solo para ese caso de uso.

@RyanCavanaugh Creo que te refieres a ReturnType<T> ?

@RyanCavanaugh eso es desafortunado: la resolución de sobrecarga es lo que realmente necesitaba. ¿Hay algún otro problema para rastrear la adición de una resolución de sobrecarga a los tipos condicionales/de inferencia?

Deberías poder escribirlo:

type Return1<A1, T extends (a: A1) => any> = T extends (a: A1) => infer R ? R : any;
type Return2<A1, A2, T extends (a: A1, a: A2) => any> = T extends (a: A1, a: A2) => infer R ? R : any;

declare function something(a: number): number;
declare function something(a: string): string;
declare function something(a: number, b: string): boolean;

type A = Return1<number, something>; // number
type B = Return1<string, something>; // string
type C = Return2<number, string, something>; // boolean

Sin embargo, no lo he probado, y necesitarías un ayudante separado para cada número de argumentos.

@ForbesLindesay : el something es actualmente una variable de nivel de expresión; hacer referencia a él, por ejemplo, con typeof aquí (o declararlo como una interfaz) soluciona eso. Sin embargo, en realidad no estoy logrando que produzca los tipos de devolución apropiados (en 2.8.0-dev.20180318 ).

@ForbesLindesay , lamentablemente, no creo que eso funcione; el mecanismo de inferencia elegirá la sobrecarga del método _last_:

type Funcs = ((p1: string, p2: string) => void) & ((p1: number) => void);

type FuncPromise1<T> = T extends (p1: infer P1) => void ? (p1: P1) => Promise<[P1]> : never;
type FuncPromise2<T> = T extends (p1: infer P1, p2: infer P2) => void ? (p1: P1, p2: P2) => Promise<[P1, P2]> : never;

let foo: FuncPromise1<Funcs> & FuncPromise2<Funcs>;

image

Sin embargo , el mecanismo de inferencia _es_ capaz de manejar uniones de tuplas:

type Tuples = [string, string] | [number];

type TuplePromise1<T> = T extends [infer P1] ?  (p1: P1) => Promise<[P1]> : never;
type TuplePromise2<T> = T extends [infer P1, infer P2] ? (p1: P1, p2: P2) => Promise<[P1, P2]> : never;

let foo: TuplePromise1<Tuples> & TuplePromise2<Tuples>;

image

tal vez necesitemos algo para permitir la sobrecarga -> objeto y función -> objeto, desenvolver. Haga el mapeo de tipos y la inferencia allí, luego regrese a una función y sobrecargue.

@MeirionHughes :

tal vez necesitemos algo para permitir la sobrecarga -> objeto y función -> objeto, desenvolver. Haga el mapeo de tipos y la inferencia allí, luego regrese a una función y sobrecargue.

¿Como (a: number, b?: string) => boolean -> { a: number, b?: string } ? Todavía no podemos obtener nombres de parámetros así, pero conceptualmente esto se vuelve más difícil para los parámetros restantes ( (a: number, ...b: string[]) => boolean ), también porque no podemos usar tipos de objetos para ordenar.

El orden probablemente importe más que los nombres, y podemos convertir entre parámetros y tuplas. Los diferenciales/opcionales también pueden complicarlo un poco.

Esto reduce el problema a extraer las sobrecargas. Las sobrecargas deben ser una intersección de tipos de funciones como ((a: number) => 123) & ((s: string) => 'hi') , entonces el problema es cómo 'desenvolver' un tipo de intersección (en, por ejemplo, un tipo de tupla) -- a partir de ahora, no tenemos eso.

Encuentro este camino insatisfactorio ya que abordaría el caso de uso de sobrecarga pero no diría genéricos, pero sí. Desenvolver la intersección seguía siendo una brecha de cualquier manera.

Dado que este problema ya está cerrado, ¿hay una nueva propuesta todavía para las piezas que aún faltan? ¿Como una forma de lidiar con el tipo de retorno dependiendo de los argumentos?

Dado que este problema ya está cerrado, ¿hay una nueva propuesta todavía para las piezas que aún faltan?

ninguno que yo sepa.

¿Como una forma de lidiar con el tipo de retorno dependiendo de los argumentos?

no creo que tengamos problemas para rastrear los tipos de llamadas.

¿Existe un apoyo preliminar para la idea de simplemente agregar una aplicación de función de nivel de tipo? Podría escribir una propuesta para eso. Sintácticamente creo que ese es el camino más fácil.

type MyType<A> = {
    foo: A
}

type Wrap = {
    <T>(maybe: MyType<T>): MyType<T>;
    (maybe: any): MyType<any>;
}

type Naive = ReturnType<Wrap>; // Naive = { foo: any }
type Proposed1 = Wrap(maybe: number); // Proposed1 = { foo: number }
type Proposed2 = Wrap(maybe: MyType<number>); // Proposed2 = { foo: number }
type Proposed3 = (<T>(maybe: T) => MyType<T>)(maybe: number) // Proposed3 = { foo: number }

Casos de borde:

const foo = <T>(a: T) => T:

type Edge1 = (typeof foo)(a: number) // Probably trivial?

type Foo = {
    <T>(a: string): T
}

type Edge2 = Foo<number>(a: string) // Should this be allowed? Probably not, because:

type Bar<A> = {
    (a: string): A
}

type Edge3 = Bar<number>(a: string) // Things are getting strange

interface Baz<A> {
    <T>(a: T): T | A
}

type Edge4 = Baz<number>(a: string) // What is this?

¿Existe un apoyo preliminar para la idea de simplemente agregar una aplicación de función de nivel de tipo? Podría escribir una propuesta para eso. Sintácticamente creo que ese es el camino más fácil.

No por el momento. Realmente no queremos colocar una resolución de sobrecarga en un espacio de tipo de orden superior; el proceso es bastante complicado e incluye varias pasadas para inferir parámetros de tipo y argumentos de tipo contextual, etc. Hacer esto en un orden superior es primero mucho trabajo, y segundo planteará desafíos de rendimiento que no estamos preparados para manejar en el de momento.

¿@mhegazy ha cambiado en absoluto la postura del equipo sobre esto, dado el trabajo reciente en #24897?

Parece haber bastantes problemas en torno a cuyas soluciones podrían reducirse a un tipo $Call , y un tipo $Call abriría la puerta a una forma relativamente sencilla de emular tipos de tipos superiores; consulte https://gist.github.com/hallettj/0fde5bd4c3ce5a5f6d50db6236aaa39e por ejemplo (reemplace el uso de $PropertyType y $ObjMap con $Call ). EDITAR: Ejemplo adicional: https://github.com/facebook/flow/issues/30#issuecomment -346674472

Podría decirse que tal característica estaría en línea con el historial de TypeScript de encontrar una solución común razonable para muchos problemas, ¿no?

Los tipos condicionales ahora hacen que esto sea en gran medida irrelevante, ya que puede escribir ReturnTypeOf<T> junto con otros alias específicos si desea validar un conjunto particular de argumentos. No pueden hacer una resolución de sobrecarga, pero no creemos que esta característica valga la complejidad solo para ese caso de uso.

@RyanCavanaugh @mhegazy

Estoy de acuerdo en que es posible hacer cosas usando tipos condicionales. Creo que no traería mucha complejidad adicional en el compilador si reescribiéramos User.avatar a User extends { avatar: infer T } ? T : never ? Entonces, por ejemplo, podríamos escribir

export type Avatar = User extends { avatar: infer T } ? T : never;

como

export type Avatar = User.avatar;

para mejorar la legibilidad.

Ejemplo completo

Supongamos que cargamos y transformamos algunos datos, y terminamos con una función findUser como esta

export function findUser() {
  return {
    username: 'johndoe',
    avatar: {
      lg: '1.jpg',
      s: '2.jpg'
    },
    repos: [
      {
        name: 'ts-demo',
        stats: {
          stars: 42,
          forks: 4
        },
        pull_requests: [
          { date: '2019-08-19', tags: ['bug', 'agreed-to-cla'] },
          { date: '2019-08-10', tags: ['bug', 'includes-tests'] },
          { date: '2019-08-07', tags: ['feature'] }
        ]
      }
    ]
  };
}

Gracias a la inferencia de tipos mapeados, podemos extraer el tipo de la función así:

export type User = ReturnType<typeof findUser>;
export type Avatar = User extends { avatar: infer T } ? T : never;

Sugerencia: esto debería evaluar lo mismo

export type Avatar = User.avatar;

Además, podríamos incluso afirmar que User.avatar no debe ser del tipo never .

Más ejemplos

export type Repositories = User extends { repos: infer T } ? T : never;
export type Repository = User extends { repos: (infer T)[] } ? T : never;
export type RepositoryStats = Repository extends { stats: infer T } ? T : never;
export type PullRequests = Repository extends { pull_requests: (infer T)[] } ? T : never;
export type PullRequest = Repository extends { pull_requests: (infer T)[] } ? T : never;
export type Tags = PullRequest extends { tags: infer T } ? T : never;
export type Tag = PullRequest extends { tags: (infer T)[] } ? T : never;
export type Repositories = User.repos;
export type Repository = User.repos[];
export type RepositoryStats = User.repos[].stats;
export type PullRequests = User.repos[].pull_requests;
export type PullRequest = User.repos[].pull_requests[];
export type Tags = User.repos[].pull_requests[].tags;
export type Tag = User.repos[].pull_requests[].tags[];

Al mapear una propiedad anidada de una sola vez, no está muy claro lo que está sucediendo

export type Tag2 = User extends { repos: { pull_requests: { tags: (infer T)[] }[] }[] } ? T : never;

Esto lo aclararía mucho

export type Tag = User.repos[].pull_requests[].tags[];

Estuche de esquina

export class Hello {
  static world = 'world';
  world = 42;
}
export type ThisWillBeANumber = Hello extends { world: infer T } ? T : never;
export type ThisWillBeANumber = Hello.world;
export type ThisWillBeAString = (typeof Hello) extends { world: infer T } ? T : never;
export type ThisWillBeAString = (typeof Hello).world;

@lukaselmer Parece que solo quieres

export type Avatar = User["avatar"];

que funciona hoy

@lukaselmer Parece que solo quieres

export type Avatar = User["avatar"];

que funciona hoy

Eso es exactamente lo que estaba buscando. Lo estaba buscando en la documentación , pero no lo encontré. ¡Gracias!

¿Es esto parte del manual o hay alguna documentación oficial sobre cómo funciona? Yo mismo estoy bastante familiarizado con su uso, pero cuando trato de dirigir a las personas a la documentación, todo lo que puedo encontrar es tipo de guardias, que en realidad es completamente diferente.

Entonces, me di cuenta de que esta propuesta rebotó a partir de 2015 y uno de los objetivos originales era obtener de alguna manera el tipo de propiedad única de una interfaz.

interface a {
 foo: bar;
 /* more types */
}

const example = (fooNeeded: [magic] a.foo ) => {};

¿Tengo razón al suponer que esto todavía no es posible 5 años después?

@MFry Creo que estás buscando esta sintaxis: a['foo']

¿Sabemos si ya hay una solución para esto?

Estoy tratando de obtener algo como esto:

declare function something<A, B>(): void;

type Payload = string;

const hello = something<{}, Payload>();

declare function doThing<T extends ReturnType<typeof something>>(arg: T): { payload: unknown };

doThing(hello).payload === 123; // this should validate to a string aka type Payload

https://www.typescriptlang.org/play/index.html?ts=4.0.0-dev.20200512#code/CYUwxgNghgTiAEAzArgOzAFwJYHtXwGccBbEDACy1QHMAeAQQBp4AhAPgAoBKALngDccWYAG4AUGIwBPAA4IAClCkQcUYPAC8hDDCrVxYsHgIZ45EBBWbCJMpRq0A3gF9mi5auCcuB0JFgIKOjYePDAOAAq9nQR8CAAHhggqMAE8ABKZMgwqBGyILTScjiINqQUemycsNR8EbzwjvAySipqfGgA1qg4AO74zr6R0RzmljhcAHQtHmqaGloAjABMAMwi8AD0m -AVaQTkOMgQ6vxQEMJQSbs48FDaujR3nfdFCq2eYkA

Hola @maraisr , no estoy 100% seguro de lo que estás tratando de lograr. En su ejemplo something toma dos tipos pero no los usa, y hello es el valor de retorno de algo que siempre será void ; Así que doThing nunca ve el tipo string en ningún momento.

¿Tal vez algo como lo siguiente es lo que quieres?

declare function something<ReturnType>(): ReturnType;

type Payload = string;

const hello = () => something<Payload>();

declare function doThing<F extends () => any>(f: F): { payload: ReturnType<F> };

doThing(hello).payload === 'a string';

Ah, sí, lo siento mucho. ¡¡Gracias por su rápida respuesta!! :100: @acutmore

El void fue solo para indicar que el tipo de retorno de esa función es irrelevante. Esos 2 tipos se reenvían a otros tipos genéricos, que finalmente se usan en los argumentos.

algo como:

declare function something<A, B>(a: MyComplexGeneric<A>, b: B[]): { somethingA: number, somethingB: number };

// Those 2 generics influence the return object so they do get used as such. And the 2 arguments are roughly that. Its an object and an array, more-or-less. 

A mi función doThing realmente no le importa cuál es el primer genérico ( A ), pero sí le importa cuál es el segundo ( B ).

Vea que something en mi propio caso de uso tiene un efecto secundario que el doThing lee.

Entonces, no puedo simplemente obtener el ReturnType de una función; de alguna manera, necesito succionar el genérico de una función creada.


Si cree que esta consulta está más allá del alcance de este problema, continuaré mi viaje en StackOverflow.

@maraisr gracias por la información adicional.

Si desea doThing para poder obtener el tipo original B de something , entonces debe pasarlo a hello de alguna manera. TypeScript solo está mirando hello y sin ayuda no sabrá que es el tipo de devolución de something .

Esta es una forma de hacerlo:

/** Create a type that can store some extra type information **/
interface SomethingResult<T> {
    __$$__: T;
    somethingA: number;
    somethingB: number;
}

declare function something<A, B>(): SomethingResult<B>;

type Payload = string;

const hello = something<{}, Payload>();

declare function doThing<Result extends SomethingResult<any>>(arg: Result): { payload: Result['__$$__'] };

doThing(hello).payload === 1123; // error because `payload` is of type string
interface User {
  avatar: string;
}

interface UserData {
  someAvatar: User['avatar'];
}

@RyanCavanaugh ¿Por qué está cerrado? Los tipos condicionales no resuelven este y muchos otros casos de uso, y si esto se fusiona, haría muchas cosas posibles.

Estoy trabajando en una función que puede convertir cualquier llamada de método en una versión "sin puntos" (ejemplo: [].map(() => n > 5) se convierte en map(() => n > 5)([]) y lo único que falta es los tipos condicionales y infer no puede detectar genéricos, por lo que en funciones genéricas algunos tipos aparecerán como unknown .

Si pudiera "llamar" a las funciones para obtener el tipo ( typeof myFunc(() => Either<string,number>) ), sería posible tener esta funcionalidad (que actualmente es imposible) y hacer que muchas otras cosas sean mucho más fáciles de hacer (HKT, etc.) .)

¿Es muy alta la complejidad para poder $Call una función (como en el flujo)? Siento que mecanografiado ya lo hace automáticamente.

@nythrox , no creemos que la confusión sintáctica a la que esto podría conducir sea superada por los casos en los que lo necesita para llegar a algún tipo. El caso específico de resolver una expresión de llamada se rastrea en otro lugar; la propuesta en el OP de "permitir cualquier expresión en absoluto" no es algo que pensemos que sería una buena opción para el idioma.

@RyanCavanaugh oh está bien, entiendo. Gracias por la respuesta, ¿sabe qué problemas están rastreando la resolución de una llamada de función?

He buscado un poco y no he encontrado ningún problema para un tipo de utilidad de llamada de función; la única referencia a uno que encontré fue en #20352, que acaba de vincular a este problema.

El caso específico de resolver una expresión de llamada se rastrea en otro lugar

@RyanCavanaugh ¿Le importa vincular a otro lugar? 🙂

@tjjfvi #37181 se trata más específicamente de resolver una función en función de sus entradas. Podría ser lo que estás buscando.

@acutmore Eso está un poco en la línea de lo que estaba buscando, aunque estaba hablando específicamente de una utilidad de flujo $Call u otra sintaxis para poder implementarla. El enfoque sugerido allí es extraño, pero gracias por el enlace.

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

Zlatkovsky picture Zlatkovsky  ·  3Comentarios

bgrieder picture bgrieder  ·  3Comentarios

wmaurer picture wmaurer  ·  3Comentarios

kyasbal-1994 picture kyasbal-1994  ·  3Comentarios

blendsdk picture blendsdk  ·  3Comentarios