Typescript: Sugerencia: tipo no anulable

Creado en 22 jul. 2014  ·  358Comentarios  ·  Fuente: microsoft/TypeScript

Introduce dos nuevas sintaxis para la declaración de tipos basada en JSDoc

var myString: !string = 'hello world'; //non-nullable
var myString1: ?string = 'hello world'; // nullable 
var myString2: string = 'hello world'; // nullable 
var myString3 = 'hello world'; // nullable

por defecto, el tipo es anulable.

Dos nuevos indicadores del compilador:

  • inferNonNullableType hace que el compilador infiera un tipo no anulable:
var myString3 = 'hello world' // typeof myString is '!string', non-nullable
  • nonNullableTypeByDefault (supongo que podría haber un nombre mejor):
var myString: !string = 'hello world'; // non-nullable
var myString1: string = 'hello world'; // non-nullable 
var myString2: ?string = 'hello world'; // nullable 
var myString3 = 'hello world' // non-nullable
Committed Fixed Suggestion

Comentario más útil

Lo siento si comento sobre un problema cerrado, pero no conozco un lugar mejor donde preguntar y no creo que valga la pena un nuevo número si no hay interés.
¿Sería factible manejar nulos implícitos por archivo?
Como, manejar un montón de archivos td con noImplicitNull (porque provienen de definitivamente tipo y fueron concebidos de esa manera) pero manejar mi fuente como implicitNull?
¿Alguien podría encontrar esto útil?

Todos 358 comentarios

Sugiero usar un tipo que no sea string como ejemplo, ya que por naturaleza es anulable. :PAGS
Puedo percibir que los tipos que no aceptan valores NULL son problemáticos ya que el usuario y el compilador de "!" espera que el tipo siempre sea no nulo, lo que nunca se puede afirmar realmente en JavaScript. Un usuario puede definir algo como esto:

function(myNonNull:!myClass):void {
  myNonNull.foo();
}

Y debido a que está definido como no nulo, todo puede ser feliz para el compilador, pero luego alguien más que lo usa en javascript pasa algo nulo y kaboom.

Ahora que dicho esto, tal vez la solución podría ser que para los métodos de cara al público, automáticamente podría afirmar que no es nulo. Pero entonces el compilador también podría afirmar que no puede tener propiedades públicas (o privadas en realidad) que puedan tener una declaración! Non null ya que no se podrían hacer cumplir.

Esto puede profundizar en la discusión de los contratos de código para que esto se aplique correctamente.

Perdone a mis críticos, creo que hay muy poca necesidad de tipos que no aceptan valores NULL si / tan pronto como los tipos de datos algebraicos estén aquí. La razón por la que la gente usa null para representar un valor faltante es porque no hay mejor manera de hacerlo en JavaScript y en la mayoría de los lenguajes de programación orientada a objetos por igual. Así que los TDA de imágenes ya están aquí. Entonces, en cuanto a las antiguas bibliotecas escritas antes que las no nulables, tenerlas no mejorará la vida. En cuanto a las nuevas bibliotecas, con ADT en su lugar, uno puede modelar con mucha precisión lo que puede tomar un valor de acuerdo con la especificación del dominio empresarial sin utilizar nulos en absoluto. Así que supongo que lo que estoy diciendo es que ADT es una herramienta mucho más poderosa para abordar el mismo problema.

Personalmente, acabo de escribir una pequeña interfaz Maybe<T> y utilizo la disciplina para asegurarme de que las variables de ese tipo nunca sean nulas.

Sugiero usar un tipo que no sea string como ejemplo, ya que por naturaleza es anulable. :PAGS
Puedo percibir que los tipos que no aceptan valores NULL son problemáticos ya que el usuario y el compilador de "!" espera que el tipo siempre sea no nulo, lo que nunca se puede afirmar realmente en JavaScript. Un usuario puede definir algo como esto:

function (myNonNull:! myClass): void {
myNonNull.foo ();
}
Y debido a que está definido como no nulo, todo puede ser feliz para el compilador, pero luego alguien más que lo usa en javascript pasa algo nulo y kaboom.

Realmente no entiendo que también puedas definir:

function myFunc(str: string): int {
 return str && str.length;
}

y si alguien pasa un int a esa función, también terminará con un error, una ventaja del mecanografiado es delegar al compilador pasar cosas que verificaría manualmente en javascript, teniendo otra verificación para nullable / non -El tipo anulable me parece razonable. Por cierto, SaferTypeScript y ClosureCompiler ya hacen ese tipo de verificación.

Con los tipos de unión, podríamos tener una especificación bastante más simple para eso.
Digamos que ahora tenemos un tipo básico 'nulo', podemos tener un modo 'más estricto' donde 'nulo' e 'indefinido' no es compatible con ningún tipo, por lo que si queremos expresar un valor anulable haríamos:

var myNullableString: (null | string);
var myString = "hello";
myNullableString = myString //valid
myString = myNullableString // error null is not assignable to string;

Con el 'modo estricto' activado, el mecanografiado debe verificar que todas las variables que no aceptan valores NULL estén inicializadas, también, por defecto, los parámetros opcionales pueden admitir valores NULL.

var myString: string; // error
var myNullableString: (null | string); // no error

function myString(param1: string, param2?: string) {
  // param1 is string
  // param2 is (null | string)
}

@fdecampredon +1

IIRC de lo que Facebook mostró de Flow, que usa la sintaxis de TypeScript pero con tipos que no aceptan valores NULL de forma predeterminada, admiten una abreviatura de (null | T) como en su publicación original: creo que era ?T o T? .

var myString: string; // error

Eso podría ser bastante molesto en el caso de que desee inicializar una variable condicionalmente, por ejemplo:

var myString: string;
if (x) {
myString = a;
} else if (y) {
myString = b;
} else {
myString = c;
}

En Rust, por ejemplo, esto está bien siempre que el compilador pueda ver que myString se inicializará antes de que se use, pero la inferencia de TypeScript no admite esto en este momento.

Honestamente, hacer algo como var myString = '' lugar de var myString: string no me molesta tanto, pero seguro que ese tipo de regla siempre es posible.

@fdecampredon +1 para esto: me gusta mucho la idea. Para las bases de código que son 100% JavaScript, esta sería una restricción útil solo en tiempo de compilación. (Según tengo entendido, su propuesta no tiene la intención de que el código generado haga cumplir esto).

En cuanto a la abreviatura de (null | string) seguro que ?string está bien.
Y seguro, @johnnyreilly , es solo una verificación de tiempo de compilación

Los tipos de suma hacen que los tipos que no aceptan valores NULL por defecto sean una posibilidad muy interesante. Las propiedades de seguridad de los valores no anulables de forma predeterminada no se pueden exagerar. Los tipos de suma más el planificado "if / typeof desestructuración" (no estoy seguro de cómo debería llamarse) incluso hacen que sea seguro integrar las API que aceptan valores NULL y las API que no aceptan valores NULL.

Sin embargo, hacer que los tipos no admitan valores NULL de forma predeterminada es un gran cambio radical, que requeriría cambiar casi todos los archivos de definición de tipos de terceros existentes. Si bien estoy 100% a favor del cambio radical, ninguna persona puede actualizar las definiciones de tipo que existen en la naturaleza.

Es bueno que se recopile un gran consenso de estas definiciones en el repositorio DefinitelyTyped, pero todavía tengo preocupaciones prácticas sobre esta función.

@samwgoldman, la idea es tener tipos que no nonImplicitAny este indicador podría llamarse strict o nonNullableType . De modo que no habría cambios importantes.

@fdecampredon ¿Qué pasa con las definiciones de tipo para bibliotecas que no son de TypeScript, como las de DefinitelyTyped? El compilador no verifica esas definiciones, por lo que cualquier código de terceros que pueda devolver un valor nulo deberá volver a anotarse para que funcione correctamente.

Puedo imaginar una definición de tipo para una función que actualmente está anotada como "devuelve cadena", pero a veces devuelve nulo. Si dependiera de esa función en mi código nonNullableType 'ed, el compilador no se queja (¿cómo podría hacerlo?) Y mi código ya no es seguro para nulos.

A menos que me falte algo, no creo que esta sea una funcionalidad que se pueda activar y desactivar con una bandera. Me parece que se trata de un cambio semántico de todo o nada para garantizar la interoperabilidad. Sin embargo, me alegraría que se demuestre que estoy equivocado, porque creo que es más probable que ocurra una función de cambio de bandera.

Aparte, todavía no hay mucha información disponible en el compilador Flow de Facebook, pero a partir de la grabación de video de la presentación, parece que optaron por no aceptar nulos de forma predeterminada. Si es así, al menos hay alguna precedencia aquí.

Ok, supongamos que hay una abreviatura ? type para type | null | undefined .

@fdecampredon ¿Qué pasa con las definiciones de tipo para bibliotecas que no son de TypeScript, como las de DefinitelyTyped? El compilador no verifica esas definiciones, por lo que cualquier código de terceros que pueda devolver un valor nulo deberá volver a anotarse para que funcione correctamente.

Puedo imaginar una definición de tipo para una función que actualmente está anotada como "devuelve cadena", pero a veces devuelve nulo. Si dependiera de esa función en mi código nonNullableType'ed, el compilador no se queja (¿cómo podría hacerlo?) Y mi código ya no es seguro para nulos.

No veo el problema, seguro que algunos archivos de definición no serán válidos con el modo nonNullableType , pero la mayor parte del tiempo una buena biblioteca evita devolver null o undefined por lo que la definición seguirá siendo correcta en la mayoría de los casos.
De todos modos, personalmente, rara vez puedo elegir una definición DefinitelyTyped sin tener que verificarla / modificarla, solo tendrá un poco de trabajo adicional para agregar un prefijo ? con algunas definiciones.

A menos que me falte algo, no creo que esta sea una funcionalidad que se pueda activar y desactivar con una bandera. Me parece que se trata de un cambio semántico de todo o nada para garantizar la interoperabilidad. Sin embargo, me alegraría que se demuestre que estoy equivocado, porque creo que es más probable que ocurra una función de cambio de bandera.

No veo por qué no podríamos tener una función de cambio de bandera, las reglas serían simples:

  • en modo normal ? string es equivalente a string y null o undefined son asignables a todos los tipos
  • en modo nonNullableType ? string es equivalente a string | null | undefined y null o undefined no se pueden asignar a ningún otro tipo que null o undefined

¿Dónde está la incompatibilidad con una función de cambio de bandera?

Las banderas que cambian la semántica de un idioma son algo peligroso. Un problema es que los efectos son potencialmente muy no locales:

function fn(x: string): number;
function fn(x: number|null): string;

function foo() {
    return fn(null);
}

var x = foo(); // x: number or x: string?

Es importante que alguien que esté mirando un fragmento de código pueda "seguir" el sistema de tipos y comprender las inferencias que se están haciendo. Si empezamos a tener un montón de banderas que cambian las reglas del lenguaje, esto se vuelve imposible.

La única forma segura de hacer es mantener la misma semántica de asignabilidad y cambiar lo que es un error y lo que no depende de una bandera, muy parecido a cómo funciona noImplicitAny hoy.

Sé que rompería la retrocompatibilidad, y entiendo el punto de vista de @RyanCavanaugh , pero después de probar que con flowtype , honestamente, es una característica invaluable, espero que termine siendo parte de mecanografiado.

Además del comentario de RyanCavanaugh -> Por lo que leí en alguna parte, la especificación / propuesta de ES7 menciona el uso de la sobrecarga de funciones (mismo nombre de función pero tipo de datos de parámetro de entrada diferente). Esa es una característica muy necesaria para Javascript.

De los documentos de flujo :

Flow considera que nulo es un valor distinto que no forma parte de ningún otro tipo

var o = null;
print(o.x); // Error: Property cannot be accessed on possibly null value

Se puede hacer que cualquier tipo T incluya nulo (y el valor relacionado indefinido) escribiendo? T

var o: ?string = null;
print(o.length); // Error: Property cannot be accessed on possibly null or undefined value

[Flow] comprende los efectos de algunas pruebas de tipo dinámico

(es decir, en la jerga de TS entiende los tipos de guardias)

var o: ?string = null;
if (o == null) {
  o = 'hello';
}
print(o.length); // Okay, because of the null check

Limitaciones

  • Las comprobaciones de las propiedades de los objetos están limitadas debido a la posibilidad de crear un alias:

Además de poder ajustar tipos de variables locales, Flow a veces también puede ajustar tipos de propiedades de objeto, especialmente cuando no hay operaciones intermedias entre una verificación y un uso. Sin embargo, en general, el alias de objetos limita el alcance de esta forma de razonamiento, ya que una verificación de la propiedad de un objeto puede ser invalidada por una escritura en esa propiedad a través de un alias, y es difícil para un análisis estático rastrear los alias con precisión.

  • Las comprobaciones de tipo guard-style pueden ser redundantes para las propiedades del objeto.

[D] no espere que un campo anulable sea reconocido como no nulo en algún método porque se realiza una verificación nula en algún otro método en su código, incluso cuando le quede claro que la verificación nula es suficiente para la seguridad en tiempo de ejecución (digamos, porque sabe que las llamadas al primer método siempre siguen a las llamadas al último método).

  • undefined no está marcado.

Los valores indefinidos, al igual que los nulos, también pueden causar problemas. Desafortunadamente, los valores indefinidos son omnipresentes en JavaScript y es difícil evitarlos sin afectar gravemente la usabilidad del lenguaje. Por ejemplo, las matrices pueden tener huecos para elementos; las propiedades del objeto se pueden agregar y eliminar dinámicamente. Flow hace una compensación en este caso: detecta variables locales indefinidas y valores de retorno, pero ignora la posibilidad de que no estén definidos como resultado de la propiedad del objeto y los accesos a los elementos de la matriz.

¿Qué pasa si la opción se agrega al mismo tiempo al introducir el tipo null (y la abreviatura del signo de interrogación)? La presencia de un tipo null en un archivo obligaría al compilador a entrar en modo no anulable para ese archivo incluso si la bandera no está presente en la línea de comando. ¿O es demasiado mágico?

@jbondc parece bueno. sin embargo, el problema con eso es que terminará con ! todas partes: p

It's tempting to want to change JavaScript but the reality is a 'string' is nullable or can be undefined.

¿Qué significa esto? No hay tipos estáticos en js. Entonces, sí, las cadenas son "anulables", pero no olvidemos que también son numerables, objetables y engañosas, etc. Cualquier valor puede tener cualquier tipo.

Entonces, al colocar un sistema de tipos estáticos sobre javascript, elegir si los tipos estáticos son anulables o no es solo una decisión de diseño. Me parece que los tipos que no aceptan valores NULL son un mejor valor predeterminado, porque generalmente solo en casos especiales desea una firma de función, por ejemplo, para aceptar un valor nulo además del tipo especificado.

Las directivas como "use estricto" que provocan cambios de ámbito en la semántica ya forman parte del lenguaje; Creo que sería razonable tener una directiva de "uso de tipos no anulables" en TypeScript.

@metaweta No creo que sea suficiente, por ejemplo, qué sucede si un _módulo no nulo_ consume uno anulable:

//module A
export function getData(): string[] {
  return null;
}
//module B
'use nonnull'
import A = require('./A');

var data: string[] = A.getData();

data en el módulo B es de hecho anulable, pero dado que 'use nonnull' no se usó en el módulo A, ¿deberíamos informar un error?
No veo una manera de resolver ese problema con la función basada en directivas.

Sí,

var data: string[] = A.getData();

causaría un error. En su lugar, tendría que proporcionar un valor predeterminado para cuando getData() devuelva null :

var data: string[] = A.getData() || [];

@metaweta ok, pero ¿cómo sabes que es un error? :)
el tipo de getData sigue siendo '() => string []', ¿trataría automáticamente todo lo que proviene de un 'módulo anulable' como 'anulable'?

Sí, exactamente (a menos que un tipo del módulo que acepta valores NULL esté marcado explícitamente de otro modo).

Parece que ahora desea una marca por archivo que indique si el archivo predeterminado es anulable o no.

Personalmente, creo que es un poco tarde para introducir este cambio, y @RyanCavanaugh tiene razón, el cambio haría que Typecript sea menos predecible, ya que no podría determinar lo que está sucediendo con solo mirar un archivo.

¿Los proyectos comienzan con esta marca de compilador activada o desactivada de forma predeterminada? Si alguien está trabajando en un proyecto que no acepta valores NULL predeterminados y crea / cambia a uno predeterminado, ¿eso causará confusión?
Actualmente trabajo con No Implicit Any en la mayoría de mis proyectos, y cada vez que me encuentro con un proyecto que no tiene esa opción activada, me sorprende.

El no impicit any es bueno, pero en términos de banderas que cambian la forma en que se comporta el idioma, creo que esa debería ser la línea. Algo más que eso y las personas que están trabajando en múltiples proyectos iniciados por diferentes personas con diferentes reglas van a perder mucha productividad debido a suposiciones falsas y deslices.

@RyanCavanaugh estaba preocupado por la no localidad, y las directivas tienen un alcance léxico. No puede obtener más local a menos que anote cada sitio. No estoy particularmente a favor de la directiva; Solo estaba señalando que la opción existe y que es al menos tan razonable como "usar estricto" en ES5. Personalmente, estoy a favor de los tipos que no aceptan valores NULL de forma predeterminada, pero prácticamente es demasiado tarde para eso. Dadas esas limitaciones, estoy a favor de usar! de algun modo. La propuesta de @jbondc le permite distinguir nulo de indefinido; Dado que los backends de Java continúan haciendo que la gente use ambos valores, me parece el más útil.

Lo siento si no fui claro, estaba de acuerdo con Ryan y agregué mis propias preocupaciones.

Honestamente, si agregar use not-null es el precio por evitar todas las excepciones de puntero nulo, lo pagaría sin ningún problema, considerando que null o undefined como asignables a cualquier tipo es el peor error ese mecanografiado hecho en mi opinión.

@jbondc No he usado 'use estricto' y, por lo tanto, estoy haciendo algunas suposiciones,

No nulo no afecta la sintaxis que escribe el programador, sino las capacidades del próximo programador que intente usar ese código (asumiendo que el creador y el usuario son personas separadas).

Entonces el código:

function myfoo (mynumber: number) {
    return !!mynumber;
} 

(escribiendo en un teléfono, por lo que puede estar mal)
Es un código válido tanto en un proyecto normal como en un proyecto no nulo. La única forma en que el codificador sabría si el código funciona o no es si observa los argumentos de la línea de comandos.

En el trabajo, tenemos un proyecto de prueba (que incluye la creación de prototipos de nuevas funciones) y un proyecto principal (con nuestro código real). Cuando los prototipos están listos para ser movidos de un proyecto a otro (típicamente con refractores grandes), no habría errores en el código, pero errores en el uso del código. Este comportamiento es diferente a ninguno implícito y el uso estricto, que ambos cometerían un error de inmediato.

Ahora tengo bastante influencia en estos proyectos, por lo que puedo advertir a las personas a cargo que no utilicen esta nueva 'función' porque no ahorraría tiempo, pero no tengo esa capacidad en todos los proyectos en trabajo.
Si queremos habilitar esta característica incluso en un proyecto, entonces tenemos que habilitarla en todos nuestros otros proyectos porque tenemos una cantidad muy significativa de código compartido y migración de código entre proyectos, y esta 'característica' nos causaría mucho de tiempo retrocediendo y 'arreglando' funciones que ya estaban terminadas.

Correcto @jbondc. @Griffork : Lo siento, no

"use not-null";
// All types in this program production (essentially a single file) are not null

versus

function f(n: number) {
  "use not-null";
  // n is not null and local variables are not null
  function g(s: string) {
    // s is not null because g is defined in the scope of f
    return s.length;
  }
  return n.toFixed(2);
}

function h(n: number) {
  // n may be null
  if (n) { return n.toFixed(3); }
  else { return null; }
}

Los tipos que no aceptan valores NULL son inútiles. Los tipos que no aceptan valores NULL son útiles. Son inútiles. ¡Inútil! No te das cuenta, pero realmente no los necesitas. Tiene muy poco sentido restringirse proclamando que de ahora en adelante no usaremos NULL. ¿Cómo representaría un valor faltante, por ejemplo, en una situación en la que intenta encontrar una subcadena que no está allí? No poder expresar un valor faltante (lo que hace NULL ahora por ahora) no va a resolver su problema. Cambiará un mundo severo con NULL en todas partes por uno igualmente severo sin valores perdidos en absoluto. Lo que realmente necesita se llaman tipos de datos algebraicos que (entre muchas otras cosas interesantes) cuentan con la capacidad de representar un valor faltante (lo que está buscando en primer lugar y lo que está representado por NULL en el mundo imperativo). Estoy firmemente en contra de agregar no nulables al lenguaje, porque parece una basura sintáctica / semántica inútil que es una solución ingenua e incómoda para un problema bien conocido. Lea sobre los opcionales en F # y Maybe en Haskell, así como las variantes (también conocidas como uniones etiquetadas, uniones discriminadas) y la coincidencia de patrones.

@ aleksey-bykov Parece que no sabe que JavaScript tiene dos valores nulos, undefined y null . El null en JavaScript solo se devuelve en una expresión regular no coincidente y cuando se serializa una fecha en JSON. La única razón por la que está en el lenguaje fue por la interacción con los subprogramas de Java. Las variables que se han declarado pero no inicializadas son undefined , no null . Las propiedades que faltan en un objeto devuelven undefined , no null . Si desea explícitamente que undefined sea ​​un valor válido, puede probar propName in obj . Si desea verificar si una propiedad existe en el objeto en sí en lugar de si es heredada, use obj.hasOwnProperty(propName) . Las subcadenas que faltan devuelven -1: 'abc'.indexOf('d') === -1 .

En Haskell, Maybe es útil precisamente porque no existe un subtipo universal. El tipo inferior de Haskell representa la no terminación, no un subtipo universal. Estoy de acuerdo en que se necesitan tipos de datos algebraicos, pero si quiero un árbol etiquetado con números enteros, quiero que cada nodo tenga un número entero, no null o undefined . Si quiero esos, usaré un árbol etiquetado como Maybe int o una cremallera.

Si adoptamos una directiva "use not-null", también me gustaría "use not-void" (ni nulo ni indefinido).

Si desea garantizar su propio código de nulos, simplemente prohíba el nulo
literales. Es mucho más fácil que desarrollar tipos que no aceptan valores NULL. Indefinido es un
un poco más complicado, pero si sabes de dónde vienen, entonces
sabes cómo evitarlos. ¡La parte inferior en Haskell es invaluable! deseo
JavaScript (TypeScript) tenía un supertipo global sin valor. lo extraño
mal cuando necesito lanzar una expresión. He estado usando TypeScript
desde v 0.8 y nunca usó nulos y mucho menos tenía una necesidad de ellos. Solo ignoralo
ellos como lo haces con cualquier otra característica de lenguaje inútil como with
declaración.

@ aleksey-bykov Si estoy escribiendo una biblioteca y quiero garantizar que las entradas no sean nulas, tengo que hacer pruebas en tiempo de ejecución en todas partes. Quiero pruebas en tiempo de compilación para él, no es difícil de proporcionar, y tanto Closure como Flow brindan soporte para tipos no nulos / indefinidos.

@metaweta , no puede garantizarse de nulos. Antes de que se compile su código, hay miles de pleaseNonNullablesNumbersOnly(<any> null) formas de hacer llorar su lib: nulls are not supported, you put a null you will get a crash , como un descargo de responsabilidad, no puede garantizarse a sí mismo de todo tipo de personas, pero puede describir su alcance de responsabilidades. En tercer lugar, difícilmente puedo pensar en una biblioteca principal que sea a prueba de balas para cualquier usuario que pueda poner como entrada, sin embargo, sigue siendo muy popular. Entonces, ¿su esfuerzo vale la pena?

@ aleksey-bykov Si los clientes de mi biblioteca también están verificados, entonces ciertamente puedo garantizar que no obtendré un nulo. Ese es el objetivo de TypeScript. Según su razonamiento, no hay necesidad de tipos en absoluto: simplemente "diga alto y claro" en su documentación cuál es el tipo esperado.

Fuera del tema, los nulos son extremadamente valiosos para nosotros porque verificarlos es más rápido que verificarlos con valores indefinidos.
Si bien no los usamos en todas partes, intentamos usarlos siempre que sea posible para representar valores no inicializados y números faltantes.

En tema:
Nunca hemos tenido un problema con nulos que 'escapan' a otro código, pero hemos tenido problemas con la aparición de indefinidos aleatorios o NaN. Creo que una gestión cuidadosa del código es mejor que una bandera en este escenario.

Sin embargo, para la tipificación de bibliotecas sería bueno tener el tipo redundante null para que podamos elegir anotar funciones que pueden devolver null (esto no debería ser impuesto por el compilador, sino por prácticas de codificación).

@metaweta , según mi razonamiento, sus clientes no deberían usar nulos en su base de código, no es tan difícil, haga una búsqueda completa de nulos (distingue entre mayúsculas y minúsculas, palabra completa) y elimínelos todos. ¿Demasiado torpe? Agregue un modificador de compilador --noNullLiteral para la fantasía. Todo lo demás permanece intacto, el mismo código, sin preocupaciones, una solución mucho más liviana con un espacio mínimo. Volviendo a mi punto, suponga que sus tipos no anulables encontraron su camino a TS y están disponibles en 2 sabores diferentes:

  • uno puede usar la sintaxis ! para denotar un tipo que no puede tomar un nulo, por ejemplo string! no puede tomar un nulo
  • noNullsAllowed interruptor está activado

luego obtienes una pieza de json de tu servidor a través de ajax con nulos en todas partes, moral: la naturaleza dinámica de javascript no se puede arreglar con una anotación de tipo en la parte superior

@ aleksey-bykov De la misma manera, si espero un objeto con una propiedad numérica x y obtengo {"x":"foo"} del servidor, el sistema de tipos no podrá evitarlo . Eso es necesariamente un error de tiempo de ejecución y un problema ineludible cuando se usa algo que no sea TypeScript en el servidor.

Sin embargo, si el servidor está escrito en TypeScript y se ejecuta en el nodo, entonces se puede transpilar en presencia de un archivo .d.ts para mi código de front-end y la verificación de tipo garantizará que el servidor nunca enviará JSON con nulos. en él o un objeto cuya propiedad x no es un número.

@metaweta , los tipos que no

Sí, necesitamos esto urgentemente. Ver El error de mil millones de dólares de Hoare. La excepción de puntero nulo (NPE) es el error más común que se encuentra en los lenguajes de programación tipados que no discriminan los tipos que aceptan valores NULL de los que no aceptan valores NULL. Es tan común que Java 8 agregó Optional en un intento desesperado por combatirlo.

Modelar nullables en el sistema de tipos no es solo una preocupación teórica, es una gran mejora. Incluso si tiene mucho cuidado de evitar nulos en su código, es posible que las bibliotecas que use no lo hagan y, por lo tanto, es útil poder modelar sus datos correctamente con el sistema de tipos.

Ahora que hay uniones y protectores de tipos en TS, el sistema de tipos es lo suficientemente poderoso para hacer esto. La pregunta es si se puede hacer de una manera compatible con versiones anteriores. Personalmente, creo que esta característica es lo suficientemente importante como para que TypeScript 2.0 sea incompatible con versiones anteriores en este sentido.

La implementación adecuada de esta función probablemente apuntará a un código que ya está roto en lugar de romper el código existente: simplemente apuntará a las funciones que filtran nulos fuera de ellas (probablemente sin querer) o clases que no inicializan correctamente sus miembros (esta parte es más difícil ya que es posible que el sistema de tipos deba tener en cuenta que los valores de los miembros se inicialicen en los constructores).

No se trata de _no usar_ nulos. Se trata de modelar adecuadamente todos los tipos involucrados. De hecho, esta característica permitiría el uso de nulos de una manera segura, ¡ya no habría razón para evitarlos! El resultado final sería muy similar a la coincidencia de patrones en un tipo algebraico Maybe (excepto que se haría con una verificación if en lugar de una expresión de caso)

Y esto no se trata solo de literales nulos. null y undefined son estructuralmente iguales (afaik, no hay funciones / operadores que funcionen en uno pero no en el otro), por lo que podrían modelarse suficientemente bien con un solo tipo nulo en TS.

@metaweta ,

El valor nulo en JavaScript solo se devuelve en una expresión regular que no coincide y cuando se serializa una fecha en JSON.

No es cierto del todo.

  • La interacción con el DOM produce nulo:
  console.log(window.document.getElementById('nonExistentElement')); // null
  • Como @ aleksey-bykov señaló anteriormente, las operaciones ajax pueden devolver un valor nulo. De hecho, undefined no es un valor JSON válido:
 JSON.parse(undefined); // error
 JSON.parse(null); // okay
 JSON.stringify({ "foo" : undefined}); // "{}"
 JSON.stringify({ "foo" : null}); // '{"foo":null}'

NB: Podemos pretender que undefined se devuelve a través de ajax, porque acceder a una propiedad inexistente dará como resultado undefined , por lo que undefined no se serializa.

Sin embargo, si el servidor está escrito en TypeScript y se ejecuta en el nodo, entonces se puede transpilar en presencia de un archivo .d.ts para mi código de front-end y la verificación de tipo garantizará que el servidor nunca enviará JSON con nulos. en él o un objeto cuya propiedad x no es un número.

Esto no es del todo correcto. Incluso si el servidor está escrito en TypeScipt, no se puede garantizar de ninguna manera que se introduzcan nulos sin verificar cada propiedad de cada objeto obtenido del almacenamiento persistente.

Estoy de acuerdo con @ aleksey-bykov en esto. Si bien sería absolutamente brillante si pudiéramos hacer que TypeScript nos alertara en el momento de la compilación sobre los errores introducidos por nulo e indefinido, me temo que solo inducirá una falsa sensación de confianza y terminará captando trivia mientras las fuentes reales de nulo pasan desapercibidas.

Incluso si el servidor está escrito en TypeScipt, no se puede garantizar de ninguna manera que se introduzcan nulos sin verificar cada propiedad de cada objeto obtenido del almacenamiento persistente.

De hecho, este es un argumento _para_ tipos que no aceptan valores NULL. Si su almacenamiento puede devolver Foo's nulos, entonces el tipo de objeto recuperado de ese almacenamiento es Nullable <Foo>, no Foo. Si luego tiene una función que devuelve que está destinada a devolver Foo, entonces _ debe_ asumir la responsabilidad manejando el nulo (o lo lanza porque sabe mejor o busca nulo).

Si no tuviera tipos que no aceptan valores NULL, no necesariamente pensaría en verificar si hay nulos al devolver el objeto almacenado.

Me temo que solo inducirá una falsa sensación de confianza y terminará atrapando trivialidades mientras las fuentes reales de nulos pasan desapercibidas.

¿Qué tipo de no trivia crees que se perderán los tipos que no aceptan valores NULL?

Esto no es del todo correcto. Incluso si el servidor está escrito en TypeScipt, no se puede garantizar de ninguna manera que se introduzcan nulos sin verificar cada propiedad de cada objeto obtenido del almacenamiento persistente.

Si el almacenamiento persistente admite datos escritos, no sería necesario. Pero incluso si ese no fuera el caso, tendría verificaciones solo en los puntos de obtención de datos y luego tendría una garantía en todo _todo_ su otro código.

Estoy de acuerdo con @ aleksey-bykov en esto. Si bien sería absolutamente brillante si pudiéramos hacer que TypeScript nos alertara en el momento de la compilación sobre los errores introducidos por nulo e indefinido, me temo que solo inducirá una falsa sensación de confianza y terminará captando trivia mientras las fuentes reales de nulo pasan desapercibidas.

El uso de tipos que aceptan valores NULL no sería un requisito absoluto. Si cree que es innecesario modelar los casos en los que un método devuelve nulos porque son "insignificantes", simplemente no podría usar un tipo que acepta valores NULL en esa definición de tipo (y obtener la misma inseguridad de siempre). Pero no hay razón para pensar que este enfoque fallará; hay ejemplos de lenguajes que ya lo han implementado con éxito (por ejemplo, Kotlin de JetBrains )

@ aleksey-bykov Honestamente, se equivocó por completo, una de las mejores cosas de los tipos que no aceptan valores NULL es _la posibilidad de expresar un tipo como que admite valores NULL_.
Con su estrategia de nunca usar nulo para evitar el error de puntero nulo, pierde completamente la posibilidad de usar null por temor a introducir un error, eso es completamente tonto.

Otra cosa, por favor, en una discusión sobre una función de idioma, no vayas con comentarios estúpidos como:

Los tipos que no aceptan valores NULL son inútiles. Los tipos que no aceptan valores NULL son útiles. Son inútiles. ¡Inútil! No te das cuenta, pero realmente no los necesitas.

Eso solo me hace sentir que debería ignorar cualquier cosa que publiques en cualquier lugar de la web, estamos aquí para discutir sobre una característica, puedo entender y aceptar con gusto que tu punto de vista no es el mío, pero no te comportes como un niño.

No estoy en contra de introducir una anotación de tipo no nulo en absoluto. Se ha demostrado que es útil en C # y otros lenguajes.

El OP ha cambiado el curso de la discusión con lo siguiente:

Honestamente, si agregar use not-null es el precio por evitar todas las excepciones de puntero nulo, lo pagaría sin ningún problema, considerando que nulo o indefinido como asignable a cualquier tipo es el peor error que cometió el mecanografiado en mi opinión.

Simplemente estaba señalando la prevalencia de null y undefined en la naturaleza.

También debo agregar que una de las cosas que realmente aprecio de TypeScript es la actitud de laissez-faire del lenguaje. Ha sido un soplo de aire fresco.

Insistir en que los tipos no admiten nulos de forma predeterminada va en contra de ese espíritu.

He estado viendo una serie de argumentos flotando sobre por qué necesitamos o no necesitamos esto y quiero ver si entiendo todos los casos subyacentes que se han discutido hasta ahora:

1) desea saber si una función puede devolver un valor nulo (causado por su patrón de ejecución, no por su escritura).
2) quiero saber si un valor puede ser nulo.
3) desea saber si un objeto de datos puede contener valores nulos.

Ahora, solo hay dos casos en los que puede ocurrir la situación 2: si está usando valores nulos o si una función devuelve un valor nulo. Si elimina todos los nulos de su código (asumiendo que no los quiere), entonces la situación 2 solo puede ocurrir como resultado de la situación 1.

Situación 1 Creo que se resuelve mejor anotando el tipo de retorno de la función para mostrar la presencia de un valor nulo. _Esto no significa que necesite un tipo no nulo_. Puede anotar la función (por ejemplo, utilizando tipos de unión) y no tener tipos que no sean nulos, es como la documentación, pero probablemente más claro en este caso.

La solución 2 también se resuelve con esto.

Esto permite a los programadores que trabajan en su empresa utilizar procesos y estándares para hacer cumplir los tipos nulos, y no el equipo de TypeScript (exactamente de la misma manera en que todo el sistema de mecanografía es opcional, por lo que los tipos explícitos que aceptan valores nulos serían una opción. en).

En cuanto al escenario 3, el contrato entre el servidor y el cliente no es para que Typecript lo haga cumplir, ser capaz de marcar los valores afectados como posiblemente nulos podría ser una mejora, pero eventualmente obtendrá la misma garantía que la de TypeScript. Las herramientas le brindan todos los demás valores (es decir, ninguno a menos que tenga buenos estándares o prácticas de codificación.

(publicación desde el teléfono, perdón por los errores)

@fdecampredon , no es el miedo en primer lugar, es que usar null es innecesario. No los necesito. Como una buena ventaja, obtuve un problema de excepciones de referencias nulas eliminadas. ¿Cómo es posible todo esto? Empleando un tipo suma con un caso vacío. Los tipos de suma son una característica nativa de todos los lenguajes FP como F #, Scala, Haskell y junto con los tipos de productos llamados tipos de datos algebraicos. Los ejemplos estándar de tipos de suma con un caso vacío serían Opcional de F # y Quizás de Haskell. TypeScript no tiene ADT, pero en cambio tiene una discusión en progreso sobre la adición de no nulables que modelarían un caso especial de lo que ADT habría cubierto. Entonces mi mensaje es, deshazte de los no nulables por ADT.

@spion , Malas noticias: F # obtuvo nulos (como herencia de .NET). Buenas noticias: nadie las usa. ¿Cómo no puedes usar null cuando está ahí? Tienen opcionales (como el Java más reciente como mencionaste). Por lo tanto, no necesita null si tiene una mejor opción a su disposición. Esto es lo que estoy sugiriendo en última instancia: deje los nulos (no nulos) en paz, simplemente olvide que existen e implemente ADT como una característica del lenguaje.

Usamos null pero no de la misma manera que lo usan ustedes. El código fuente de mi empresa se divide en 2 partes.

1) Cuando se trata de datos (como datos de bases de datos), reemplazamos nulos con datos en blanco en el momento de la declaración de la variable.

2) Todos los demás, como los objetos programables, usamos null para saber cuándo hay un error y una laguna en el código fuente donde no se crean o asignan objetos que no son objetos de javascript necesarios. Lo indefinido son los problemas de objetos de JavaScript en los que hay un error o una laguna en el código fuente.

Los datos que no queremos que sean nulos porque son datos de clientes y ellos verán redacciones nulas.

@ aleksey-bykov mecanografiado tiene adt con tipo de unión, lo único que falta es la coincidencia de patrones, pero esa es una característica que simplemente no se puede implementar con la filosofía mecanografiada de generar javascript cerca de la fuente original.

Por otro lado, es imposible definir con el tipo de unión la exclusión del valor nulo, por eso necesitamos un tipo no nulo.

@fdecampredon , TS no tiene ADT, tiene uniones que no son tipos de suma, porque, como dijiste, 1. no pueden modelar un caso vacío correctamente ya que no hay un tipo de unidad, 2. no hay una forma confiable de desestructurar ellos

la coincidencia de patrones para ADT se puede implementar de una manera que se alinee estrechamente con el JavaScript generado, de todos modos espero que este argumento no sea un punto de inflexión

@ aleksey-bykov esto no es F #. Es un lenguaje que tiene como objetivo modelar los tipos de JavaScript. Las bibliotecas de JavaScript utilizan valores nulos e indefinidos. Por lo tanto, esos valores deben modelarse con los tipos apropiados. Dado que ni siquiera ES6 admite tipos de datos algebraicos, no tiene sentido usar esa solución dados los objetivos de diseño de TypeScript.

Además, los programadores de JavaScript suelen utilizar comprobaciones if (en conjunción con pruebas de igualdad y typeof), en lugar de la coincidencia de patrones. Estos ya pueden restringir los tipos de unión de TypeScript. A partir de este punto, es solo un pequeño paso para admitir tipos que no aceptan valores NULL con beneficios comparables a los algebraicos Quizás, etc.

Me sorprende que nadie haya mencionado realmente los enormes cambios que puede necesitar lib.d.ts para introducir y los problemas potenciales del estado nulo transitorio de los campos de clase durante la invocación del constructor. Esos son algunos problemas potenciales reales y reales para implementar tipos que no aceptan valores NULL ...

@Griffork, la idea es evitar tener comprobaciones nulas en todas partes de su código. Digamos que tienes la siguiente función

declare function getName(personId:number):string|null;

La idea es que verifique si el nombre es nulo solo una vez y ejecute todo el resto del código sin preocupaciones de que tenga que agregar verificaciones nulas.

function doSomethingWithPersonsName(personId:number) {
  var name = getName(personId);
  if (name != null) return doThingsWith(name); // type guard narrows string|null to just string
  else { return handleNullCase(); }
}

¡Y ahora eres genial! El sistema de tipos garantiza que doThingsWith se llamará con un nombre que no sea nulo

function doThingsWith(name:string) {
  // Lets create some funny versions of the name
  return [uppercasedName(name), fullyLowercased(name), funnyCased(name)]
}

Ninguna de estas funciones necesita verificar si hay un valor nulo, y el código seguirá funcionando sin lanzar. Y, tan pronto como intente pasar una cadena anulable a una de estas funciones, el sistema de tipos le dirá inmediatamente que ha cometido un error:

function justUppercased(personId:number) {
  var name = getName(personId);
  return uppercasedName(name); // error, passing nullable to a function that doesn't check for nulls.
}

Este es un gran beneficio: ahora el sistema de tipos rastrea si las funciones pueden manejar valores que aceptan valores NULL o no, y además, rastrea si realmente lo necesitan o no. Código mucho más limpio, menos controles, más seguridad. Y esto no se trata solo de cadenas: con una biblioteca como las comprobaciones de tipo en tiempo de ejecución, también puede crear guardias de tipo para datos mucho más complejos

Y si no le gusta el seguimiento porque cree que no vale la pena modelar la posibilidad de un valor nulo, puede volver al viejo comportamiento inseguro:

declare function getName(personId:number):string;

y en esos casos, el mecanografiado solo le advertirá si hace algo que obviamente está mal

uppercasedName(null);

Francamente, no veo desventajas, excepto la compatibilidad con versiones anteriores.

@fdecampredon Los tipos de sindicatos son solo eso, sindicatos. No son uniones disjuntas, también conocidas como sumas. Vea el n. ° 186.

@ aleksey-bykov

Tenga en cuenta que agregar un tipo de opción seguirá siendo un cambio importante.

// lib.d.ts
interface Document {
    getElementById(id: string): Maybe<Element>;
}

...

// Code that worked with 1.3
var myCanvas = <HTMLCanvasElement>document.getElementById("myCanvas");
// ... now throws the error that Maybe<Element> can't be cast to an <HTMLCanvasElement>

Después de todo, puede obtener los tipos de opciones de un pobre ahora mismo con desestructuración

class Option<T> {
    hasValue: boolean;
    value: T;
}

var { hasValue, myCanvas: value } = <Option<HTMLCanvasElement>> $("myCanvas");
if (!hasValue) {
    throw new Error("Canvas not found");
}
// Use myCanvas here

pero el único valor de esto es si lib.d.ts (y cualquier otro .d.ts, y todo su código base, pero asumiremos que podemos solucionarlo) también lo usa, de lo contrario, no sabe si un La función que no usa Option puede devolver nulo o no a menos que mires su código.

Tenga en cuenta que tampoco estoy a favor de que los tipos no sean nulos de forma predeterminada (de todos modos, no para TS 1.x). Es un cambio demasiado grande.

Pero digamos que estamos hablando de 2.0. Si vamos a tener un cambio importante de todos modos (agregando tipos de opciones), ¿por qué no hacer que los tipos no sean anulables también de forma predeterminada? Hacer que los tipos no admitan nulos de forma predeterminada y agregar tipos de opciones no es exclusivo. El último puede ser independiente (por ejemplo, en F # como señala) pero el primero requiere el segundo.

@Arnavion , hay un malentendido, no dije que necesitáramos reemplazar las firmas, todas las firmas existentes permanecen intactas, es para los nuevos desarrollos que puede usar con ADT o cualquier otra cosa que desee. Así que no hay cambios importantes. Nada se convierte en no nulo de forma predeterminada.

Si los ATD están aquí, depende de un desarrollador ajustar todos los lugares donde los nulos pueden filtrarse en la aplicación transformándolos en opcionales. Esta puede ser una idea para un proyecto independiente.

@ aleksey-bykov

No dije que necesitáramos reemplazar las firmas, todas las firmas existentes permanecen intactas

_I_ dije que las firmas deben ser reemplazadas, y ya di la razón:

pero el único valor de esto es si lib.d.ts (y cualquier otro .d.ts, y todo su código base, pero asumiremos que podemos solucionarlo) también lo usa, de lo contrario, no sabe si un La función que no usa Option puede devolver nulo o no a menos que mires su código.


Nada se convierte en no nulo de forma predeterminada. Alguna vez.

Para TS 1.x, estoy de acuerdo, solo porque es un cambio demasiado grande. Para 2.0, usar el tipo de opción en las firmas predeterminadas (lib.d.ts, etc.) ya sería un cambio importante. Hacer que los tipos no sean nulos de forma predeterminada _ además de eso_ vale la pena y no tiene inconvenientes.

No estoy de acuerdo, la introducción de opcionales no debería romper nada, no es como si usáramos opcionales o nulables o no nullables. Todos usan lo que quieren. La forma antigua de hacer las cosas no debería depender de las nuevas funciones. Depende del desarrollador utilizar una herramienta adecuada para sus necesidades inmediatas.

Entonces, está diciendo que si tengo una función foo que devuelve Option <number> y una función bar que devuelve un número, no puedo estar seguro de que bar no puede devolver null a menos que mire la implementación de bar o mantenga la documentación "Esta función nunca devuelve un valor nulo". ¿No crees que esto castiga las funciones que nunca devolverán un valor nulo?

La función bar de su ejemplo se conocía como anulable desde el principio del tiempo, se usó en unas 100500 aplicaciones y todos trataron el resultado como anulable, ahora vino y miró dentro y descubrió que nulo es imposible, ¿significa que debería seguir adelante y cambiar la firma de anulable a no anulable? Creo que no deberías. Porque este conocimiento, aunque valioso, no vale la pena frenar 100500 aplicaciones. Lo que debe hacer es crear una nueva biblioteca con firmas revisadas que lo haga así:

old_lib.d.ts

...
declare function bar(): number; // looks like can return a potentially nullable number
...

revisada_lib.d.ts

declare function bar(): !number; // now thank to the knowledge we are 100% certain it cannot return null

ahora las aplicaciones heredadas siguen usando old_lib.d.ts , para las nuevas aplicaciones, un desarrollador puede elegir libremente revised_libs.d.ts

Desafortunadamente, revisado_libs.d.ts tiene otras 50 funciones que no he visto todavía, todas las cuales devuelven un número (pero no sé si es realmente un número o un número anulable). ¿Ahora que?

Bueno, tómese su tiempo, pida ayuda, use el control de versiones (dependiendo del nivel de conocimiento que haya adquirido hasta ahora, es posible que desee publicarlo gradualmente con un número de versión cada vez mayor: revised_lib.v-0.1.12.d.ts )

En realidad, no es necesario. Una función que devuelve un tipo que acepta valores NULL en la firma pero un tipo que no admite valores NULL en la implementación solo da como resultado una verificación de errores redundante por parte del llamador. No compromete la seguridad. Anotando gradualmente más y más funciones con! a medida que los descubra, funcionarán bien, como dijo.

¡No soy fan de! solo porque es más complicado escribir (tanto en términos de pulsaciones de teclas como en la necesidad de recordar usarlo). Si queremos la no nulabilidad en 1.x, ¡entonces! es una de las opciones que ya se discutieron anteriormente, pero aún así diría que vale la pena tener un eventual cambio de ruptura con 2.0 y hacer que la no nulabilidad sea la predeterminada.

Por otro lado, tal vez conduzca a una situación de Python 2/3, donde nadie actualiza a TS 2.0 durante años porque no pueden permitirse revisar su código base de un millón de líneas asegurándose de que cada declaración y clase de variable miembro y parámetro de función y ... está anotado con? si puede ser nulo. Incluso 2to3 (la herramienta de migración de Python 2 a 3) no tiene que lidiar con cambios tan amplios como ese.

Si 2.0 puede permitirse el lujo de ser un cambio radical depende del equipo de TS. Votaría por sí, pero no tengo una base de código de un millón de líneas que deba arreglarse, así que tal vez mi voto no cuente.

Quizás deberíamos preguntarle a la gente de Funscript cómo reconcilian la API DOM que devuelve nulos con F # (Funscript usa lib.d.ts de TypeScript y otros .d.ts para usar desde el código F #). Nunca lo he usado, pero mirando http://funscript.info/samples/canvas/index.html, por ejemplo, parece que el proveedor de tipos no cree que document.getElementsByTagName ("lienzo") [0] pueda ser indefinido.

Editar: Aquí parece que no se espera que document.getElementById () devuelva un valor nulo. Al menos, no parece estar devolviendo Option <Element> ya que está accediendo a .onlick en el resultado.

@espion
Gracias, no había pensado en eso.

En el trabajo, nuestra base de código no es pequeña, y este cambio radical que la gente quiere nos haría retroceder mucho tiempo con muy poca ganancia. A través de buenos estándares y una comunicación clara entre nuestros desarrolladores, no hemos tenido problemas con la aparición de nulos donde no deberían.
Estoy sinceramente sorprendido de que algunas personas lo estén presionando tanto.
No hace falta decir que esto nos proporcionaría muy pocos beneficios y nos costaría mucho tiempo.

@Griffork echó un vistazo a esta lista filtrada de problemas en el compilador de mecanografiado para obtener una estimación del beneficio que podría tener este cambio. Todos los errores "bloqueados" enumerados en ese enlace podrían evitarse mediante el uso de tipos que no aceptan valores NULL. Y estamos hablando del increíble nivel de estándares, comunicación y revisión de código de Microsoft aquí.

Con respecto a la rotura, creo que si continúa utilizando las definiciones de tipos existentes, es posible que no obtenga ningún error, excepto que el compilador señale las variables potencialmente no inicializadas y los campos que se filtran. Por otro lado, es posible que obtenga muchos errores, particularmente, por ejemplo, si los campos de clase a menudo se dejan sin inicializar en los constructores de su código (para inicializarlos más adelante). Por lo tanto, comprendo sus preocupaciones y no estoy presionando por un cambio incompatible con versiones anteriores para TS 1.x. Todavía espero haber logrado persuadirlos de que si algún cambio en el idioma era digno de romper la compatibilidad con versiones anteriores, es este.

En cualquier caso, el Flow de Facebook tiene tipos que no aceptan valores NULL. Una vez que esté más maduro, valdría la pena investigarlo como un reemplazo del ST para aquellos de nosotros que nos preocupamos por este problema.

@spion , el único número que nos da su lista es cuántas veces se mencionó nulo o indefinido por cualquier motivo, básicamente solo dice que se ha hablado de nulo e indefinido, espero que quede claro que no puede usarlo como argumento

@ aleksey-bykov No lo soy: miré _todos_ los problemas en esa lista filtrada y todos y cada uno de los que tenían la palabra "bloqueo" estaban relacionados con un seguimiento de pila que muestra que una función intentó acceder a una propiedad o un método de un valor nulo o indefinido.

Traté de reducir el filtro con diferentes palabras clave y (creo que) logré obtener todas

@espion
Pregunta: ¿cuántos de esos errores se producen en ubicaciones en las que necesitarían marcar la variable como anulable o indefinible?

Por ejemplo, si un objeto puede tener un padre, y usted inicializa el padre como nulo, y siempre va a tener un objeto con un padre que es nulo, aún tendrá que declarar al padre como posiblemente nulo.
El problema aquí es si un programador escribe algún código con la suposición de que un bucle siempre se romperá antes de que llegue al padre nulo. Eso no es un problema con null en el idioma, exactamente lo mismo sucedería con undefined .

Razones para mantener null tan fácil de usar como lo es ahora:
• Es un mejor valor predeterminado que indefinido.
. 1) es más rápido de verificar en algunos casos (nuestro código debe ser muy eficaz)
. 2) hace que los bucles de entrada funcionen de manera más predecible en los objetos.
. 3) hace que el uso de matrices tenga más sentido cuando se usan nulos para valores en blanco (en lugar de valores perdidos). Tenga en cuenta que delete array[i] y array[i] = undefined tienen un comportamiento diferente al usar indexOf (y probablemente otros métodos de matriz populares).

Lo que siento que el resultado de hacer nulos requiere un marcado adicional para usar en el idioma:
• Recibí un error indefinido en lugar de un error nulo (que es lo que sucedería en la mayoría de los escenarios mecanografiados).

Cuando dije que no tenemos un problema con el escape de nulos, quise decir que las variables que no se inicializan en nulos nunca se vuelven nulas, todavía obtenemos errores de excepción nula en exactamente el mismo lugar en el que obtendríamos errores indefinidos (al igual que el equipo de TypeScript ). Hacer que null sea más difícil de usar (al requerir una sintaxis adicional) y dejar sin definir lo mismo en realidad causará más problemas a algunos desarrolladores (por ejemplo, a nosotros).

Agregar sintaxis adicional para usar nulo significa que durante varias semanas / meses los desarrolladores que usan mucho nulo cometerán errores a la izquierda, a la derecha y al centro mientras intentan recordar la nueva sintaxis. Y será otra forma de equivocarse en el futuro (al anotar algo ligeramente incorrecto). [Es hora de señalar que odio la idea de usar símbolos para representar tipos, hace que el lenguaje sea menos claro]

Hasta que pueda explicarme una situación en la que null cause un error o un problema que undefined no lo haría, entonces no estaré de acuerdo con hacer que null sea significativamente más difícil de usar que undefined. Tiene su caso de uso, y sólo porque no te está ayudando, no significa que el cambio de última hora que desea (que _deberá_ dolía el flujo de trabajo de otros desarrolladores) debería seguir adelante.

Conclusión:
No tiene sentido poder declarar tipos que no aceptan valores NULL sin poder definir tipos no indefinidos. Y los tipos no indefinidos no son posibles debido a la forma en que funciona JavaScript.

@Griffork cuando digo no anulable, me refiero a no anulable o indefinido. Y no es cierto que no sea posible debido a la forma en que funciona JS. Con las nuevas funciones de protección de tipos, una vez que utilice una protección de tipos, sabrá que el valor que fluye desde allí ya no puede ser nulo o indefinido. También hay una compensación involucrada, y presento la implementación de Facebook Flow como prueba de que es bastante factible.

El _único_ caso que se volverá un poco más difícil de usar es este: asignaré temporalmente nulo a esta variable y luego lo usaré en este otro método como si no fuera nulo, pero sé que nunca llamaré a este otro método antes inicializando la variable primero, por lo que no hay necesidad de verificar si hay nulos. Este es un código muy frágil, y definitivamente agradecería que el compilador me advirtiera al respecto: de todos modos, está a una refactorización de ser un error.

@espion
Creo que finalmente estoy entendiendo de dónde vienes.

Creo que desea que los protectores de tipos lo ayuden a determinar cuándo un valor no puede ser nulo y le permitan llamar a funciones que no verifican nulos dentro. Y si se elimina la declaración if de protección, se convierte en un error.

Puedo ver que eso es útil.

También creo que esta no será la solución milagrosa que esperas.

Un compilador que no ejecuta su código y prueba cada faceta del mismo no será mejor para determinar dónde están los indefinidos / nulos que el programador que escribió el código. Me preocupa que este cambio adormezca a las personas con una falsa sensación de seguridad y, de hecho, haga que los errores nulos / indefinidos sean más difíciles de rastrear cuando ocurren.
Realmente, creo que la solución que necesita es un buen conjunto de herramientas de prueba que admitan Typecript, que pueda reproducir este tipo de errores en Javascript, en lugar de implementar un tipo en un compilador que no puede cumplir su promesa.

Mencionas que Flow tiene una solución a este problema, pero al leer tu enlace vi algunas cosas preocupantes:

"El flujo a veces también puede ajustar tipos de propiedades de objeto, especialmente cuando no hay operaciones intermedias entre una verificación y un uso. Sin embargo, en general, la creación de alias de los objetos limita el alcance de esta forma de razonamiento , ya que una verificación de la propiedad de un objeto puede ser invalidado por una escritura en esa propiedad a través de un alias, y es difícil para un análisis estático rastrear los alias con precisión ".

"Los valores indefinidos, al igual que los nulos, también pueden causar problemas. Desafortunadamente, los valores indefinidos son omnipresentes en JavaScript y es difícil evitarlos sin afectar gravemente la usabilidad del lenguaje [...] Flow hace una compensación en este caso: detecta variables locales indefinidas y valores de retorno, pero ignora la posibilidad de que no lo sean como resultado de la propiedad del objeto y los accesos a los elementos de la matriz ".

Ahora los indefinidos y los nulos funcionan de manera diferente, los errores indefinidos aún pueden aparecer en todas partes, el signo de interrogación no puede garantizar que el valor no sea nulo y el lenguaje se comporta de manera más diferente a Javascript (que es lo que TS está tratando de evitar de lo que he visto ).

PD

foo(thing: whatiknowaboutmyobject) {
    if (thing.hidden) {
        delete thing.description;
    }
}

if (typeof thing.description === "string") {
    //thing.description is non-nullable now, right?
    foo(thing);
    //What is thing.description?
    console.log(thing.description.length);
}

TS ya es vulnerable a los efectos de alias (como lo es cualquier lenguaje que permita valores mutables). Esto se compilará sin errores:

function foo(obj: { bar: string|number }) {
    obj.bar = 5;
}

var baz: { bar: string } = { bar: "5" };

foo(baz);

console.log(baz.bar.charAt(0)); // Runtime error - Number doesn't have a charAt method

Los documentos de Flow indican esto solo para completar.

Editar: mejor ejemplo.

Viejo:

Sí, pero sostengo que los medios para establecer algo en indefinido son mucho mayores que los medios para establecer algo en otro valor.

Quiero decir,

mything.mystring = 5; // is clearly wrong.
delete mything.mystring; //is not clearly wrong - this is not quite the equivalent of setting mystring to >undefined.

Editar:
Meh, en este punto es una preferencia personal. Después de usar javascript durante años, no creo que esta sugerencia vaya a ayudar al idioma. Creo que va a adormecer a la gente con una falsa sensación de seguridad, y creo que alejará a Typecript (como lenguaje) de Javascript.

@Griffork Para ver un ejemplo de cómo el texto mecanografiado actual lo lleva a una falsa sensación de seguridad, pruebe el ejemplo que presentó en el patio de recreo:

var mything = {mystring: "5"}; 
delete mything.mystring;
console.log(mything.mystring.charAt(1));

Por cierto, el operador de eliminación podría tratarse de la misma manera que asignar un valor de tipo nulo y eso sería suficiente para cubrir su caso también.

La afirmación de que el lenguaje se comportará de manera diferente a JavaScript es cierta, pero no tiene sentido. TypeScript ya tiene un comportamiento diferente al de JavaScript. El objetivo de un sistema de tipos siempre ha sido no permitir programas que no tienen sentido. Modelar tipos que no aceptan valores NULL simplemente agrega un par de restricciones adicionales. No permitir la asignación de valores nulos o indefinidos a una variable de tipo que no acepta valores NULL es exactamente lo mismo que no permitir la asignación de un número a una variable de tipo cadena. JS permite ambos, TS no podría permitir ninguno

@espion

la idea es evitar tener comprobaciones nulas en todas partes de su código

Si entiendo lo que defiende:

A. Hacer que todos los tipos no sean nulos de forma predeterminada.
B. Marque los campos y las variables que aceptan valores NULL.
C. Asegúrese de que el desarrollador de la aplicación / biblioteca verifique todos los puntos de entrada a la aplicación.

Pero, ¿no significa eso que la responsabilidad de garantizar que el código de uno esté libre de nulos recae en la persona que escribe el código y no en el compilador? Efectivamente, le estamos diciendo al compilador "dw, no voy a permitir que entren nulos en el sistema".

La alternativa es decir, los nulos están en todas partes, así que no se moleste, pero si algo no es anulable, se lo haré saber.

El hecho es que el último enfoque es propenso a excepciones de referencias nulas, pero es más veraz. Pretender que un campo de un objeto obtenido a través del cable (es decir, ajax) no es nulo implica tener fe en Dios: smiley:.

Creo que hay un fuerte desacuerdo sobre este tema porque, dependiendo de en qué se esté trabajando, el elemento C anterior podría ser trivial o inviable.

@jbondc Me alegro de que hayas preguntado eso. De hecho, CallExpression está marcado como indefinido o anulable. Sin embargo, el sistema de tipos actualmente no aprovecha eso; aún permite todas las operaciones en typeArguments como si no fuera nulo o indefinido.

Sin embargo, al usar los nuevos tipos de unión en combinación con tipos que no aceptan valores NULL, el tipo podría expresarse como NodeArray<TypeNode>|null . Entonces, el sistema de tipos no permitirá ninguna operación en ese campo a menos que se aplique una verificación nula:

if (ce.typeArguments != null) {
  callSomethingOn(ce.typeArguments)
}

// callSomethingOn doesn't need to perform any checks

function callSomethingOn(na:NodeArray<TypeNode>) {
...
}

Con la ayuda de los protectores de tipo TS 1.4, dentro del bloque if, el tipo de expresión se reducirá a NodeArray<TypeNode> que a su vez permitirá todas las operaciones NodeArray en ese tipo; Además, todas las funciones llamadas dentro de esa verificación podrán especificar que su tipo de argumento sea NodeArray<TypeNode> sin realizar más verificaciones, nunca.

Pero si intentas escribir

function someOtherFunction(ce: CallExpression) {
  callSomethingOn(ce.typeArguments)
}

el compilador le advertirá al respecto en tiempo de compilación, y el error simplemente no habría ocurrido.

Entonces no, @NoelAbrahams , no se trata de saberlo todo con certeza. Se trata de que el compilador le ayude a decir qué valor puede contener una variable o campo, al igual que con todos los demás tipos.

Por supuesto, con valores externos, como siempre, depende de usted especificar cuáles son sus tipos. Siempre puede decir que los datos externos contienen una cadena en lugar de un número, y el compilador no se quejará, pero su programa se bloqueará cuando intente realizar operaciones de cadena en el número.

Pero sin tipos que no aceptan valores NULL, ni siquiera tiene la capacidad de decir que un valor no puede ser nulo. El valor nulo se puede asignar a todos y cada uno de los tipos y no puede establecer ninguna restricción al respecto. Las variables se pueden dejar sin inicializar y no recibirá ninguna advertencia, ya que undefined es un valor válido para cualquier tipo. Por lo tanto, el compilador no puede ayudarlo a detectar errores relacionados con nulos e indefinidos en el momento de la compilación.

Me sorprende que haya tantos conceptos erróneos sobre los tipos que no aceptan valores NULL. Son solo tipos que no se pueden dejar sin inicializar o que no se les pueden asignar los valores null o undefined . Esto no es tan diferente a no poder asignar una cadena a un número. Esta es mi última publicación sobre este tema, ya que siento que realmente no estoy llegando a ninguna parte. Si alguien está interesado en saber más, le recomiendo comenzar con el video "El error de los mil millones de dólares" que mencioné anteriormente. El problema es bien conocido y muchos lenguajes y compiladores modernos lo han abordado con éxito.

@spion , estoy completamente de acuerdo con todos los beneficios de poder indicar si un tipo puede o no ser nulo. Pero la pregunta es ¿desea que los tipos no sean nulos _ por defecto _?

Pretender que un campo en un objeto obtenido a través del cable (es decir, ajax) no es nulo implica tener fe en Dios

Así que no finjas. Márquelo como anulable y se verá obligado a probarlo antes de usarlo como no anulable.

Seguro. Todo se reduce a si queremos marcar un campo como anulable (en un sistema donde los campos no son nulos por defecto) o si queremos marcar un campo como no anulable (en un sistema donde los campos son anulables por defecto).

El argumento es que el primero es un cambio rotundo (que puede ser significativo o no) y también insostenible porque requiere que el desarrollador de la aplicación verifique y garantice todos los puntos de entrada.

@NoelAbrahams No veo por qué es 'intenable' básicamente la mayoría de las veces no quiere nulo, también cuando un punto de entrada puede devolver nulo , tendrá que verificarlo , al final, un sistema de tipos con tipo no nulo de forma predeterminada, le permitirá hacer menos comprobaciones nulas porque podrá confiar en alguna api / biblioteca / punto de entrada en su aplicación.

Cuando piense un poco en marcar un tipo como no nulo en un sistema de tipos que aceptan valores NULL tiene un valor limitado, aún podrá consumir el tipo de variable / retorno con tipo que admite valores NULL sin verse obligado a probarlo.
También obligará al autor de la definición a escribir más código, ya que la mayoría de las veces una biblioteca bien diseñada nunca devuelve un valor nulo o indefinido.
Finalmente, incluso el concepto es extraño, en un sistema de tipos con tipos que no aceptan valores NULL, un tipo que admite valores NULL se puede expresar perfectamente como un tipo de unión: ?string es el equivalente de string | null | undefined . En un sistema de tipos con un tipo que acepta valores NULL como predeterminado, donde puede marcar el tipo como no anulable, ¿cómo expresaría !string ? string - null - undefined ?

Al final, realmente no entiendo la preocupación de la gente aquí, null no es una cadena, de la misma manera que 5 no es una cadena, ambos valores no podrán usarse donde se espera una cadena, y dejar escapar var myString: string = null es tan propenso a errores como: var myString: string = 5 .
Tener nulos o indefinidos asignables a cualquier tipo es quizás un concepto con el que los desarrolladores están familiarizados, pero sigue siendo malo.

No creo que haya acertado del todo en mi publicación anterior: echaré la culpa a la hora.

Acabo de revisar algunos de nuestros códigos para ver cómo funcionarían las cosas y ciertamente ayudaría marcar ciertos campos como anulables, por ejemplo:

interface Foo {
        name: string;
        address: string|null; /* Nullable */
}

var foo:Foo = new FooClass();
foo.name.toString(); // Okay
foo.address.toString(); // Error: do not use without null check

Pero a lo que me opongo es a lo siguiente:

foo.name = undefined; // Error: non-nullable

Siento que esto interferirá con la forma natural de trabajar con JavaScript.

Exactamente lo mismo podría aplicarse con el número:

interface Foo {
        name: string;
        address: string|number; 
}
var foo:Foo = new FooClass();
foo.name.toString(); // Okay
foo.address.slice() // error

foo.name  = 5 // error

Y sigue siendo válido en JavaScript.

Razonablemente, ¿cuántas veces asigna voluntariamente nulo a una propiedad de un objeto?

Creo que la mayoría de las cosas se marcarían como nulas, pero dependería de los protectores de tipos para declarar que el campo ahora no admite nulos.

@fdecampredon
Mucho en realidad.

@Griffork ,

Creo que la mayoría de las cosas se marcarían como nulas.

Ese fue mi pensamiento inicial. Pero después de revisar algunas secciones de nuestro código, encontré comentarios como los siguientes:

interface MyType {

     name: string;

     /** The date the entry was updated from Wikipedia or undefined for user-submitted content. */
     wikiDate: Date; /* Nullable */
}

La idea de que un campo es anulable se utiliza a menudo para proporcionar información. Y TypeScript detectará errores si requiere un tipo de protección al acceder a wikiDate .

@fdecampredon

foo.name = 5 // error
Y sigue siendo válido en JavaScript.

Es cierto, pero eso es un error porque TypeScript sabe con 100% de certeza que no fue intencional.

mientras que

foo.name = undefined; // No enviar nombre al servidor

es perfectamente intencional.

Creo que la implementación que se ajusta mejor a nuestros requisitos es no usar tipos de unión, sino seguir la sugerencia original:

 wikiDate: ?Date;

Estoy de acuerdo con @NoelAbrahams

foo.name = 5 // error
Y sigue siendo válido en JavaScript.
Es cierto, pero eso es un error porque TypeScript sabe con 100% de certeza que no fue intencional.

El compilador solo sabe que marcó el nombre como string y no string | number si desea un valor anulable, simplemente lo marcará como ?string o string | null (que es bastante equivalente)

Creo que la implementación que se ajusta mejor a nuestros requisitos es no usar tipos de unión, sino seguir la sugerencia original:

wikiDate:? Fecha;

Por lo tanto, estamos de acuerdo en que el tipo no es nulo de forma predeterminada y que marcaría como anulable con ? :).
Tenga en cuenta que sería un tipo de unión ya que ?Date sería el equivalente a Date | null | undefined :)

Oh, lo siento, estaba tratando de aceptar que se aceptan valores nulos de forma predeterminada y no nulos con una escritura especial (los símbolos son confusos).

@fdecampredon , en realidad lo que significa es que cuando un campo o variable se marca como anulable, se requiere un tipo de protección para el acceso:

var wikiDate: ?Date;

wikiDate.toString(); // error
wikiDate && wikiDate.toString(); // okay

Este no es un cambio importante, porque aún deberíamos poder hacer esto:

 var name: string;   // okay
 name.toString();  // if you think that's fine then by all means

¿Quizás cree que no podemos tener esto sin introducir null en los tipos de unión?

Su primer ejemplo es absolutamente correcto cuando lo hace:

wikiDate && wikiDate.toString(); // okay

utiliza un tipo de protección y el compilador no debería advertir nada.

Sin embargo, tu segundo ejemplo no es bueno.

var name: string;   // okay
name.toString();  // if you think that's fine then by all means

el compilador debería tener un error aquí, un algoritmo simple podría simplemente error en la primera línea (variable unitaria no marcada como anulable), uno más complejo podría intentar detectar la asignación antes del primer uso:

var name: string;   // okay
name.toString();  // error because not initialized
var name: string;
if (something) {
  name = "Hello World";
} else {
  name = "Foo bar";
}
name.toString();  // no error since name will always be initialized.

No sé exactamente dónde poner la barrera, pero seguramente necesitaría algún tipo de ajuste sutil para no interponerse en el camino del desarrollador.

Es un cambio importante y no se puede introducir antes de 2.0, excepto quizás con la directiva 'use nonnull' propuesta por @metaweta

Por qué no tener:

var string1: string; //this works like typescript does currently, doesn't need type-guarding before use, null and undefined can be assigned to it.
string1.length; //works
var string2: !string; //this doesn't work because the string must be assigned to a non-null and non-undefined value, doesn't need type-guarding before use.
var string3: ?string; //this must be type guarded to non-null, non-undefined before use.
var string4: !string|null = null; //This may be null, but should never be undefined (and must be type-guarded before use).
var string5: !string|undefined; //this may never be null, but can be undefined (and must be type-guarded before use).

Y tener un indicador de compilador (que solo funciona si -noimplicitany está activado) que dice -noinferrednulls, que deshabilita la sintaxis normal para tipos (como string e int) y debe proporcionar un? o ! con ellos (nulos, indefinidos y cualquier tipo que sea una excepción).

De esta manera, los no nulables son una opción de inclusión, y puede usar la marca del compilador para forzar que un proyecto se anule explícitamente.
El compilador marca errores en la asignación de tipos , no después (como la propuesta anterior).

¿Pensamientos?

Editar: escribí esto porque obliga a que la idea de usar un valor no nulo sea explícita en cada acción. Cualquiera que lea el código que provenga de cualquier otro proyecto de TS sabrá exactamente lo que está sucediendo. Además, el indicador del compilador se vuelve muy obvio si está activado (ya que blah: string es un error, pero blah:!string no lo es, similar a la forma en que funciona -noimplicitany).

Edición 2:
DefinatelyTyped podría actualizarse para admitir nulos no inferidos, y no cambiarán el uso de las bibliotecas si las personas eligen no suscribirse a? y ! característica.

No me importa si lo que no es nulo y lo que no está definido son optar por participar o no, con
modificadores de tipo (!?), una directiva o una bandera del compilador; Haré lo que sea
tarda en conseguirlos _ siempre que sea posible expresarlos_, lo cual no es
actualmente el caso.

El lunes 22 de diciembre de 2014 a las 2:35 p.m., Griffork [email protected] escribió:

Por qué no tener:

var string1: string; // esto funciona como lo hace el mecanografiado actualmente. No necesita protección de tipos antes de su uso, se le pueden asignar nulos e indefinidos.
string1.length; // variable de trabajo cadena2:! cadena; // esto no funciona porque la cadena debe asignarse a un valor no nulo y no indefinido, no necesita protección de tipo antes de use.var string3:? string; // este debe ser de tipo protegido para que no sea nulo, no indefinido antes de use.var string4:! string | null = null; // Esto puede ser nulo, pero nunca debe ser indefinido (y debe tener protección de tipo antes de su uso) .var string5:! String | undefined; // esto nunca puede ser nulo, pero puede ser indefinido (y debe tener protección de tipo antes de su uso).

Y tener un indicador de compilador (que solo funciona si -noimplicitany está activado) que
dice -noinferrednulls, que deshabilita la sintaxis normal para tipos (como
string e int) y debe proporcionar un? o ! con ellos (nulo, indefinido
y cualquier excepción).

De esta manera, los no nulables son una opción de inclusión y puede usar el compilador
flag para forzar la anulación explícita de un proyecto.
El compilador marca errores en la _asignación de tipos_, no después (como
la propuesta anterior).

¿Pensamientos?

Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -67899445
.

Mike Stay - [email protected]
http://www.cs.auckland.ac.nz/~mike
http://reperiendi.wordpress.com

@Griffork esa sería una opción, pero mala en mi opinión, y explicaré por qué:

  • Causará muchos más trabajos en los archivos de definición, ya que ahora tendremos que verificar y anotar cada tipo para tener la definición correcta.
  • Terminaremos escribiendo !string (ya veces ?string ) en todas partes del código, lo que haría que el código fuera mucho menos legible.
  • !string en un sistema donde los tipos son anulables es un concepto extraño, la única forma en que realmente no puedes describirlo es string minus null minus undefined , por el contrario ?string es bastante simple de describir en un sistema de tipos donde los tipos son nulos por defecto string | null | undefined .
  • Preveo muchos dolores de cabeza (y pérdida de rendimiento) para encontrar un algoritmo de verificación de tipo donde el compilador entienda que string | null requiere una protección de tipo pero string no, básicamente introduce un concepto donde algún tipo de unión debe tratarse de manera diferente a otro.
  • Y finalmente, la peor parte es que perdemos completamente la inferencia de tipo var myString = "hello" ¿cuál debería ser myString string , ?string o !string ? Honestamente, un gran dolor de cabeza en perspectiva aquí.

Si no tenemos un tipo no nulo por defecto, la mejor propuesta que he visto aquí es la directiva 'use non-null' propuesta por @metaweta.
Seguro que debe especificarse bien, pero al menos con solo una cadena use non-null en todo nuestro archivo podemos obtener un comportamiento simple y predecible.

@fdecampredon

  1. Puede ser mucho más trabajo en archivos de definición, pero _ tendrías que hacer ese trabajo de todos modos_ (para asegurarte de que los tipos sean correctos) y esta vez el compilador te recordará lo que aún no has editado (si usas -noimplicitnull ).
  2. Estoy abierto a otras sugerencias de anotaciones. Sinceramente, creo que el sistema de tipos actual tiene su lugar y no debería ser _remplazado_. No creo que valga la pena hacer un cambio radical. En cambio, creo que deberíamos encontrar una mejor manera de describir lo que buscas. (Realmente, realmente no me gusta la idea de representar todo esto con símbolos, no son intuitivos).
  3. ¿Qué es difícil de describir sobre esto? He visto discusiones en otros lugares en mecanografiado donde se ha propuesto esta solicitud (para ciertos tipos) (sin un marcador). Hice una búsqueda rápida y no pude encontrar el problema, buscaré más más tarde.

  4. Si te refieres a lo que había escrito como !string|null , eso funcionaría en el sistema actual si se tratara el valor nulo _like_ {} (pero no se le pudiera asignar).
    Si estás hablando de string|null que no tenía en mi lista, creo que en este caso debería ignorarse null. Nulo e indefinido solo tienen sentido en uniones donde todo lo no nulo y no indefinido está precedido por un! y any no es ninguno (esto podría ser una advertencia / error del compilador).
  5. Buena pregunta, y una que solo surge si está usando la opción -noimplicitnull, creo que la opción más segura sería asignarla a la opción que sea más probable que cause un error temprano (probablemente anulable), pero obtengo el sintiendo que hay una idea mejor en la que no estoy pensando. Me pregunto si alguien más tiene una sugerencia sobre cómo debería abordarse esto.

Editar: agregado al punto 2.
Editar: error tipográfico fijo en el punto 4.

Puede ser mucho más trabajo en archivos de definición, pero tendría que hacer ese trabajo de todos modos (para asegurarse de que los tipos sean correctos) y esta vez el compilador le recordará lo que aún no ha editado (si usa -noimplicitnull ).

Sí y no, verifique la biblioteca que usa y vea cuánto devuelve realmente nulo o indefinido.
Es un caso bastante raro, solo pudimos encontrar en este problema muy pocas ocurrencias para la biblioteca estándar y, por ejemplo, la biblioteca Promise nunca lo hace.
Mi punto es que en un sistema de tipos donde los tipos no son anulables por defecto, la mayoría de los archivos de definición existentes ya son válidos .

Estoy abierto a otras sugerencias de anotaciones. Sinceramente, creo que el sistema de tipos actual tiene su lugar y no debería ser reemplazado. No creo que valga la pena hacer un cambio radical. En cambio, creo que deberíamos encontrar una mejor manera de describir lo que buscas. (Realmente, realmente no me gusta la idea de representar todo esto con símbolos, no son intuitivos).

No creo que exista tal manera pero espero equivocarme, porque tiene un valor inestimable en mi opinión.
Para la parte del cambio de ruptura, ¿por qué está tan en contra de la directiva 'use non-null' ?
Los desarrolladores que desean un sistema de tipos que aceptan valores NULL no se verían afectados en absoluto (a menos que ya agreguen extrañamente 'use non-null' en la parte superior de su archivo, pero honestamente eso sería un poco ... extraño)
Y los desarrolladores que deseen un sistema de tipo no nulo podrían simplemente usar la nueva directiva.

¿Qué es difícil de describir sobre esto? He visto discusiones en otros lugares en mecanografiado donde se ha propuesto esta solicitud (para ciertos tipos) (sin un marcador). Hice una búsqueda rápida y no pude encontrar el problema, buscaré más más tarde.
Encuentro el concepto un poco 'extraño' y no muy claro, generalmente uso la composición como la herramienta principal para la programación, no _descomposición_ pero por qué no.

Si te refieres a lo que había escrito como! String | null, eso funcionaría en el sistema actual si se tratara a null como {} (pero no se le pudiera asignar). Si está hablando de una cadena | null que no tenía en mi lista, creo que en este caso debería ignorar null. Nulo e indefinido solo tienen sentido en uniones donde todo lo no nulo y no indefinido está precedido por un! y any no es ninguno (esto podría ser una advertencia / error del compilador).
Me refiero al hecho de que cuando descompone los 3 tipos:

  • !sting : string - null - undefined
  • string : string | null | undefined
  • ?string : string | null | undefined

Los 2 últimos básicamente no tienen ninguna diferencia, pero el compilador debe saber que por string no debe forzar la verificación de protección de tipos y por ?string debería, esa información tendrá que propagarse a todas partes, el El algoritmo será mucho más complejo de lo que es en realidad, y estoy bastante seguro de que podría encontrar un caso extraño con la inferencia de tipos.

Buena pregunta, y una que solo surge si está usando la opción -noimplicitnull, creo que la opción más segura sería asignarla a la opción que sea más probable que cause un error temprano (probablemente anulable), pero obtengo el sintiendo que hay una idea mejor en la que no estoy pensando. Me pregunto si alguien más tiene una sugerencia sobre cómo debería abordarse esto.

No lo haré, básicamente, presentaría el mismo problema que @RyanCavanaugh comentó cuando pensé en introducir una bandera que permitiría cambiar de nulo como predeterminado a nulo como predeterminado.
En este caso, la propuesta anterior era mucho más simple.

Una vez más, ¿por qué estás en contra de la directiva 'use non-null' Más lo pienso, más me parece la solución ideal.

@fdecampredon
Debido a que el "uso no nulo", como se ha propuesto actualmente, cambia la forma en que se _utiliza_ el idioma, no la forma en que se _escribe_ el idioma. Lo que significa que una función de una ubicación cuando se mueve a otra ubicación puede funcionar de manera diferente cuando se _utiliza_. Obtendrá errores en tiempo de compilación que están potencialmente a 1-3 archivos de distancia porque algo está escrito incorrectamente.

La diferencia entre:
string
y
?string
¿Es eso el? y ! symbol está solicitando una verificación de tipo estricta para esta variable (muy parecido a lo que sería su directiva "use nonnull", pero por variable). Explícalo de esa manera y creo que no tendrás muchos problemas para que la gente lo entienda.

La diferencia es, por supuesto:
Archivo 1:

//...187 lines of code down...
string myfoo(checker: boolean) {
    if(checker){
        return null;
    }
    else {
        return "hello";
    }
}

Archivo 2:

"use nonnull"
//...2,748 lines of code down...
string myfoo(checker: boolean) {
    if(checker){
        return null; //Error!
    }
    else {
        return "hello";
    }
}

Los desarrolladores ahora tienen que mantener un mapa mental de qué archivos no son nulos y cuáles no. Sinceramente, creo que esta es una mala idea (incluso si la mayor parte de su código es 'use nonnull').

Editar: Además, cuando comienzas a escribir una función y obtienes la pequeña ventana que te dice cuál es la definición de la función, ¿cómo sabes si string en esa definición es anulable o no anulable?

@Griffork

  • de nuevo, ese ya es el caso con 'use strict' .
  • Al menos la idea es simple. e introducir un concepto simple en el sistema de tipos.
  • Si un desarrollador mueve una función en otro archivo, me parece un caso bastante límite, y dado que el error se le indicará será de verificación nula, podrá comprender rápidamente lo que está sucediendo ...

Una vez más, no es perfecto, pero no veo una alternativa mejor.

Para que quede claro, sus únicos problemas con lo que propuse (después de los contraargumentos) que puedo ver son:

  • No sabemos cuál sería el comportamiento de var mystring = "string"
  • No querrás tener que escribir símbolos en todas partes.

Y mis preocupaciones con la directiva son:

  • Los no nulos no serían explícitos (pero pueden ocurrir en el mismo proyecto que los nulables). editar : redacción fija para que tenga más sentido.
  • Será más difícil para los desarrolladores hacer un seguimiento de si lo que están escribiendo es anulable o no.
  • Las definiciones de función que ve cuando invoca una función ( editar : la ventana emergente proporcionada por Visual Studio cuando comienza a escribir una función) pueden ser anulables o no y usted no podría saberlo.
  • Una vez que una función está envuelta en 2 o 3 capas, no sabe si su definición sigue siendo correcta (ya que no puede usar la inferencia de tipo a través de archivos que no tienen "use nonnull").

Honestamente, la implementación del "uso estricto" no es
Y no veo por qué la directiva es mejor que obligar a los desarrolladores a ser explícitos sobre sus intenciones (después de todo, esa es la razón por la que se creó Typecript, ¿no?).

Editar: Aclarado un punto.
Editar: ponga la cadena entre comillas

Honestamente, su resumen de mi preocupación es un poco pequeño y no refleja lo que escribí, su propuesta agrega complejidad al sistema de tipos, hace que el algoritmo de verificación de tipos sea un dolor de cabeza, es bastante difícil de especificar con todo el caso de borde que sería cree especialmente para la inferencia de tipos y haga que el código sea ultra detallado por nada. Básicamente, no es la herramienta adecuada para el trabajo.

Quiero que todos los no nulos sean explícitos (ya que pueden ocurrir en el mismo proyecto que los nulos).

Quiero que el hecho de que string sea ​​en realidad string | null | undefined sin forzarte a comprobarlo es explícito.

Será más difícil para los desarrolladores hacer un seguimiento de si lo que están escribiendo es anulable o no.

Dudo que haya un solo archivo en un proyecto sin 'use non-null' si el proyecto usa nonnullity, y desplazarse en la parte superior de su archivo no es tan difícil (al menos cuando escribe un archivo con menos de 500 líneas de código que es la mayor parte del caso ...)

Las definiciones de función que ves cuando invocas una función pueden ser anulables o no y no podrías saberlo.

Sí, lo hará si proviene de un módulo que tiene una directiva 'use non-null', lo que se escribirá en consecuencia, si no considera todo como anulable ...

Una vez que una función está envuelta en 2 o 3 capas, no sabe si su definición sigue siendo correcta (ya que no puede usar la inferencia de tipo a través de archivos que no tienen "use nonnull").

No entiendo tu punto aquí.

Honestamente, la implementación del "uso estricto" no es algo a lo que se deba aspirar. Fue diseñado para JavaScript (no Typecript), y en JavaScript hay muy pocas alternativas. Estamos usando TypeScript, por lo que tenemos la opción de hacer las cosas mejor.
Y no veo por qué la directiva es mejor que obligar a los desarrolladores a ser explícitos sobre sus intenciones (después de todo, esa es la razón por la que se creó Typecript, ¿no?).

TypeScript es un superconjunto de javascript, tiene su raíz en JavaScript y el objetivo es permitirle escribir javascript de una manera más segura, por lo que reutilizar un concepto de JavaScript no parece descabellado.

Y no veo por qué la directiva es mejor que obligar a los desarrolladores a ser explícitos sobre sus intenciones.

Debido a que simplemente no podrá lograr el mismo resultado, por todas las razones que mencioné, tener un tipo que no acepta valores NULL en un sistema de tipos que aceptan valores NULL es solo una idea bade.

Desde mi punto de vista, hay 3 soluciones viables aquí:

  • Rompiendo cambios para 2.0 donde los tipos se vuelven no anulables
  • Indicador del compilador que cambia a un tipo no anulable de forma predeterminada, como propuse algunas docenas de comentarios, pero @RyanCavanaugh tenía un buen punto al respecto. (evento si honestamente creo que vale la pena)
  • directiva 'use non-null'

La bandera valdría la pena para mí, por cierto.

function fn(x: string): number;
function fn(x: number|null): string;

function foo() {
    return fn(null);
}

var x = foo(); // x: number or x: string?

Si las funciones son la única preocupación, se podría hacer una excepción en los casos en que una sobrecarga contenga un argumento null explícito; siempre tendría prioridad sobre el nulo implícito (para estar en armonía con --noImplicitNull ) . Esta sería también la interpretación que tendría sentido para el usuario (es decir, "lo que digo explícitamente debe anular lo que se dice implícitamente"). Aunque me pregunto si hay otros problemas similares con la bandera que no se pueden resolver de esta manera. Y, por supuesto, esto agrega cierta complejidad pirata tanto a la especificación como a la implementación: |

  1. string|null|undefined fue explícito en mi propuesta usando la bandera, por eso lo dejé fuera.
  2. Entonces, ¿ por puede verse obligado a abarcar todo el proyecto.
  3. Utilizo muchos archivos que no creo; ¿Cómo puedo saber que ese archivo en particular tiene "uso no nulo"? Si tengo 100 archivos creados por otras personas, tengo que memorizar cuál de esos 100 archivos no es nulo. (o, cada vez que uso una variable / función de otro archivo, ¿tengo que abrir ese archivo y verificarlo?)
  4. Veamos si puedo aclarar esto: ejemplo al final de la publicación.
  5. ¿Dónde te detienes? ¿Dónde lo llamas malo? Una cadena o palabra clave al principio de un archivo no debería cambiar la forma en que se comporta un archivo. "use estricto" se agregó a javascript porque

    1. En ese momento, no existía un gran superconjunto de Javascript (por ejemplo, TypeScript) que pudiera hacer lo que quería.

    2. Fue un intento de acelerar el procesamiento de JavaScript (que en mi opinión es la única razón por la que es excusable).

      Se adoptó el "uso estricto", no porque fuera lo correcto, sino porque era la única forma en que los navegadores podían satisfacer las demandas de los desarrolladores. Odiaría ver que el mecanografiado agregue 1 (luego 2, luego 3 y luego 4) otras directivas que _cambian fundamentalmente la forma en que funciona el lenguaje_ como cadenas que se declaran en algún ámbito arbitrario y afectan algunos otros ámbitos arbitrarios. Es un diseño de lenguaje realmente malo. Sería feliz si "use estricto" no existiera en Typecript, y en su lugar fuera una marca de compilador (y Typecript lo emitiera en cada archivo / alcance que lo necesitara).

  6. Javascript no es un "sistema de tipos que no aceptan valores NULL", Typecript no es un "sistema de tipos que no aceptan valores NULL". La introducción de la capacidad de declarar tipos que no aceptan valores NULL no significa que todo el sistema sea "que no admite valores NULL". No es el objetivo de TypeScript.
File 1:
"use notnull"
export string foo() {
    return "mygeneratedstring";
}
File 2:
export string foo() {
    return file1.foo()
}
File 3:
"use notnull"
file2.foo(); //???

¡De hecho, tiene la capacidad de perder información contextual mientras sigue usando la misma sintaxis! Eso no es algo que espero tener en ningún idioma que use.


Parece que estamos discutiendo en círculos. Si no tengo sentido para usted, lo siento, pero parece que está planteando repetidamente los mismos problemas, y yo les respondo repetidamente.

@espion
En ese ejemplo, la cadena no es un nulo implícito (creo que asume que el indicador --noImplicitNull está activado)

string | null | undefined fue explícito en mi propuesta usando la bandera, por eso lo dejé

Lo que quiero decir es que en el sistema actual var myString: string es implícitamente var myString: (string | null | undefined); y que, además de eso, el compilador no te obliga a usar typeguard a menos que todos los demás tipos de unión.

Entonces, ¿por qué tenerlo por archivo? Estoy sugiriendo mi sugerencia porque no rompe la compatibilidad con versiones anteriores, lo cual es importante para mí y probablemente para muchos otros desarrolladores. Y puede verse obligado a abarcar todo el proyecto.
La directiva no rompe la compatibilidad con versiones anteriores (a menos que haya utilizado un literal de cadena 'use not-null' en una posición de una directiva por una razón bastante extraña, que apuesto a que nadie lo hace), también en algún momento, TypeScript tendrá que romper la compatibilidad con versiones anteriores, por eso semver definió la versión principal, pero esa es otra historia.

Utilizo muchos archivos que no creo; ¿Cómo puedo saber que ese archivo en particular tiene "uso no nulo"? Si tengo 100 archivos creados por otras personas, tengo que memorizar cuál de esos 100 archivos no es nulo. (o, cada vez que uso una variable / función de otro archivo, ¿tengo que abrir ese archivo y verificarlo?)

Si estás en un proyecto con pautas, etc., como todos los proyectos están organizados normalmente, sabes ... en el peor de los casos, solo tienes que desplazarte un poco ... no parece tan difícil ...

¿Dónde te detienes? ¿Dónde lo llamas malo? Una cadena o palabra clave al principio de un archivo no debería cambiar la forma en que se comporta un archivo. "use estricto" se agregó a javascript porque
En ese momento, no existía un gran superconjunto de Javascript (por ejemplo, TypeScript) que pudiera hacer lo que quería.
Fue un intento de acelerar el procesamiento de JavaScript (que en mi opinión es la única razón por la que es excusable). Se adoptó el "uso estricto" no porque fuera lo correcto, sino porque era la única forma en que los navegadores podían satisfacer las demandas de los desarrolladores. Odiaría ver que el mecanografiado agregue 1 (luego 2, luego 3 y luego 4) otras directivas que cambian fundamentalmente la forma en que funciona el lenguaje como cadenas que se declaran en algún ámbito arbitrario y afectan algunos otros ámbitos arbitrarios. Es un diseño de lenguaje realmente malo. Sería feliz si "use estricto" no existiera en Typecript, y en su lugar fuera una marca de compilador (y Typecript lo emitiera en cada archivo / alcance que lo necesitara).

'use estricto' se introdujo para cambiar el comportamiento del lenguaje y mantener la compatibilidad retro, exactamente el mismo problema al que nos enfrentamos ahora, y desaparecerá más o menos con los módulos es6 (por lo que el uso no nulo podría desaparecer con la próxima versión principal de mecanografiado) .

Javascript no es un "sistema de tipos que no aceptan valores NULL", Typecript no es un "sistema de tipos que no aceptan valores NULL". La introducción de la capacidad de declarar tipos que no aceptan valores NULL no significa que todo el sistema sea "que no admite valores NULL". No es el objetivo de TypeScript.

Esa oración no tiene ningún sentido para mí, JavaScript no tiene un sistema de tipo estático, así que, como dije, el hecho de que puede asignar null a una variable que estaba antes de string se puede comparar fácilmente al hecho de que puede asignar 5 a una variable que antes era una cadena.
La única diferencia es que TypeScript, un verificador de tipos creado para JavaScript, considera obstinadamente null como asignable a todo y no 5 .

Para su ejemplo, sí, perdemos información entre archivos, es una compensación aceptable para mí, pero puedo entender su preocupación.

Parece que estamos discutiendo en círculos. Si no tengo sentido para usted, lo siento, pero parece que está planteando repetidamente los mismos problemas, y yo les respondo repetidamente.

Sí, estoy de acuerdo, dudo que haya algún consenso al respecto, honestamente creo que terminaré bifurcando el compilador y agregando un tipo no nulo para mí, con la esperanza de que algún día termine en el compilador principal, o ese flujo lo suficientemente maduro como para ser utilizable.

@Griffork, por cierto, creo que la idea de @spion funciona, la idea es decir que sin que la bandera string sea ​​un number | null nulo implícito foo(null) será string .

@RyanCavanaugh , ¿crees que podría funcionar?

@fdecampredon

  1. Se introdujo el "uso estricto" para optimizar javascript. Tenía que ser consumido por navegadores , por lo tanto, tenía que estar en código. Y tenía que ser compatible con versiones anteriores, por lo que tenía que ser una declaración no funcional.
    Los navegadores no tienen que consumir los tipos que no aceptan valores NULL, por lo que no es necesario que tengan una directiva en el código.
  2. Lo siento, me estaba refiriendo a algo que había olvidado publicar, aquí está:

Según Facebook : "En JavaScript, null se convierte implícitamente a todos los tipos primitivos; también es un habitante válido de cualquier tipo de objeto".
El razonamiento para que yo quiera que no anulable sea explícito (en cada acción que hace un desarrollador) es porque al usar no anulable, el desarrollador está reconociendo que están divergiendo de la forma en que funciona javascript, y que un tipo no es anulable no está garantizado (ya que hay muchas cosas que pueden hacer que esto salga mal).
Una propiedad que no admite valores NULL es una propiedad que nunca se puede eliminar.

He estado usando TypeScript durante bastante tiempo. Fue difícil para mí convencer inicialmente a mi jefe de que cambiara de Javascript a Typecript, y fue bastante difícil para mí convencerlo de que nos dejara actualizar cada vez que había un cambio importante (incluso si no lo hay, no es fácil). Cuando los navegadores admitan ES: Harmony, probablemente querremos utilizarlo. Si ocurre un cambio rotundo en Typecript entre ahora y cuando Typecript admita ES: Harmony (particularmente uno tan frecuente como no nulo), dudo que gane ese argumento. Sin mencionar la cantidad de tiempo y dinero que nos costaría volver a capacitar a todos nuestros desarrolladores (yo soy nuestra "persona de TypeScript", mi trabajo es capacitar a nuestros desarrolladores de TypeScript).
Por lo tanto, estoy realmente en contra de introducir cambios importantes.
No me importaría poder usar no nulables yo mismo, si hubiera una manera de poder introducirlos en el código futuro sin romper el código antiguo (esta es la razón de mi propuesta). Idealmente, eventualmente todo nuestro código se convertiría, pero nos costaría mucho menos tiempo y dinero en capacitar a las personas y usar bibliotecas de terceros.

Me gusta la idea de usar símbolos (según mi sugerencia) en lugar de una directiva por archivo, porque tienen la capacidad de propagarse sin pérdida contextual.

@Griffork Ok, como dije, entiendo tu preocupación, espero que entiendas la mía.
Ahora no tengo la misma opinión, la tecnología cambia, nada es estable (todo el drama alrededor de angular 2 lo ha demostrado), y sí prefiero romper cosas y tener mejores herramientas que quedarme con lo que tengo, sí creo que en a largo plazo me hace ganar tiempo y dinero.
Ahora, como ambos concluimos antes, dudo que tengamos un consenso aquí, prefiero la directiva, usted prefiere su propuesta.
De todos modos, como dije, por mi parte, intentaré bifurcar el compilador y agregar un tipo no nulo, ya que creo que no es un gran cambio en el compilador.
Gracias por la discusión fue interesante

@fdecampredon

Gracias por la discusión fue

Suponiendo que te refieres a esclarecedor (obras interesantes también: D), también disfruté del debate :).
Buena suerte con su bifurcación, y espero que tengamos una persona de TS aquí para al menos descartar algunas de nuestras sugerencias (para que sepamos lo que definitivamente no quieren implementar).

Entonces, para profundizar en mi solución propuesta al problema que describió @RyanCavanaugh , después de agregar el tipo nulo y la bandera --noImplicitNull , el comprobador de tipos cuando se ejecuta sin la bandera tendrá un comportamiento modificado:

Siempre que una sobrecarga de tipo contenga |null (como en el ejemplo dado):

function fn(x: string): number;
function fn(x: number|null): string;

el comprobador de tipos tomará cada sobrecarga y creará nuevas variantes con (implícito) nulo en la posición de ese argumento, agregado al final de la lista:

function fn(x: string): number;
function fn(x: number|null): string;
// implicit signature added at the end, caused by the first overload
function fn(x: null): number;

Esto resulta con la misma interpretación que la versión estricta --noImplicitNull : cuando se pasa un nulo, la primera sobrecarga coincidente es la explícita number|null . Efectivamente, hace nulos explícitos más fuertes que los implícitos.

(Sí, sé que el número de definiciones implícitas crece exponencialmente con el número de argumentos que tienen sobrecargas de |null ; espero que haya una implementación más rápida que "permita" valores nulos en un segundo paso si no hay otros tipos coincidencia, pero no creo que sea un problema de ninguna manera, ya que espero que esta situación sea rara)

Formulado de esta manera, creo que el cambio será, con suerte, totalmente compatible con versiones anteriores y opt-in.

Aquí hay algunas ideas más sobre el resto del comportamiento:

También se permitirá asignar valores nulos o dejar valores sin inicializar para valores de cualquier tipo cuando no se pase la bandera.

Cuando la bandera está encendida, se permitiría dejar valores sin inicializar (sin asignar) o nulos hasta el momento en que se pasen a una función u operador que espera que no sean nulos. No estoy seguro acerca de los miembros de la clase: ellos tampoco

  • debe inicializarse al final del constructor, o
  • La inicialización se puede rastrear verificando si se llamaron a los métodos miembro que los inicializan por completo.

Las uniones nulas explícitas se comportarán de manera similar a todas las demás uniones en ambos modos. Requerirían un guardia para reducir el tipo de unión.

Acabo de leer _todos_ los comentarios anteriores y eso es mucho. Es decepcionante que después de tantos comentarios y 5 meses, sigamos debatiendo los mismos puntos: sintaxis y banderas ...

Creo que muchas personas han presentado argumentos sólidos sobre el valor de la comprobación de nulos estáticos por parte del compilador en bases de código grandes. No diré nada más (pero +1).

Hasta ahora, el debate principal ha sido sobre la sintaxis y el problema de compatibilidad con toneladas de códigos TS existentes y definiciones de bibliotecas TS. Puedo ver por qué la introducción de un tipo nulo y la sintaxis de flujo ?string parece la más limpia y natural. Pero esto cambia el significado de string y, por lo tanto, es un cambio _ enorme_ para _todo_ el código existente.

Las soluciones propuestas anteriormente para este cambio radical son introducir indicadores del compilador ( @RyanCavanaugh ha 'use strict'; . No es porque TS haya heredado errores pasados ​​de JS por lo que debamos repetirlos.

Por eso creo que la anotación "no nula" ( string! , !string , ...) es la única forma de hacerlo e intentaré introducir un nuevo argumento en la discusión: tal vez sea no es tan malo.

Nadie quiere tener flequillo ( ! ) en todo el código. Pero probablemente no tengamos que hacerlo. TS infiere mucho sobre la mecanografía.

1. Variables locales
No espero anotar mis variables locales con explosiones. No me importa si asigno null a una variable local, siempre que la use de una manera segura, que TS es perfectamente capaz de afirmar.

var x: string;  // Nullable
x.toString();  // Error: x can be undefined or null
x = "jods";
x.toUpperCase();  // Fine.
notNull(x);  // Fine

Muy a menudo utilizo un inicializador en lugar de un tipo.

var x = "jods";  // x: string (nullable)
notNull(x);  // Fine
x = null;  // Fine
notNull(x);  // Error

2. Valores de retorno de la función
Muy a menudo no especifico el valor de retorno. Así como TS infiere el tipo, puede inferir si acepta valores NULL o no.

function() /* : string! */ {
  return "jods";
}

EDITAR: mala idea debido a la herencia y las variables de función, vea el comentario de @Griffork a continuación
Evento si doy un tipo de retorno, TS es perfectamente capaz de agregar la anotación "no nula" para mí.

Si desea anular la nulabilidad inferida (por ejemplo, porque una clase derivada puede devolver un valor nulo aunque su método no lo haga), especifique el tipo explícitamente:

function f() : string {  // f is nullable although this implementation never returns null.
  return "abc";
}

3. Parámetros de función
Al igual que tiene que especificar explícitamente un tipo, ahí es donde _debe_ indicar si no acepta los parámetros nulos:

function (x: string!) { return x.toUpperCase(); } // OK
function (x: string) { return x.toUpperCase(); } // Error

Para evitar errores de compilación en bases de código antiguas, necesitamos un nuevo indicador "La desreferencia nula es un error", al igual que tenemos "No implícita ninguna". _¡Esta bandera no cambia el análisis del compilador, solo lo que se informa como un error! _
¿Eso es mucho flequillo para agregar? Es difícil de decir sin hacer estadísticas sobre bases de código grandes y reales. No olvide que los parámetros opcionales son comunes en JS y todos son anulables, por lo que necesitaría esas anotaciones si 'no nulo' fuera el valor predeterminado.
Esos golpes también son buenos recordatorios de que no se aceptan nulos para las personas que llaman, de acuerdo con lo que saben sobre string hoy.

4. Campos de clase
Esto es similar a 3. Si necesita leer un campo sin poder inferir su contenido, debe marcarlo como no anulable en la declaración.

class C {  x: string!; }

function(c: C!) // : string! is inferred
{ return c.x; } // OK, but annotations are required

Podríamos imaginar una abreviatura de inicializador, tal vez:

class C {
  x! = "jods"; // Note the bang: x is inferred as !string rather than just string.
}

Una abreviatura similar es probablemente deseable para las propiedades que aceptan valores NULL si dice que no es nulo es el valor predeterminado con un inicializador.
Nuevamente, es difícil decir si la mayoría de los campos en el código existente son anulables o no, pero esta anotación está muy en línea con las expectativas que los desarrolladores tienen hoy.

5. Declaraciones, .d.ts
El gran beneficio es que todas las bibliotecas existentes funcionan. Aceptan valores nulos y no nulos como entradas y se supone que devuelven un valor posiblemente nulo.
Al principio, necesitará convertir explícitamente algunos valores de retorno a "no nulo", pero la situación mejorará a medida que las definiciones se actualicen lentamente para indicar cuándo nunca devuelven un valor nulo. Anotar los parámetros es más seguro, pero no es necesario para compilar correctamente.

Creo que indicar el estado "no nulo" de un valor en el que el compilador no puede inferirlo puede ser útil (por ejemplo, cuando se manipula código sin escribir o cuando el desarrollador puede hacer algunas afirmaciones sobre el estado global del programa) y una abreviatura podría ser bueno:

var a: { s: string } = something(); // Notice s is nullable.
notNull(a.s); // Error
notNull(<!>a.s);  // OK, this is a shorthand "not-null" cast, because the dev knows about something().

Creo que la capacidad de insinuar al compilador, de una manera simple, que algo no es nulo es deseable. Esto también es cierto para no nulos de forma predeterminada, aunque es más difícil obtener una sintaxis lógica simple.

Lo siento, esta fue una publicación muy larga. Traté de demostrar la idea de que incluso con "nulo por defecto", no tendríamos que anotar todo nuestro código: TS puede inferir la exactitud de la mayoría del código sin nuestra ayuda. Las dos excepciones son campos y parámetros. Creo que esto no es excesivamente ruidoso y es una buena documentación. Un punto fuerte a favor de este enfoque es que es compatible con versiones anteriores.

Estoy de acuerdo con la mayor parte de lo que dijiste @ jods4, pero tengo algunas preguntas e inquietudes:

2) si el compilador puede convertir automáticamente anulable a no anulable en tipos de retorno, ¿cómo puede anular esto (por ejemplo, para herencia o funciones que se reemplazan)?

3 y 5) Si el compilador puede convertir anulable a no anulable, entonces una firma de función en un archivo de código ahora puede comportarse de manera diferente a la misma firma de función en un archivo d.ts.

6) ¿Cómo funciona / se ve la explosión cuando se utilizan tipos de unión?

2) Buena captura. No creo que funcione bien con la herencia (o 'delegados') de hecho.
Donde trabajo, nuestra experiencia con TS es que casi nunca escribimos explícitamente valores de retorno de funciones. TS los resuelve y creo que TS puede descubrir la parte nula / no nula.

Los únicos casos en los que los especificamos explícitamente es cuando queremos devolver una interfaz o una clase base, por ejemplo, para la extensibilidad (incluida la herencia). La nulabilidad parece encajar bastante bien en esta descripción.

Cambiemos mi propuesta anterior: un valor de retorno escrito explícitamente _no_ no se infiere, no admite nulos si no se declara así. Esto funciona bien para los casos que sugieres. Proporciona una forma de anular el compilador que infiere "no nulo" en un contrato que podría ser nulo en las clases secundarias.

3 y 5) Con el cambio anterior, ese ya no es el caso, ¿verdad? La firma de funciones en el código y las definiciones ahora se comportan exactamente de la misma manera.

6) ¡Pregunta interesante!
Este no es muy bueno, especialmente si agrega genéricos a la mezcla. La única salida que se me ocurre es que todas las partes deben ser no nulas. Probablemente necesite un literal de tipo nulo de todos modos debido a los genéricos, vea mi último ejemplo.

var x: number | string!; // compiler error
var x: number | string; // x can be null
var x: number! | string!; // x cannot be null
function f<T>() : number | T; // f can be null
function f<T>() : number! | T; // f is nullable if T is nullable
function f<T, G>(): T | G; // f is nullable if T or G is nullable
function f<T>(): T | null; // f is nullable even if T is not nullable.
function f<T>(): T!; // Whatever T is, f never returns null.

// Generics constraint option 1
function f<T!>(x: T!, y: T): T!; // T: not nullable type, x: not-null, y: null, f: not-null
function f<T!>(x: T!, y: T): T;  // T: not nullable type, x: not-null, y: null, f: null

// Generics constraint option 2
function f<T!>(x: T, y: T | null): T; // same as option 1.1
function f<T!>(x: T, y: T | null): T | null;  // same as option 1.2

En aras de la discusión, tenga en cuenta que si optara por tipos no nulos de forma predeterminada, también necesitaría inventar una nueva sintaxis: ¿cómo expresar que un tipo genérico no debe permitir valores que aceptan valores NULL?
Y a la inversa, ¿cómo especificaría que incluso si el tipo genérico T contiene nulo, una función nunca devuelve T pero nunca nulo?

Los literales de tipo están bien, pero tampoco son muy satisfactorios: var x: { name: string }! , especialmente si tuvieran más de una línea :(

Más ejemplos:
Matriz de números no nulos number![]
Matriz de números no nula: number[]!
Nada puede ser nulo: number![]!
Genéricos: Array<number!>[]

2, 3 y 5) Estoy de acuerdo con el cambio a la propuesta, parece que funcionaría.
6)

function f<T>() : number | T; // f can be null
function f<T>() : number! | T; // f is nullable if T is nullable

Supongo que aquí quiere decir que f puede devolver nulo, no que f pueda asignarse a nulo.

Para los sindicatos, ¿qué piensa de la siguiente sintaxis como otra opción disponible? (parece un poco tonto, pero podría ser más conciso / más fácil de seguir en algunos casos).

var x = ! & number | string;

Para significar que ninguno es anulable.


Para los literales de tipo, diría que puede poner la explosión al principio de la expresión literal así como al final, ya que var x: !{name: string} , será más fácil trabajar con, en mi opinión, que tenerlo al final.

@ jods4 , ¿leíste acerca de mi solución propuesta "nulo explícito tiene prioridad sobre nulo implícito"? Eso se encarga de la cuestión de la semántica variable, creo.

@Griffork
Sí, por supuesto, "es anulable" era una mala redacción para "puede devolver nulo".

Lo que me hace pensar que function f() {} es en realidad una declaración de f , que se puede configurar más adelante ... ¿queremos expresar que f nunca se establecerá en nulo? ? ¿Como function f!() {} ? En mi opinión, eso parece ir demasiado lejos. Creo que el verificador de tipos debería asumir que este es siempre el caso. Hay otros casos extremos poco comunes que no puede manejar de todos modos.

Con respecto a la nueva opción, estoy de acuerdo en que parece un poco tonta e introduce un símbolo adicional en la declaración de tipo: & , con un solo propósito ... Eso no me parece una buena idea. Y en varios idiomas & AND enlaza con mayor prioridad que | OR que no es lo que sucede aquí.

Poner la explosión delante de los tipos es una posible alternativa, supongo que deberíamos intentar reescribir todos los ejemplos para ver qué se ve mejor. Creo que este atajo de campo que sugerí será un problema:

class C {
  name! = "jods";  // Field inferred string, but marked not nullable.
  // Maybe we could do that instead, which works with "!T" convention:
  name : ! = "jods";
  // That kind of make sense with the proposed <!> cast.
}

@espion
Sí lo vi.

Primero, no estoy seguro de que realmente resuelva el problema en cuestión. Entonces, con su nueva definición, ¿qué sobrecarga se une a fn(null) ? ¿El 'generado' que devuelve number ? ¿No es eso inconsistente con el hecho de que la segunda sobrecarga también acepta null pero devuelve string ? ¿O es un error del compilador porque no puede realizar la resolución de sobrecargas?

En segundo lugar, estoy bastante seguro de que podemos encontrar muchos otros problemas. Creo que cambiar el significado global del código fuente con solo presionar un interruptor no puede funcionar. No solo en .d.ts , sino que a la gente le encanta reutilizar el código (reutilizar la biblioteca o incluso copiar y pegar). Su sugerencia es que el significado semántico de mi código fuente depende de la bandera del compilador y eso me parece una proposición muy defectuosa.

Pero si puede demostrar que el código se compilará y se ejecutará correctamente en todos los casos, ¡estoy totalmente de acuerdo!

@ jods4

En ambos casos se utilizará la segunda sobrecarga

function fn(x: string): number;
function fn(x: number|null): string; 

porque el segundo nulo explícito tendrá prioridad sobre el implícito (el primero), sin importar qué bandera se use. Por lo tanto, el significado no cambia según la bandera.

Dado que el tipo nulo no existe en este momento, se introducirían juntos y el cambio será totalmente compatible con versiones anteriores. Las definiciones de tipo antiguo seguirán funcionando con normalidad.

@espion
Entonces, ¿cuál era el problema con el function fn(x: null): number; implícito? Si la segunda sobrecarga siempre tiene prioridad, no importa qué bandera se use, ¿toda esta "solución" no tiene ningún efecto en absoluto?

Otra pregunta: hoy todas las definiciones de lib tienen un comportamiento "nulo permitido". Entonces, hasta que estén _todos_ actualizados, tengo que usar el indicador "implícitamente nulo". Ahora, con esta bandera activada, ¿cómo puedo declarar una función que toma un parámetro no nulo en mi proyecto?

// null by default flag turned on because of 3rd party libs.
function (x: string)   // <- how do I declare this not null?
{ return x.toUpperCase(); }

La solución ciertamente tiene un efecto. Hace que el comportamiento con y sin el indicador sea coherente, lo que soluciona el problema semántico mencionado anteriormente.

En segundo lugar, no tendrá que utilizar un indicador "nulo por defecto" para las bibliotecas de terceros. Lo más importante de esta función es que no tendrá que hacer nada en absoluto para obtener los beneficios en la gran mayoría de los casos.

Digamos que hay una biblioteca que calcula el recuento de palabras de una cadena. Su declaración es

declare function wordCount(s: string): number;

Digamos que su código usa esta función de esta manera:

function sumWordcounts(s1:string, s2:string) {
  return wordCount(s1) + wordCount(s2);
}

Este programa pasa por ambos compiladores: incluso si los nulos implícitos están deshabilitados. El código es totalmente compatible con versiones anteriores.

¿Por qué? Porque el compilador, tal como está hoy, ya tiene la fe de que los valores no son nulos en todos los lugares donde intente usarlos (aunque teóricamente pueden ser nulos).

El nuevo compilador también supondría que los valores no son nulos cuando intenta utilizarlos. No tiene ninguna razón para creer lo contrario, ya que no ha especificado que el valor puede ser nulo, por lo que parte del comportamiento sigue siendo el mismo. El cambio en el comportamiento solo tiene efecto cuando se asigna un valor nulo (o se dejan variables sin inicializar) y luego se intenta pasar esos valores a una función como la anterior. No requieren ningún cambio en otras partes del código que usan valores normalmente.

Ahora veamos la implementación de wordCount

function wordCount(s) {
  if (s == '') return null;
  return s.split(' ').length
}

Vaya, este tipo no cuenta toda la historia. Es posible que esa función devuelva un valor nulo.

El problema es precisamente ese. Es imposible contar toda la historia en el compilador actual, incluso si quisiéramos. Claro, decimos que el valor de retorno es un número, lo que implícitamente dice que puede ser nulo: pero el compilador nunca advierte sobre eso cuando intentamos acceder incorrectamente a ese valor potencialmente nulo. Siempre piensa felizmente que es un número válido.

Después del cambio noImplicitNull , obtendremos exactamente el mismo resultado aquí si usamos las mismas definiciones de tipo. El código se compilará sin quejas. El compilador seguirá pensando felizmente que wordCount siempre devuelve un número. El programa aún puede fallar si pasamos cadenas vacías, como antes (el compilador antiguo no nos advertirá que el número devuelto puede ser nulo, y tampoco lo hará el nuevo, ya que confía en la definición del tipo). Y si queremos el mismo comportamiento, podemos mantenerlo sin cambiar nada en nuestro código. (1)

Sin embargo, ahora _podremos_ hacerlo mejor: podremos escribir una definición de tipo mejorada para wordCount:

declare function wordCount(s:string):string|null;

y obtenemos una advertencia del compilador cada vez que intentamos usar wordCount sin verificar un valor de retorno nulo.

La mejor parte es que esta mejora es completamente opcional. Podemos mantener todas las declaraciones de tipos como estaban y obtener prácticamente el mismo comportamiento que ahora. Las declaraciones de tipo no empeorarán (2), solo se pueden mejorar para ser más precisos en los casos en que lo consideremos necesario.


(1): ya hay una mejora aquí incluso sin mejorar la definición de tipo. con las nuevas definiciones de tipo, no podrá pasar accidentalmente nulo a sumWordcounts y obtener una excepción de puntero nulo cuando la función wordCount intente .split() ese nulo.

sumWordcounts(null, 'a'); // error after the change

(2): ok, eso es mentira. Las declaraciones de tipos empeorarán para un pequeño subconjunto de funciones: aquellas que toman argumentos que aceptan valores NULL que no son argumentos opcionales.

Las cosas seguirán estando bien para argumentos opcionales:

declare function f(a: string, b?:string); 

pero no para argumentos que no son opcionales y pueden ser nulos

declare function f(a: string, b:string); // a can actually be null

Yo diría que esas funciones son bastante raras y las correcciones necesarias serán mínimas.

@espion

La solución ciertamente tiene un efecto. Hace que el comportamiento con y sin la bandera sea consistente.

¿De qué manera, puede dar un ejemplo completo? También dijiste:

En ambos casos se utilizará la segunda sobrecarga

¿Lo que, para mí, implica que la solución no tiene ningún efecto?

Este programa pasa por ambos compiladores: incluso si los nulos implícitos están deshabilitados. El código es totalmente compatible con versiones anteriores.

Sí, se compila sin errores en ambos casos, pero no con el mismo resultado. sumWordCounts() se escribirá como number! en un caso y number? en el segundo. Cambiar la semántica del código con un interruptor es altamente _no_ deseable. Como se demostró anteriormente, este cambio podría tener efectos globales, por ejemplo, en la resolución de sobrecargas.

El nuevo compilador también supondría que los valores no son nulos cuando intenta utilizarlos. No tiene ninguna razón para creer lo contrario

¡No! Este es el punto: quiero que el compilador me arroje un error cuando uso un valor potencialmente nulo. Si "asume" que no es nulo como lo hago yo cuando codifico, entonces esta característica es inútil.

El cambio en el comportamiento solo tiene efecto cuando se asigna un valor nulo (o se dejan variables sin inicializar) y luego se intenta pasar esos valores a una función como la anterior.

No estoy seguro de entender, ya que la "función como la anterior" en realidad acepta valores nulos ...

Al leer el final de su último comentario, tengo la impresión de que no obtendremos un análisis nulo estático real hasta que activemos el interruptor, lo cual no puede hacer hasta que _todas_ las definiciones de su biblioteca estén arregladas. :(

En general, es difícil comprender completamente todos los casos y cómo funciona todo con solo palabras. Algunos ejemplos prácticos serían de gran ayuda.

Aquí hay un ejemplo y cómo me gustaría que se comporte el compilador:

Suponga una biblioteca con funciones yes(): string! que nunca devuelve nulo y no(): string? que pueden devolver nulo.

La definición de .d.ts es:

declare function yes(): string;
declare function no(): string;

Quiero poder crear grandes proyectos en TS que se beneficien de nulos comprobados estáticamente y usar las definiciones de biblioteca existentes. Además, quiero poder hacer la transición _progresivamente_ a una situación en la que todas las bibliotecas se actualicen con la información de nulidad correcta.

Usando el modificador no nulo propuesto ! anterior, puedo hacer eso:

function x(s: string!) {  // inferred : string, could be explicit if we want to
  return s.length === 0 ? null : s;  // no error here as s is declared not null
}

x(no());  // error: x called with a possibly null parameter
y(<!>yes());  // no error because of not null cast. When .d.ts is updated the cast can be dropped.

¿Cómo funcionaría eso con tu idea?

Este hilo es una discusión demasiado larga (¡130 comentarios!), Lo que hace muy difícil seguir las ideas, sugerencias y problemas mencionados por todos.

Sugiero que creemos esencias externas con nuestras propuestas, incluida la sintaxis sugerida, lo que acepta TS, lo que es un error, etc.

@spion, dado que su idea implica

Aquí está la esencia del marcador !T no nulo:
https://gist.github.com/jods4/cb31547f972f8c6bbc8b

Es casi lo mismo que en mi comentario anterior, con las siguientes diferencias:

  • Me di cuenta de que el compilador puede inferir de forma segura el aspecto 'no nulo' de los parámetros de la función (gracias a @spion por esa información).
  • Incluí comentarios de !T lugar de T! .
  • Agregué algunas secciones, por ejemplo, sobre el comportamiento del constructor.

No dude en comentar, bifurcar y crear propuestas alternativas ( ?T ).
Intentemos hacer avanzar esto.

@ jods4

En ambos casos se utilizará la segunda sobrecarga

¿Lo que, para mí, implica que la solución no tiene ningún efecto?

  1. Implica que se cambia la resolución de sobrecarga para que la semántica del lenguaje sea coherente para ambos indicadores.

Sí, se compila sin errores en ambos casos, pero no con el mismo resultado. ¡sumWordCounts () se escribirá como número! en un caso y número? en el segundo. Cambiar la semántica del código con un conmutador no es deseable. Como se demostró anteriormente, este cambio podría tener efectos globales, por ejemplo, en la resolución de sobrecargas.

  1. No hay number! en mi propuesta. El tipo será simplemente number en ambos casos. Lo que significa que la resolución de sobrecarga continúa funcionando normalmente, excepto con valores nulos, en cuyo caso el nuevo comportamiento para nulos explícitos que tienen prioridad sobre los implícitos normaliza la semántica de una manera compatible con versiones anteriores.

¡No! Este es el punto: quiero que el compilador me arroje un error cuando uso un valor potencialmente nulo. Si "asume" que no es nulo como lo hago yo cuando codifico, entonces esta característica es inútil.

  1. El punto que estaba tratando de expresar es que hay muy poca incompatibilidad hacia atrás de la función (en términos de declaraciones de tipo). Si no lo usa, obtiene exactamente el mismo comportamiento que antes. Si desea usarlo para expresar la posibilidad de que se devuelva un valor de tipo nulo, ahora puede hacerlo.

Es como si el compilador hubiera usado el tipo string para valores de tipo object y string antes, permitiendo todos los métodos de cadena en todos los objetos y nunca revisándolos. Ahora tendría un tipo separado para Object , y puede comenzar a usar ese tipo en su lugar para indicar que los métodos de cadena no siempre están disponibles.

No estoy seguro de entender, ya que la "función como la anterior" en realidad acepta valores nulos ...

Echemos un vistazo a esta función:

function sumWordcounts(s1:string, s2:string) {
  return wordCount(s1) + wordCount(s2);
}

Bajo la nueva bandera del compilador, no podría llamarlo con valores nulos, por ejemplo, sumWordCounts(null, null); y esa es la única diferencia. La función en sí se compilará porque la definición de wordCounts dice que toma una cadena y devuelve un número.

Al leer el final de su último comentario, tengo la impresión de que no obtendremos un análisis nulo estático real hasta que activemos el interruptor, lo que no puede hacer hasta que todas las definiciones de su biblioteca estén arregladas. :(

Una gran mayoría de código simplemente no trata realmente con valores nulos o indefinidos, aparte de verificar si son nulos / indefinidos y arrojar un error para evitar la propagación de ese valor a través de la base de código a un lugar completamente no relacionado, lo que lo hace difícil. depurar. Hay algunas funciones aquí y allá que usan argumentos opcionales, que son reconocibles y probablemente se pueden modelar en consecuencia con el nuevo modificador. No es tan frecuente que las funciones devuelvan valores nulos como mi ejemplo (que lo usé solo para hacer un punto sobre la función, no como un caso representativo)

Lo que estoy diciendo es que la gran mayoría de las definiciones de tipo permanecerán correctas, y para los pocos casos restantes en los que necesitamos hacer la corrección, podríamos optar por no corregirlos si consideramos que el caso nulo no es importante, o para cambie el tipo en consecuencia, si nos importa. Lo cual está completamente en línea con el objetivo mecanografiado de "aumentar progresivamente el dial" para obtener garantías más sólidas siempre que las necesitemos.


Bien, entonces las definiciones de tipo existentes son

declare function yes(): string;
declare function no(): string;

Y digamos que su código se escribió antes de que se agregara la función también:

function x(s: string) {
  return s.length === 0 ? null : s;
}

Bajo el nuevo compilador, el comportamiento será exactamente el mismo que el anterior.

x(no());  // no error
x(yes());  // no error

A menos que intente algo que el compilador sabe que puede ser nulo, como el resultado de x ()

x(x(no())) // error, x(something) may be null

Observa no() y puede decidir que los casos en los que devuelve nulo son raros, por lo que no va a modelar eso, o puede corregir la definición de tipo.

Ahora veamos qué sucede con su propuesta: cada línea de código que incluso toca una biblioteca externa se rompe. Las funciones que tenían definiciones de tipo perfectamente válidas como las anteriores también se rompen. Debe actualizar cada anotación de argumento en todas partes y agregar ! para que el compilador deje de quejarse, o agregar comprobaciones nulas en todas partes.

La conclusión es que el sistema de tipos en TypeScript actualmente es incorrecto. Tiene una interpretación dual de valores nulos:

Cuando intentamos asignar nulo a alguna variable de tipo T , o pasamos nulo como argumento donde se requiere un tipo T , actúa como si el tipo fuera T|null .

Cuando intentamos usar un valor de tipo T , actúa como si el tipo fuera T y no hay posibilidad de nulo.

Su propuesta sugiere tratar todos los tipos normales T como T|null siempre; el mío sugiere tratarlos como solo T . Ambos cambian la semántica. Ambos hacen que el compilador se comporte "correctamente"

Mi argumento es que la variante "solo T " es mucho menos dolorosa de lo que parece y está en sintonía con la gran mayoría del código.

editar: Me acabo de dar cuenta de que su propuesta puede ser seguir tratando los tipos sin "!" o "?" de la misma manera que antes. Eso es compatible con versiones anteriores, sí, pero mucho trabajo para obtener beneficios (ya que la gran mayoría del código simplemente no trata con valores nulos que no sean verificar / lanzar.

@espion

Implica que se cambia la resolución de sobrecarga para hacer que la semántica del lenguaje sea consistente para ambos indicadores.

Simplemente está afirmando el mismo hecho, pero todavía no me queda claro a qué caso se refiere y de qué manera.
Por eso pedí un ejemplo concreto.

1. No hay number! en mi propuesta. El tipo será simplemente number en ambos casos.

Eso es incorrecto, sé que la sintaxis es diferente pero los conceptos subyacentes son los mismos. Cuando digo number! , estoy destacando un tipo de número no nulo, que en su propuesta sería simplemente number . Y el segundo caso no sería number en su caso, sino number | null .

Una gran mayoría de código simplemente no trata realmente con valores nulos o indefinidos, aparte de verificar si son nulos / indefinidos y arrojar un error para evitar la propagación de ese valor a través de la base de código a un lugar completamente no relacionado, lo que lo hace difícil. depurar. Hay algunas funciones aquí y allá que usan argumentos opcionales, que son reconocibles y probablemente se pueden modelar en consecuencia con el nuevo modificador. No es tan frecuente que las funciones devuelvan valores nulos como mi ejemplo (que lo usé solo para hacer un punto sobre la función, no como un caso representativo)

Creo que descripciones como esta hacen _muchas_ suposiciones y atajos. Por eso creo que para avanzar necesitamos pasar a ejemplos más concretos con código, sintaxis y explicaciones de cómo funciona el sistema. Tu dices:

La gran mayoría del código simplemente no trata realmente con valores nulos o indefinidos, aparte de verificar si son nulos / indefinidos y arrojar un error

Muy discutible.

Hay algunas funciones aquí y allá que usan argumentos opcionales.

Aún más discutible. Las bibliotecas JS están llenas de ejemplos de este tipo.

, que son reconocibles y probablemente se pueden modelar en consecuencia con el nuevo interruptor

Haga una propuesta más concreta porque es un atajo enorme. Creo que puedo ver hacia dónde se dirige esto para el código JS real, pero ¿cómo "reconocería" un argumento opcional dentro de un declare function(x: {}); o dentro de un interface ?

No es tan frecuente que las funciones devuelvan valores nulos como mi ejemplo.

De nuevo muy discutible. Muchas funciones devuelven un null o undefined : find (cuando no se encuentra el elemento), getError (cuando no hay error) y así sucesivamente ... Si desea más ejemplos, solo mire las API de navegador estándar, encontrará _plenty_.

Lo que estoy diciendo es que la gran mayoría de las definiciones de tipos seguirán siendo correctas.

Como puede deducir de mis comentarios anteriores, no estoy convencido de eso.

y para los pocos casos restantes en los que necesitamos hacer la corrección, podríamos optar por no arreglarlos si consideramos que el caso nulo no es importante, o cambiar el tipo en consecuencia, si nos importa. Lo cual está completamente en línea con el objetivo mecanografiado de "aumentar progresivamente el dial" para obtener garantías más sólidas siempre que las necesitemos.

En este punto, esta afirmación no me parece trivial. ¿Puede dar ejemplos concretos de cómo funciona? Especialmente la parte _progresivamente_.

Ahora veamos qué sucede con su propuesta: cada línea de código que incluso toca una biblioteca externa se rompe. Las funciones que tenían definiciones de tipo perfectamente válidas como las anteriores también se rompen. Debe actualizar cada anotación de argumento en todas partes y agregar ! para que el compilador deje de quejarse, o agregar comprobaciones nulas en todas partes.

Esto es casi completamente incorrecto. Por favor, lea atentamente la esencia que escribí. Notará que en realidad se requieren pocas anotaciones no nulas. Pero hay _un_ cambio importante, sí.

Suponga que toma una base de código existente que usa definiciones de biblioteca e intenta compilarla con mi propuesta sin cambiar ningún código:

  • Obtendrá muchos beneficios de análisis nulo, incluso sin anotaciones (más o menos de la misma manera que lo obtiene en el compilador de Flow).
  • Una sola cosa se romperá: usar el valor de retorno de una función de biblioteca que sabe que no devuelve nulo.
declare function f(): string; // existing declaration, but f will never return null.
var x = f();
x.toUpperCase();  // error, possibly null reference.

La solución a largo plazo es actualizar las definiciones de la biblioteca para que sean más precisas: declare function f(): !string;
La solución a corto plazo es agregar un elenco no nulo: var x = <!>f(); .
Y para ayudar a que los proyectos grandes con muchas dependencias se actualicen más fácilmente, sugiero agregar un indicador de compilador similar a "no implícito ninguno": "tratar las posibles referencias nulas como advertencia". Esto significa que puede usar el nuevo compilador y esperar hasta que las bibliotecas tengan una definición actualizada. Nota: a diferencia de su propuesta, esta bandera no cambia la semántica del compilador. Solo cambia lo que se informa como un error.

Como dije, no estoy seguro de que estemos avanzando en este debate. Le sugiero que mire mi esencia y cree una con sus propias ideas, ejemplos de código y comportamiento en ambos estados de su bandera. Como todos somos programadores, creo que quedará más claro. También hay muchos casos extremos a considerar. Habiendo hecho eso, tengo toneladas de preguntas para hacer en situaciones especiales, pero realmente no sirve de nada discutir sobre conceptos e ideas sin una definición precisa.

Hay demasiadas discusiones sobre diferentes cosas que suceden en este número de más de 130 comentarios.

Sugiero que continuemos con la discusión _general_ aquí.

Para las discusiones de propuestas concretas que pueden implementar tipos no nulos en TS, sugiero que abramos nuevos temas, cada uno sobre una única propuesta de diseño. Creé # 1800 para discutir la sintaxis !T .

@spion Te sugiero que crees un problema para tu diseño también.

Mucha gente quiere tipos no nulos. Es fácil en nuevos lenguajes (p. Ej., Rust) pero es muy difícil adaptarlo a los lenguajes existentes (la gente ha estado pidiendo referencias no nulas en .net durante mucho tiempo, y no creo que nunca lo consigamos ellos). Al mirar los comentarios aquí, se muestra que no es un tema trivial.
Flow me ha convencido de que esto se puede hacer para TS, ¡intentemos que suceda!

@ jods4 Lamento decirlo, pero su propuesta no tiene nada que ver con el tipo no nulo, está más relacionada con algún tipo de análisis de flujo de control, difícilmente predecible, y que rompe completamente la retrocompatibilidad (de hecho, la mayor parte del código que es válido 1.4 ts fallará según la regla descrita en su esencia).
Estoy de acuerdo con el hecho de que la discusión no va a ninguna parte y que 130 comentarios + es quizás demasiado. Pero quizás sea porque hay 2 grupos de personas discutiendo aquí:

  • aquellos que piensan que el tipo no nulo debería ser el predeterminado y quieren encontrar una manera de hacer que eso suceda (a través de una bandera o cualquier otro mecanismo)
  • aquellos que no quieren un tipo no nulo y simplemente intentan evitar su introducción, o introducirlos en una nueva sintaxis.

Esos 2 grupos de pueblos han dejado de escuchar desde hace mucho tiempo los argumentos de los demás, y al final lo que necesitamos es el punto de vista del equipo de TS, hasta entonces personalmente dejaré de intentar discutir más sobre este tema.

@fdecampredon
Con respecto a la parte sobre mi esencia, copié y pegué sus argumentos en el n. ° 1800 y si está realmente interesado, podemos discutir por qué es un mal diseño allí. Realmente no entiendo tu punto de vista, pero no quiero empezar a discutir aquí, por eso creé el número 1800 en primer lugar.

Con respecto a este hilo, estoy de acuerdo contigo en que la discusión no va a ninguna parte ...

Como puede deducir de mis comentarios anteriores, no estoy convencido de eso.

Bueno, realmente no sé cómo explicarlo de otra manera, ya lo he explicado varias veces. Déjame intentarlo una vez más.

Ahora mismo no puedes expresar eso

declare function err():string;

devuelve nulo. Es imposible, porque mecanografiado siempre te permitirá hacer err().split(' ') . No puede decir "eso puede no ser válido".

Después de este cambio, podrá cambiar string a ?string o string|null

Pero si no lo hace, _no pierde nada_ que tenía antes del cambio:

No tenía verificación nula antes del cambio y no tiene después (si no hace nada). Argumenté que esto es en su mayoría compatible con versiones anteriores. Queda por demostrar en bases de código más grandes, por supuesto.

La diferencia es: no se podía forzar la comprobación de nulos antes del cambio, pero se _puede_ después (progresivamente, primero en las definiciones que más le interesan, luego en otros lugares)

@fdecampredon Sigo discutiendo el tema porque siento que hay muchos conceptos erróneos sobre el tema y sobre lo "difícil" y "estricto" que será aprovechar la función. Sospecho que muchos de los problemas potenciales están muy exagerados. Probablemente deberíamos intentar implementar una variante básica de --noImplicitNull en una bifurcación; no estoy seguro de poder encontrar la hora en el próximo período, pero si lo hago, lo intentaré. , Suena como un proyecto divertido.

@spion , estoy a favor de que se declaren nulos con la palabra clave nula, pero ¿cuál es el comportamiento entre eso y lo indefinido en su propuesta?

Y @fdecampredon, mi única solicitud fue no tener un gran cambio

Supongo que @spion en su ejemplo actual podría continuar codificando Typecript de la misma manera que lo hacemos ahora, si asume que una variable local, si no está asignada, se convierte en parámetros que aceptan valores NULL y de función con un? también puede ser nulo. Bueno, excepto por ese ejemplo que tuvo anteriormente con los parámetros de función temprana que aceptan valores NULL.

@espion
Lo que entiendo de tu último comentario es enormemente diferente de lo que entendí antes ...

Por tanto, : string es "su antiguo tipo de cadena que nunca se comprobará en busca de nulidad, como <any> nunca se comprueba si el tipo es correcto".

La nueva sintaxis : string | null es compatible con versiones anteriores porque no existía antes y acceder a este tipo sin una verificación nula es un error.

Eso es interesante y es difícil comprender todas las implicaciones. Tendré que pensarlo durante algún tiempo.

La primera pregunta que me viene a la mente es cómo se imponen restricciones a los valores de entrada (por ejemplo, parámetros de función):

declare f(x: string): void;
f(null);

¿Está bien o es un error? Si está bien, ¿cómo puedo convertirlo en un error?

_Sigo pensando que alguna idea dentro de esta discusión está perdida, ¿no quieres abrir un nuevo número con esta idea tuya? Podríamos discutirlo allí.

@jods eso sería un error:

declare f(x: string): void; //string must not be null.
declare f(x: string|null): void; //string may be null (not sure about undefined here).
declare f(x?: string): void; //I assume x may be null or undefined.

@ jods4 No creo que necesitemos crear varios problemas en un tema, entonces las cosas se volverán más difíciles de rastrear, ya que tendrás que ir a 10 propuestas diferentes solo para ver si ha sucedido algo / suscribirte a 10 diferentes. . Y todos tendrían que tener enlaces a las otras propuestas en sus presentaciones para que todos los que buscan la no nulabilidad no solo vean y voten por una.

@fdecampredon Sé que hay muchas publicaciones y muchas cosas sin resolver, pero eso no significa que debamos dejar de buscar una solución que guste a todos. Si no le gusta el hecho de que todavía estamos felices de hablar sobre esto y explicarnos, entonces puede darse de baja del tema.

@Griffork
Pero solo después de haber encendido la bandera mágica, ¿verdad?
Entonces, con la bandera desactivada, tiene un código desmarcado, excepto los nuevos tipos de nullables que no existían antes; luego, cuando enciende la bandera, ¿todos los tipos no aceptan nulos y están marcados?

Creo que el resultado final es probablemente la mejor solución. _Pero está rompiendo prácticamente todo el código que existe hoy ._ Imagina que tengo 300K líneas de código ... Tengo que agregar una anotación anulable en todos los lugares donde un tipo es realmente anulable antes de poder activar la bandera. Eso es un gran problema.

Si lo entiendo correctamente, esto es bueno para proyectos nuevos (una vez que se actualizan .d.ts ) pero muy doloroso para el código existente.

@Griffork Mi problema con este tema único y su largo hilo de comentarios es que es muy difícil tener una discusión enfocada en una sola propuesta. Suscribirse a 3 o 4 números no es gran cosa, y tiene un buen contexto en cada uno.

La propuesta original de @spion no tenía bandera mágica. Fue un cambio fundamental en la forma en que funcionaba el mecanografiado.

Sí, pero alguien que participe en esta discusión debería leer todo lo que vino antes. Si cree que la gente no debería leer todo lo que vino antes (y, por lo tanto, potencialmente proponer las mismas propuestas) _entonces_ podemos dividirlo; pero no estoy de acuerdo. El punto de que esté en un solo lugar es que, aunque estábamos hablando de diferentes detalles o diferentes posibles implementaciones , todos estábamos hablando de lo mismo .
El mismo tema, no diferentes temas.
Toda la conversación está centralizada y ninguna queda enterrada por no recibir respuesta.

Tampoco puede desglosar esta conversación por detalles de implementación, porque muchas propuestas a menudo tocarán muchos detalles diferentes que se están debatiendo actualmente.

Separar las propuestas entre sí significa que si alguien detecta un defecto, tiene que ir a varios hilos diferentes y escribirlo. Las personas no están aprendiendo de los errores de los demás entonces, y las personas tienen la capacidad (lo que harán) de aislarse en una discusión y repetir los errores descritos en otras discusiones.

El punto es: dividir la conversación hará que las personas tengan que repetir muchas publicaciones debido a que los espectadores son demasiado perezosos para leer todo en todas las demás publicaciones vinculadas (asumiendo que todas las publicaciones relevantes vinculan todas las demás publicaciones). ).

Además, @ jods4 es solo un cambio importante cuando asigna nulo a algo.

Me parece un cambio mucho menos importante que su propuesta, que tendría todas las definiciones que no deberían tener nulos y ser asignables, deben ser revisadas / tocadas antes de que pueda usarlas y obtener los beneficios de los tipos que no aceptan nulos.

Aquí hay un ejemplo de cómo podría ser el proceso de "actualización" para las propuestas de @ jods4 y @spion (asumiendo que los indicadores necesarios están activados):

function a(arg1: string): string;
a("mystr").toLowerCase(); //errors in <strong i="11">@jods4</strong>'s proposal because a may return null.
a("mystr").toLowerCase(); //fine in <strong i="12">@spion</strong>'s proposal.

a(null).toLowerCase(); //fine in <strong i="13">@jods4</strong>'s proposal because a may accept null.
a(null).toLowerCase(); //errors in <strong i="14">@spion</strong>'s proposal since a doesn't accept null.

Al final, ambos son cambios importantes. Ambos son igualmente dolorosos de depurar, con errores que ocurren en el uso de variables en lugar de en la declaración de variables. La diferencia es que uno comete errores cuando se asigna nulo a algo (@spion) y el otro error cuando no se asigna nulo a algo (@ jods4). Personalmente, asigno nulos a las cosas con mucha menos frecuencia que cuando no asigno nulos a las cosas, por lo que @spion es un cambio menor para mí para trabajar.

Otras cosas que me gustan:

  • Los tipos de variables no me cambian dinámicamente. Si quisiera, usaría flow:
var s: string;
s.toUpperCase(); // error
var t = (s || "test").toUpperCase(); // ok. Inferred t: string
yes(t);  // ok
t = no();
yes(t);  // error

Personalmente, querría un error si llamo a no (), no un cambio implícito de tipo.

  • Nullables que contienen la palabra nulo, en lugar de algún símbolo, es más fácil de leer y menos de recordar. En realidad ! debe usarse para "no" no "no nulo". Odio tener que recordar lo que hace un símbolo, así como odio los acrónimos, nunca puedo recordarlos y ralentizan significativamente mi velocidad de trabajo.

@ jods4 Sé que es difícil para la gente ponerse al día con la conversación actual, pero dividirla en temas separados no ayudará, ya que tendremos que plantear todos estos puntos en todos ellos eventualmente porque el desinformado hará las mismas preguntas que hicimos. Eso realmente no va a _ayudar_ a la conversación, solo hará que sea más difícil mantenerla y seguirla.

@Griffork, honestamente, lo leí todo, pero dudo que mucha gente lo haga. Fue en muchas discusiones secundarias diferentes y no relacionadas y perdí la pista más de una vez. Pero no hagamos este hilo más pesado de lo que ya es y lo dejemos así.

Es un cambio rotundo cada vez que tiene algo que admite nulos (lo que implica que se le asigna un valor nulo en algún momento, de lo contrario, no sería realmente anulable). El error se informará cuando se asigne / pase nulo a una función, pero la solución está en la declaración. Esto incluye declaraciones de funciones, declaraciones de variables, declaraciones de campos en clases ... Muchas cosas tienen que ser revisadas y modificadas potencialmente.

@ jods4 las discusiones secundarias no relacionadas deberían haberse bifurcado, no la información central real. Pero es demasiado tarde para volver atrás y cambiar eso ahora.

@spion Creo que tengo una solución más simple para el problema de @RyanCavanaugh con la bandera '-nonImplicitNull', la idea es bastante simple, solo tenemos que no permitir ?type type|null o type|undefined sin esta bandera.

@fdecampredon, entonces necesita dos versiones de cada biblioteca disponible y actualizada.
También mataría nuestro proceso donde tenemos un proyecto no estricto para probar código y ejecutar pruebas que dependen del código de un proyecto más estricto.

@ jods4

declare f(x: string): void;
f(null);

Al usar la bandera --noImplicitNull , sí, eso sería un error.

No me siento cómodo abriendo una propuesta oficial todavía, quiero al menos hacer algunos experimentos mentales más o tal vez intentar implementar una versión más simple de la idea en una bifurcación.

Me di cuenta de que la propuesta !T también romperá mucho código. No porque haya cambiado la semántica de los tipos existentes, sino porque el nuevo análisis generará errores en muchos lugares.

Así que lo cerré. Si rompemos mucho código, prefiero el otro enfoque, parece más limpio.

Ahora estoy convencido de que no hay forma de introducir esta función sin romper mucho código existente. Esto sigue siendo bueno para todo el código que aún no se ha escrito y posiblemente el código antiguo pueda continuar compilándose sin los beneficios de las comprobaciones nulas por parte del compilador, que es exactamente como es hoy.

Me pregunto cuál es la postura del equipo oficial de TS con respecto al aspecto rompedor de esto frente a sus beneficios.

Es muy poco probable que nos tomemos un descanso tan grande como "hacer que todo no sea anulable por defecto". Si hubiera alguna propuesta que _sólo_ emitiría nuevos errores en casos que obviamente son errores, eso podría estar sobre la mesa, pero propuestas como esa son difíciles de conseguir: wink:

Si el alcance del impacto fuera menor, cambios en la resolución de sobrecarga al pasar null como argumento, por ejemplo, cuando el comportamiento exacto no es necesariamente obvio de inmediato, eso podría ser una historia diferente.

: +1: persona cuerda detectada: @RyanCavanaugh

¿Qué tal si no hay cambios en la forma existente en que funcionan las cosas, sino la introducción de un tipo nulo para uniones, que lo obliga a proteger la variable antes de usarla (o asignarla a una variable de tipo no nulo)? El comportamiento predeterminado aún permite que se asignen nulos a las variables escritas normalmente, así como a las variables escritas con nulo, sin embargo, una función que puede devolver un valor nulo si se marca correctamente forzará una conversión.

De esta manera, no hay cambios importantes, sin embargo, con las buenas prácticas, el sistema de mecanografía aún puede detectar un montón de errores con la escritura inferida y las buenas prácticas de codificación.

Luego (¿más tarde tal vez?) Puede agregar un indicador --noImplicitNullCast que evita que se asigne nulo a variables que no tienen nulo como parte de su escritura (esto funciona más o menos como la sugerencia de @spion con el indicador habilitado).

Me imagino que esto no sería muy diferente a todos los tipos normales y la adición de la bandera --noImplicitAny.

@Griffork
La primera mitad del paquete (agregue un tipo null y prohíba la desreferencia sin protección) no es 100% compatible. Romperás más código de lo que crees. Considera esto:

Con esta nueva capacidad, se actualizarán muchas definiciones de lib, incluida la incorporada. Digamos que cambian algunas firmas para incluir explícitamente nulo como un posible valor de retorno. Algunos ejemplos:

interface Array<T> {
  find(predicate: (T) => bool) : T | null;
  pop() : T | null;
}
interface Storage {
  getItem(key: string) : any | null;
}

Hay muchas apis de este tipo: find devuelve undefined si ningún elemento de la matriz coincide con el predicado, pop devuelve undefined si la matriz está vacía, localStorage.getItem o sessionStorage.getItem ambos devuelven null si no se encontró la clave.

El siguiente código es legítimo y está perfectamente compilado antes. Ahora se romperá con un error:

var xs: string[];
if (xs.length > 0) return xs.pop().trim();  // error xs.pop() may be undefined (false positive)

var items : { id: number }[];
var selectedId : number;
// Assume we are sure selectedId is amongst items
var selectedItem = items.find(x => x.id === selectedId);
selectedItem.toString(); // error selectedItem may be undefined (false positive)

La misma idea si busca algo de localStorage que sabe que está allí. Hay mucho código como ese y ahora requiere un reparto (o alguna sintaxis nueva) para compilar y decirle al compilador: suponga que esto no es nulo, sé lo que estoy haciendo.

Eso incluirá algunos errores reales, seguro. Pero también incluirá muchos falsos positivos y estos son cambios importantes. En 100K + LOC, eso no es una actualización trivial del compilador.

Esto probablemente significa que el único enfoque que funcionaría es: el código nuevo escrito desde cero o el código antiguo migrado aprovechan al máximo la verificación de nulos; el código heredado no tiene ningún beneficio. Esto está impulsado por una opción del compilador (--enableNullAnalysis) y sin una ruta de migración de "transición" o "progresiva".

Bueno, sí. Pero siempre puede congelar lib.d.ts para un proyecto o tomar uno antiguo.
El nuevo tecleo romperá el código antiguo, eso sucederá de todos modos. Casi todos los tipos nuevos cuando se introducen requieren que el código antiguo se actualice de una forma u otra para que funcione correctamente (por ejemplo, uniones, genéricos).
Este cambio significa que aquellos que quieran ignorar la actualización o congelar su biblioteca / definiciones de código no sufrirán.
Teóricamente, la mayoría de los códigos que utilizan esas funciones deben protegerse de todos modos (y normalmente se encuentran en bases de código grandes), porque si no lo hace, hay errores al acecho en su código. Por lo tanto, es más probable que este cambio atrape errores que cause cambios de ruptura generalizados.

Editar
@ jods4 ¿por qué usó código inseguro como ejemplo cuando ese es exactamente el tipo de código que podría causar un error que estamos tratando de detectar al hacer este cambio?

Editar 2
Si no se rompen _some_ (mal / inseguro) código, entonces no hay razón para hacer cualquier cambio que ver con este tema nunca.

Pero, de nuevo, dado que esto es solo un problema de sintaxis, todo se compilaría correctamente, solo produciría un error por todas partes.

_La nueva escritura romperá el código antiguo, eso sucederá de todos modos.

Este no es el caso. Excepto en circunstancias excepcionales, no estaríamos agregando nuevas funciones que rompan el código existente. Los genéricos y las uniones no se agregaron a lib.d.ts de tal manera que requiera que los consumidores de lib.d.ts actualicen su código base para poder usar la nueva versión del compilador. Sí, la gente podría optar por usar una versión anterior de la biblioteca, pero simplemente no es el caso de que vayamos a realizar cambios de idioma que rompan una gran cantidad de código existente (como es el caso de prácticamente todos los idiomas principales). Habrá una excepción ocasional a esto (https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes) pero serán pocas y distantes entre sí, la mayoría de las veces si creemos que el código que estamos rompiendo solo podría ser un insecto.

Pero siempre puede congelar los lib.d.ts para un proyecto o tomar uno antiguo

Congelar lib.d.ts (y cualquier otro .d.ts que pueda usar BTW) tiene implicaciones muy fuertes. Significa que no puedo obtener actualizaciones sobre las bibliotecas que uso o las nuevas apis HTML. Esto no es algo para tomar a la ligera.

Teóricamente, la mayoría de los códigos que utilizan esas funciones deben protegerse de todos modos (y normalmente se encuentran en bases de código grandes), porque si no lo hace, hay errores al acecho en su código.

JS tiene la tradición de devolver undefined (oa veces null ) en muchos casos que arrojarían un error en otros idiomas. Un ejemplo de esto es que es Array.prototype.pop . En C #, sacar de una pila vacía arrojaría. Entonces se podría decir que siempre devuelve un valor válido. En Javascript, al hacer estallar una matriz vacía se devuelve undefined . Si tu sistema de tipos es estricto, tienes que encargarte de ello de alguna manera.

Sabía que ibas a responder eso, por eso escribí ejemplos que son códigos legítimos y que funcionan. En bases de código grandes, encontrará muchos ejemplos en los que una api puede devolver nulo en algunas situaciones, pero sabe que es seguro en su caso específico (por lo que ignora cualquier verificación de seguridad).

Hace solo una hora estaba mirando la parte de micro tarea de una biblioteca. La parte principal básicamente se reduce a esto:

class MicroTasks {
  queue: Array<() => void>;

  flushQueue() {
    while (queue.length > 0) {
      let task = queue.pop();
      task();  // error possible null dereference (not!)
     }
  }
}

Hay muchos casos como este.

Con respecto a su Edición 2: sí, es de esperar que este cambio _ detectará errores y señalará un código no válido. Estoy convencido de que es útil y por eso me gustaría que sucediera de alguna manera. Pero el equipo de TS considerará los cambios importantes en el código _valid_. Hay compensaciones que deben decidirse aquí.

@danquirk Fair, entonces supongo que esta característica nunca se podrá implementar.
@ jods4 Entiendo lo que estás diciendo, pero los nullables nunca se pueden implementar sin romper ese código.

Triste, ¿debería finalmente cerrar ese tema?

Probablemente.

Creo que si nos mantenemos alejados del argumento anulable y no anulable ...

El problema se puede abordar (o mitigar) de una manera no intrusiva con un conjunto de nuevas características:

  • Introduzca el símbolo null-typeguard ?<type>
    Si un tipo está anotado con este símbolo, entonces es un error acceder a él directamente.

`` `TypeScript
var foo:? cadena;

foo.indexOf ('s'); // Error
foo && foo.indexOf ('s'); // Okey
''

  • Introducir una bandera --nouseofunassignedlocalvar

`` `TypeScript
var foo: cadena;

foo.indexOf ('s'); // error

foo = 'barra';

foo.indexOf ('s'); // bueno
''

Nota histórica interesante : mientras intentaba encontrar un problema relacionado con esto, encontré un problema antiguo en el codeplex que menciona una opción de compilador --cflowu en febrero de 2013. @RyanCavanaugh , me pregunto qué pasó con esa bandera.

  • Presentar al operador de navegación segura # 16

TypeScript var x = { y: { z: null, q: undefined } }; console.log(x?.y?.z?.foo); // Should print 'null'

En combinación, estas características ayudarían a atrapar más errores en torno al uso de null sin causar realmente un ataque de nervios a todos.

@NoelAbrahams :
Tu primera propuesta es esencialmente la misma que la última, solo estás usando un? en lugar de | null (lea la publicación de @ jods4 sobre los problemas con la actualización de lib.d.ts y los cambios importantes).

Su segunda propuesta tiene un problema previamente abordado por @RyanCavanaugh (arriba):

Las banderas que cambian la semántica de un idioma son algo peligroso. Un problema es que los efectos son potencialmente muy no locales:
...[recorte]...
El único tipo de cosa segura que se puede hacer es mantener la misma semántica de asignabilidad y cambiar lo que es un error y lo que no depende de una bandera, al igual que noImplicitAny funciona hoy.

Estoy bastante seguro de que anteriormente se propuso una bandera no diferente y luego se abandonó debido al comentario de

Su tercera propuesta tiene muy poco que ver con el tema actual (ya no se trata de escribir y de errores en tiempo de compilación, sino de detectar errores en tiempo de ejecución). La razón por la que digo esto es porque la razón por la que se creó este tema fue para ayudar a reducir la necesidad de realizar verificaciones nulas o indefinidas en variables que se sabe que son "seguras" (con una manera fácil de realizar un seguimiento de eso), sin agregar nuevos valores nulos. o cheques indefinidos en todas partes.

¿Podría ser posible implementar una de las propuestas sugeridas y simplemente no actualizar lib.d.ts para usar los nullables (y las otras bibliotecas)? Entonces, ¿la comunidad podría mantener / usar sus propias versiones con nullables si lo desea?

Editar:
Específicamente, todas las bibliotecas contienen la información más reciente, pero no tienen su escritura actualizada para requerir protectores de tipos.

@RyanCavanaugh Volveré y discutiré eso si / cuando tenga un parche que demuestre que no es un cambio tan grande (especialmente si los archivos .d.ts no están actualizados).

Las definiciones de tipo

De todos modos, parece que este problema es una causa perdida.

Por cierto, hubo un compilador modificado por MSR que hizo esto, entre otras cosas: ¿hay documentos disponibles con sus hallazgos? Editar: lo encontré: http://research.microsoft.com/apps/pubs/?id=224900 pero desafortunadamente, no estoy seguro de que esté relacionado.

@Griffork , sí, estoy seguro de que casi todo se ha discutido de una forma u otra, dada la duración de la discusión. Mi resumen es práctico sobre cómo mitigar los problemas relacionados con nulos mediante la introducción de una serie de características relacionadas.

no agregar nuevos controles nulos o indefinidos en todas partes

La captura de variables locales no asignadas mitiga esto y el operador de navegación segura es una característica de ES propuesta, por lo que aterrizará en TS en algún momento.

También creo que la probabilidad de que esto entre en TS es baja ... :(

La única solución que se me ocurre es convertir la seguridad nula en un indicador de compilador opcional. Por ejemplo, introduzca la nueva sintaxis T | null o ?T pero no genere _cualquier_ error a menos que el proyecto lo acepte. Por lo tanto, los proyectos nuevos obtienen los beneficios y también los proyectos antiguos que eligen hacer que su código sea compatible con la nueva función. Las bases de código grandes que son demasiado grandes para adaptarse fácilmente simplemente no obtienen esta función.

Dicho esto, aún así quedan varios problemas para hacer volar esta función ...

@NoelAbrahams lo siento, mi punto no fue que fuera una mala sugerencia, simplemente no la respuesta que se deseaba de la instigación de esta discusión.
Seguramente usaré esa función cuando (/ si) se convierta en nativa, suena realmente bien.

@ jods4 que tiene el mismo problema con el comentario de @RyanCavanaugh que cité anteriormente (error de asignación debido a un problema en otro lugar cuando se cambia una bandera).

Nuevamente, la forma más fácil es implementar una de las otras propuestas (probablemente @spion 's) y no agregar los nuevos tipos a los .d.ts'.

@Griffork
No necesariamente, aunque este es uno de los "varios temas" que quedan. La función debe diseñarse de modo que la activación de la bandera _no_ cambie la semántica del programa. Solo debería generar errores.

Por ejemplo, si optamos por el diseño 'no nulo' !T , no creo que tengamos este problema. Pero esto está lejos de ser un problema resuelto.

También debo señalar que, si bien T | null es un cambio radical, ?T no lo es.

@ jods4 Tenía la impresión de que "cambiar la semántica" incluía "cambiar la capacidad de asignación", de cualquier manera, mis preocupaciones (y posiblemente las de @RyanCavanaugh ) sobre los efectos no locales siguen en pie.

@NoelAbrahams ¿cómo?
T | null requiere un typeguard,? T requiere un typeguard, son lo mismo con diferentes nombres / símbolos.
Sí, puede hacer que ambos se "enciendan" con una bandera.
¿No logro ver la diferencia?

@Griffork , la forma en que lo veo ?T simplemente significa "no acceder sin verificar nulo". Uno es libre de agregar esta anotación al nuevo código para hacer cumplir la verificación. Estoy pensando en esto como una especie de operador en lugar de una anotación de tipo.

La anotación T|null rompe el código existente porque hace una declaración sobre si los tipos son anulables o no de forma predeterminada.

@jbondc
Interesante ... No he analizado demasiado su propuesta todavía, pero creo que lo haré.

Por el momento, la inmutabilidad impuesta por el compilador no es tan interesante en JS como en otros lenguajes porque su modelo de subprocesos prohíbe los datos compartidos. Pero seguramente algo a tener en cuenta.

@NoelAbrahams
Todas las propuestas de ejecución nula son cambios rotundos. Incluso ?T que describiste. Mostré varios ejemplos de por qué algunos comentarios arriba.

Es por eso que creo que la única salida si queremos esto es convertirlo en una opción de compilador, y pensar esto lo suficientemente bien como para que habilitar la opción cambie los errores reportados pero no el comportamiento del programa. No estoy seguro de si es factible o no.

De hecho, estoy un poco bien con esto. Con TS 1.5 podré conseguir lo que quiero:

function isVoid(item:any): item is void { return item == null; }
declare externalUnsafeFunction(...):string|void

function test() {
  var res = externalUnsafeFunction(...);
  var words = res.split(' '); // error
  if (!isVoid(res)) {
    var words = res.split(' '); // ok
  }
}

ahora la verificación isVoid se fuerza en los valores de retorno para externalUnsafeFunction o cualquier otra función o definición de función donde agrego |void al tipo de retorno.

Todavía no podré declarar funciones que no acepten nulo / indefinido, pero es posible ser lo suficientemente diligente como para recordar inicializar las variables locales y los miembros de la clase.

Debido a que @NoelAbrahams ya discutimos que incluso con un tipo nulo, aún se debería permitir que nulo se

También significa que en el futuro nos puede traer en una bandera compilador que nos vamos a anotamos dónde y cuándo una función puede aceptar nula.

Y personalmente, detesto la idea de usar símbolos para representar tipos cuando en su lugar podemos usar palabras.

@spion, ese es un buen punto en realidad. Si eso funciona en TS actualmente, entonces podría decirse que todos los archivos d.ts integrados ya son

En realidad, dado que ya es algo alcanzable, voy a proponer que comencemos a usar eso esta semana a mi jefe.

Le advertiré que de ninguna manera vemos T|void como una sintaxis que tengamos miedo de romper en el futuro. Es casi una tontería.

@RyanCavanaugh Es tan absurdo como vacío === indefinido (un valor asignable).

Suspiro.

Nuestro código es demasiado grande para comenzar dependiendo de T|void si pronto se romperá y no será reemplazado. Y no podré convencer a los otros programadores de que lo usen si alguna semana se puede romper.

Ok, @spion , si alguna vez haces tu parche, avísame y lo ejecutaré contra el código base de mi trabajo. Al menos puedo dar estadísticas sobre cuántos errores causa.

@RyanCavanaugh tonterías, ¿de verdad? ¿En qué manera? ¿Y qué sugeriría para expresar tipos que aceptan valores NULL que deberían estar prohibidos para cualquier tipo de consumo antes de una verificación nula / indefinida?

Realmente tengo muchas bibliotecas que se beneficiarían de eso.

@Griffork no será posible eliminar de forma segura void de la unión antes de 1.5 de todos modos, no sin guardias de tipo definidos por el usuario, por lo que usar esto solo será posible después de 1.5 (si es que es posible)

Tonterías, ¿alguna vez escribirías este código?

var foo: void;
var bar: void = doStuff();

Por supuesto que no. Entonces, ¿cuál es el significado de agregar void a una unión de tipos posibles? Tenga en cuenta esta parte de la especificación del lenguaje que ha existido durante bastante tiempo en la sección que describe The Void Type (3.2.4):

_NOTA: Podríamos considerar no permitir la declaración de variables de tipo Void ya que no tienen ningún propósito útil. Sin embargo, debido a que se permite Void como un argumento de tipo para un tipo o función genérica, no es factible rechazar propiedades o parámetros de Void.

Esta pregunta:

_¿Y qué sugeriría para expresar tipos que aceptan valores NULL que deberían estar prohibidos para cualquier tipo de consumo antes de una verificación nula / indefinida? _

de eso se trata todo el hilo. Ryan simplemente está señalando que string|void es una tontería de acuerdo con la semántica del lenguaje actual y, por lo tanto, no es necesariamente razonable depender de la semántica sin sentido.

El código que escribí arriba es un TS 1.5 perfectamente válido, ¿verdad?

Daré un ejemplo que ciertamente no es una tontería. Tenemos una función de biblioteca de base de datos (nodejs) que llamamos get() similar al Single () de Linq que, lamentablemente, devuelve Promise<null> lugar de arrojar un error (una promesa rechazada) cuando no se encuentra el elemento. Se usa en toda nuestra base de código en miles de lugares y no es probable que se reemplace o desaparezca pronto. Quiero escribir una definición de tipo que me obligue a mí y a los otros desarrolladores a usar un tipo de protección antes de consumir el valor, porque hemos tenido docenas de errores difíciles de rastrear que se derivan de un valor nulo que se mueve lejos en el código base antes de ser consumido incorrectamente.

interface Legacy { 
  get<T>(...):Promise<T|void>
}

function isVoid(val: any): val is void { return val == null; } // also captures undefined

legacy.get(...).then(val => {
  // val.consumeNormally() is a type error here
  if (!isVoid(val)) { 
    val.consumeNormally(); // OK
  }
  else { handle null case }
});

Esto me parece completamente razonable. Agregar void a la unión hace que todas las operaciones en val no sean válidas, como deberían ser. Typeguard reduce el tipo eliminando |void y deja la parte T .

¿Quizás la especificación no tiene en cuenta las implicaciones de los sindicatos con nulo?

¡No rechace las variables vacías! Funcionan como valores de tipo de unidad y se pueden utilizar
en expresiones (a diferencia de void en C # que requiere 2 conjuntos de todo: uno
para acciones y funciones). Por favor, deje el vacío solo, ¿de acuerdo?
El 27 de enero de 2015 a las 8:54 p.m., "Gorgi Kosev" [email protected] escribió:

El código que escribí arriba es un TS 1.5 perfectamente válido, ¿verdad?

Les daré un ejemplo que ciertamente no es una tontería. Tenemos una
función de biblioteca de base de datos similar a Single () de Linq que desafortunadamente
devuelve Promiseen lugar de lanzar un error (una promesa rechazada) cuando
el artículo no se encuentra. Se utiliza en toda nuestra base de código en miles de
lugares y no es probable que sea reemplazado o desaparecido pronto. Quiero escribir un
definición de tipo que me obliga a mí y a los demás desarrolladores a utilizar un tipo de protección
antes de consumir el valor, porque hemos tenido docenas de errores difíciles de rastrear
derivado de un valor nulo que se mueve lejos en la base de código antes de ser
consumido incorrectamente.

interfaz heredada {
obtener(...):Promesa
}
función isVoid (val: any): val is void {return val == null; } // también captura indefinido

legacy.get (...). luego (val => {
// val.consumeNormally () es un error
if (! isVoid (val)) {
val.consumeNormally (); // OK
}
else {manejar caso nulo}
});

Esto me parece completamente razonable. ¿Puedes señalar qué parte es
¿disparates?

Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -71767358
.

Lo que estoy diciendo es que T|void esencialmente no tiene sentido en este momento porque ya tenemos la regla de que la unión de un tipo y su subtipo es equivalente simplemente al supertipo, por ejemplo, Cat|Animal es equivalente a Animal . Tratar void como un sustituto de los valores null / undefined no es coherente porque null y undefined están en el dominio de T _ para todos los T , lo que implica que deben ser subtipos de T

En otras palabras, T|null|undefined , si pudiera escribir eso, ya estaría sujeto a colapsar en T . Tratar void como un encantamiento para null|undefined es incorrecto porque si ese fuera realmente el caso, el compilador ya habría colapsado T|void en T .

Yo personalmente leería esto http://www.typescriptlang.org/Content/TypeScript%20Language%20Specification.pdf

Los únicos valores posibles para el tipo Void son nulos e indefinidos. El tipo Void es un subtipo del tipo Any y un supertipo de los tipos Null e Undefined, pero por lo demás, Void no está relacionado con todos los demás tipos.

Como; en T|void , T es específicamente _no_ un supertipo de void porque el único supertipo (según la especificación) es any .

Editar: rechazarlo es un problema diferente.

Edit2: Vuelva a leer lo que dijo, y tiene sentido literalmente (en la forma en que la documentación se ha redactado como vacío), pero no tiene sentido en lo que se supone que es la interpretación común (o correcta) de la documentación.

@RyanCavanaugh Usaré la función de todos modos hasta que se rompa, ya que mientras tanto es probable que me ayude a encontrar muchos errores.

Y no puedo evitar señalar la ironía de decir que colapsar T|null|undefined en T "tiene sentido" mientras que la existencia de T|void no. (Lo sé, se trata de la especificación, pero aún así ...)

@jbondc

No hay tipos estructurales estáticos en ECMAScript, por lo que ese argumento no es válido. En ECMAScript, todos los valores son de tipo any por lo tanto, cualquier cosa puede asignarse a cualquier cosa o pasarse a cualquier cosa. Ese hecho no debería implicar nada sobre el sistema de tipos estáticos de TypeScript, que está ahí para mejorar el inexistente en JS.

Es un poco triste que tenga que explicar la ironía, pero aquí va:

  1. tiene un valor que no admite métodos ni propiedades, null
  2. crea un sistema de tipos en el que este valor se puede asignar a variables de cualquier tipo, la mayoría de las cuales admiten varios métodos y propiedades (por ejemplo, números de cadenas u objetos complejos var s:string = null )
  3. haces un tipo void que acepta correctamente ese valor y no permite métodos o propiedades, y como consecuencia T|void no permite métodos ni propiedades.

La afirmación es que (3) es una tontería, mientras que (2) no lo es. ¿Ves la parte que es irónica aquí? Si no, intentaré con otro ejemplo:

  1. tiene valores que admiten solo algunos métodos y propiedades {}
  2. crea un sistema de tipos donde este valor se puede asignar a variables de cualquier tipo (por ejemplo, a números de cadenas u objetos complejos var s:string = {} ) que tienen un conjunto mucho mayor de métodos y propiedades
  3. tiene un tipo {} que acepta correctamente los valores {} y no permite métodos o propiedades que no sean unos pocos incorporados, y T|{} naturalmente no permite métodos o propiedades que no sean los incorporados de {}

Ahora bien, ¿cuál es una tontería, (2) o (3)?

¿Cuál es la única diferencia entre los dos ejemplos? Algunos métodos incorporados.

Finalmente, la interfaz era solo una descripción de definición de tipo. No es una interfaz que se esté implementando en cualquier lugar. Es la interfaz proporcionada por la biblioteca, que nunca devuelve nada más que promesas, pero puede devolver una promesa de nulo. Por lo tanto, la seguridad es definitivamente muy real.

@spion eso es exactamente lo que estaba pensando.

Aquí, creo que esto aclarará un poco las cosas:
Según las especificaciones :


¿Qué es vacío?

El tipo Void, al que hace referencia la palabra clave void, representa la ausencia de un valor y se utiliza como el tipo de retorno de funciones sin valor de retorno.

Entonces, void no es nulo | indefinido.

Los únicos valores posibles para el tipo Void son nulos e indefinidos

Por lo tanto, nulo e indefinido se pueden asignar a nulo (ya que se pueden asignar a casi cualquier otra cosa).


Null es un tipo.

3.2.5 El tipo nulo
El tipo nulo corresponde al tipo primitivo de JavaScript de nombre similar y es el tipo de nulo
literal.
El literal nulo hace referencia al único valor del tipo Nulo. No es posible hacer referencia directamente al tipo nulo en sí.


Indefinido es un tipo.

3.2.6 El tipo indefinido
El tipo indefinido corresponde al tipo primitivo de JavaScript de nombre similar y es el tipo del literal indefinido.


El vacío es solo un supertipo de los _tipos_ nulo e indefinido

. El tipo Void es un subtipo del tipo Any y un supertipo de los tipos Null e Undefined, pero por lo demás, Void no está relacionado con todos los demás tipos.


Entonces @jbondc de acuerdo con la Especificación del lenguaje de any ). De hecho, Void está escrito específicamente para tener un comportamiento diferente al de los tipos Null e Undefined:

Vacío:

El tipo Void es un subtipo del tipo Any y un supertipo de los tipos Null e Undefined, pero por lo demás, Void no está relacionado con todos los demás tipos.

Nulo:

El tipo Nulo es un subtipo de todos los tipos, excepto el tipo Indefinido. Esto significa que nulo se considera un valor válido para todos los tipos primitivos, tipos de objeto, tipos de unión y parámetros de tipo, incluidos incluso los tipos primitivos numéricos y booleanos.

Indefinido:

El tipo indefinido es un subtipo de todos los tipos. Esto significa que indefinido se considera un valor válido para todos los tipos primitivos, tipos de objeto, tipos de unión y parámetros de tipo.

¿Más claro ahora?

Lo que estamos pidiendo es un tipo que fuerza una guardia para nulos y un tipo que fuerza una guardia para indefinidos. No estoy pidiendo que los valores nulos o indefinidos no se puedan asignar a otros tipos (eso es demasiado rompedor), solo la capacidad de optar por un margen adicional. Diablos, ni siquiera tienen que llamarse nulos o indefinidos (los tipos).

@jbondc esta es la ironía: no importa lo que diga el compilador de TS, los valores nulos e indefinidos en JS nunca admitirán ningún método o propiedad (aparte de lanzar excepciones). Por lo tanto, hacerlos asignables a cualquier tipo es una tontería, tanto como permitir que se asigne el valor {} a cadenas. El tipo void en TS no contiene valores, _pero_ null o undefined se pueden asignar a todos y, por lo tanto, se pueden asignar a variables de tipo void, por lo que hay otra tontería allí. Y esa es la ironía: que (a través de guardias de tipo y uniones) ambos se combinan actualmente en algo que realmente tiene sentido :)

Algo tan complejo como el compilador de TS se escribió sin guardias, eso es algo en lo que pensar.

Hay una aplicación bastante grande escrita en PHP o JAVA, que no mejora ni empeora el lenguaje.
Las pirámides egipcias se han construido sin ninguna máquina moderna, ¿significa eso que todo lo que hemos inventado en los últimos 4500 años es una mierda?

@jbondc Vea mi comentario anterior con la lista de problemas en el compilador de TypeScript causados ​​por valores nulos e indefinidos (menos el primero en la lista)

@jbondc no se supone que resuelva los problemas del mundo, se supone que es otra herramienta disponible a disposición de los desarrolladores.
Tendría el mismo impacto y utilidad que las sobrecargas de funciones, las uniones y los alias de tipo.
Es otra sintaxis opcional que permite a los desarrolladores escribir valores con mayor precisión.

Uno de los grandes problemas mencionados para hacer que los tipos no admitan nulos es qué hacer con el código TypeScript existente. Hay dos problemas aquí: 1) Hacer que el código existente funcione con un compilador más nuevo que admita tipos que no aceptan valores NULL y 2) Interoperar con código que no ha sido actualizado, evitando un atolladero de estilo Python 2/3

Sin embargo, debería ser posible proporcionar una herramienta que realice una reescritura automatizada del código TS existente para que se compile y, para este caso limitado, algo que funcione mucho mejor que, digamos, 2to3 para Python.

Quizás una migración gradual podría verse así:

  1. Introduzca soporte para la sintaxis '? T' para indicar un tipo posiblemente nulo. Inicialmente, esto no tiene ningún efecto en la verificación de tipos, solo está ahí para la documentación.
  2. Proporcione una herramienta que reescribe automáticamente el código existente para usar '? T'. La implementación más tonta convertiría todos los usos de 'T' en '? T'. Una implementación más inteligente verificaría si el código que usó el tipo asumió implícitamente que no era nulo y usaría 'T' en ese caso. Para el código que intenta asignar un valor de tipo '? T' a un parámetro o propiedad que espera 'T', esto podría envolver el valor en un marcador de posición que afirma (en tiempo de compilación) que el valor no es nulo, por ejemplo, let foo: T = expect(possiblyNullFoo) o let foo:T = possiblyNullFoo!
  3. Aplicar la herramienta a todas las definiciones de tipo proporcionadas y a las del repositorio DefinitelyTyped
  4. Introduzca una advertencia del compilador cuando intente asignar '? T' a una ranura que espera una 'T'.
  5. Deje que las advertencias se horneen en la naturaleza y verifique que la portabilidad sea manejable y vea cómo se recibe el cambio
  6. Haga que la asignación de '? T' a una ranura que espera una 'T' sea un error, en una nueva versión importante del compilador.

Quizás también valga la pena introducir algo que desactive las advertencias para módulos específicos para facilitar el uso del código al que aún no se ha aplicado la herramienta.

En el comentario anterior, asumo que no habría un modo para elegir la nulabilidad de la 'T' simple para evitar dividir el idioma y que los pasos 1-3 serían útiles por sí mismos incluso si el paso 6 nunca resulta práctico.

Mis pensamientos aleatorios:

  1. La parte expect() puede ser complicada, debería estar bien pensada. No solo hay asignaciones, sino interfaces, genéricos o tipos de parámetros ...
  2. Creo que TS no tiene ninguna advertencia, solo errores. Si estoy en lo cierto, no estoy seguro de que presenten advertencias solo para esa función.
  3. Incluso si muchas personas actualizan su código, las empresas pueden tardar mucho en hacerlo, o es posible que nunca quieran hacerlo en el caso de grandes bases de código existentes. Esto hace que ese gran lanzamiento sea un gran cambio radical, algo que el equipo de TS no quiere.

Dicho esto, sigo pensando que una marca de "suscripción voluntaria" para informar errores nulos es una solución aceptable. El problema es que el mismo código / definiciones deberían tener el mismo comportamiento (menos los errores) cuando la bandera está apagada, algo en lo que no he tenido tiempo de pensar todavía.

@ jods4 - Para la parte expect() , creo que dondequiera que tenga ?T el compilador ve T | undefined para que pueda reutilizar las reglas para manejar tipos de unión hasta posible.

Estoy de acuerdo en que no estoy seguro de cuán realista es un 'día de la bandera' para TypeScript. Aparte de una bandera de 'suscripción voluntaria', también podría ser una bandera de suscripción voluntaria que eventualmente se convierta en una bandera de exclusión voluntaria. Lo importante es tener una dirección clara sobre cuál es el estilo idiomático.

Algo más relevante para esta discusión: https://github.com/rwaldron/tc39-notes/blob/master/es6/2015-01/JSExperimentalDirections.pdf analiza las investigaciones del equipo de Chrome sobre el uso de información de tipos (especificada a través del estilo TypeScript anotaciones) en el compilador para optimizar JS. Hay una pregunta abierta sobre la nulabilidad: les gustaría tipos que no admitan nulos, pero tampoco están seguros de la viabilidad.

Solo volveré a sonar aquí. Me sorprende que no hayamos resuelto realmente este.

Digo que hay un valor agregado aquí. Uno que solo es aplicable en tiempo de compilación.
En lugar de tener que escribir más código para verificar un valor nulo, poder declarar un valor como no anulable puede ahorrar tiempo y codificación. Si describo un tipo como 'no anulable', entonces debe inicializarse y posiblemente afirmarse como no nulo antes de pasar a otra función que espera un valor no nulo.

Como dije anteriormente, tal vez esto podría resolverse simplemente mediante una implementación de contratos de código.

¿Por qué no puede simplemente dejar de usar el literal nulo y proteger todos los lugares donde
los nulos pueden filtrarse en su código? De esta manera estarás a salvo de null
excepción de referencia sin tener que hacer comprobaciones de nulo en todas partes.
El 20 de febrero de 2015 a las 13:41, "electricessence" [email protected] escribió:

Solo volveré a sonar aquí. Me sorprende que no hayamos resuelto realmente
Éste.

Digo que hay un valor agregado aquí. Uno que solo es aplicable en la compilación
hora.
En lugar de tener que escribir más código para comprobar un valor nulo, poder
declarar un valor como no anulable puede ahorrar tiempo y codificación. Si describo un
escriba como 'no anulable', entonces debe inicializarse y posiblemente afirmarse
como no nulo antes de pasar a otra función que espera
no nulo.

Como dije anteriormente, tal vez esto simplemente podría resolverse mediante un contrato de código
implementación.

Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -75295204
.

Las verificaciones nulas son significativamente más rápidas que las verificaciones indefinidas.
El 21/02/2015 11:54 a. M., "Aleksey Bykov" [email protected] escribió:

¿Por qué no puede simplemente dejar de usar el literal nulo y proteger todos los lugares donde
los nulos pueden filtrarse en su código? De esta manera estarás a salvo de null
excepción de referencia sin tener que hacer comprobaciones de nulo en todas partes.
El 20 de febrero de 2015 a las 13:41, "electricessence" [email protected]
escribió:

Solo volveré a sonar aquí. Me sorprende que no hayamos resuelto realmente
Éste.

Digo que hay un valor agregado aquí. Uno que solo es aplicable en la compilación
hora.
En lugar de tener que escribir más código para comprobar un valor nulo, poder
declarar un valor como no anulable puede ahorrar tiempo y codificación. Si describo un
escriba como 'no anulable', entonces debe inicializarse y posiblemente
afirmó
como no nulo antes de pasar a otra función que espera
no nulo.

Como dije anteriormente, tal vez esto simplemente podría resolverse mediante un contrato de código
implementación.

Responda a este correo electrónico directamente o véalo en GitHub
<
https://github.com/Microsoft/TypeScript/issues/185#issuecomment-75295204>
.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -75346198
.

Mi punto de que esos controles son innecesarios (si la palabra clave nula está prohibida
y todos los demás lugares por donde puede entrar están vigilados).

¿Por qué? 1. El código sin comprobaciones se ejecuta significativamente más rápido que el código.
con comprobaciones de nulos. 2. Contiene menos "if's" y, por lo tanto, es más legible.
y mantenible.
El 20 de febrero de 2015 a las 7:57 p.m., "Griffork" [email protected] escribió:

Las verificaciones nulas son significativamente más rápidas que las verificaciones indefinidas.
El 21/02/2015 11:54 a. M., "Aleksey Bykov" [email protected] escribió:

¿Por qué no puede simplemente dejar de usar el literal nulo y proteger todos los lugares donde
los nulos pueden filtrarse en su código? De esta manera estarás a salvo de null
excepción de referencia sin tener que hacer comprobaciones de nulo en todas partes.
El 20 de febrero de 2015 a las 13:41, "electricessence" [email protected]
escribió:

Solo volveré a sonar aquí. Me sorprende que no hayamos resuelto realmente
Éste.

Digo que hay un valor agregado aquí. Uno que solo es aplicable en la compilación
hora.
En lugar de tener que escribir más código para comprobar un valor nulo, poder
a
declarar un valor como no anulable puede ahorrar tiempo y codificación. Si yo
describir un
escriba como 'no anulable', entonces debe inicializarse y posiblemente
afirmó
como no nulo antes de pasar a otra función que espera
no nulo.

Como dije anteriormente, tal vez esto simplemente podría resolverse mediante un contrato de código
implementación.

Responda a este correo electrónico directamente o véalo en GitHub
<
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -75295204

.

Responda a este correo electrónico directamente o véalo en GitHub
<
https://github.com/Microsoft/TypeScript/issues/185#issuecomment-75346198>
.

Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -75346390
.

Estás perdiendo el punto, usamos nulos para evitar verificaciones indefinidas.
Y dado que nuestra aplicación no tiene un estado consistente, lo hacemos y
a menudo tienen que tener cosas nulas / indefinidas en lugar de un objeto para
representar eso.
El 21/02/2015 12:20 p. M., "Aleksey Bykov" [email protected] escribió:

Mi punto de que esos controles son innecesarios (si la palabra clave nula está prohibida
y todos los demás lugares por donde puede entrar están vigilados).

¿Por qué? 1. El código sin comprobaciones se ejecuta significativamente más rápido que el código.
con comprobaciones de nulos. 2. Contiene menos "if's" y, por lo tanto, es más legible.
y mantenible.
El 20 de febrero de 2015 a las 7:57 p.m., "Griffork" [email protected] escribió:

Las verificaciones nulas son significativamente más rápidas que las verificaciones indefinidas.
El 21/02/2015 11:54 a. M., "Aleksey Bykov" [email protected]
escribió:

¿Por qué no puede simplemente dejar de usar el literal nulo y proteger todos los lugares?
donde
los nulos pueden filtrarse en su código? De esta manera estarás a salvo de null
excepción de referencia sin tener que hacer comprobaciones de nulo en todas partes.
El 20 de febrero de 2015 a las 13:41, "electricessence" [email protected]
escribió:

Solo volveré a sonar aquí. Me sorprende que no lo hayamos hecho realmente
resuelto
Éste.

Digo que hay un valor agregado aquí. Uno que solo es aplicable en
compilar
hora.
En lugar de tener que escribir más código para comprobar un valor nulo,
poder
a
declarar un valor como no anulable puede ahorrar tiempo y codificación. Si yo
describir un
escriba como 'no anulable', entonces debe inicializarse y posiblemente
afirmó
como no nulo antes de pasar a otra función que espera
no nulo.

Como dije anteriormente, tal vez esto simplemente podría resolverse mediante un código
contratos
implementación.

Responda a este correo electrónico directamente o véalo en GitHub
<

https://github.com/Microsoft/TypeScript/issues/185#issuecomment -75295204

.

Responda a este correo electrónico directamente o véalo en GitHub
<
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -75346198

.

Responda a este correo electrónico directamente o véalo en GitHub
<
https://github.com/Microsoft/TypeScript/issues/185#issuecomment-75346390>
.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -75347832
.

También te estás perdiendo mis puntos. Para mantener la coherencia de su estado y poder representar los valores faltantes, no es necesario utilizar valores nulos o indefinidos. Hay otras formas, especialmente ahora con el apoyo de tipos de sindicatos, considere:

class Nothing { public 'i am nothing': Nothing; }
class Something { 
    constructor(
    public name: string,
    public value: number
   ) { }
}
var nothing = new Nothing();
var whoami = Math.random() > 0.5 ? new Something('meh', 66) : nothing;

if (whoami instanceof Nothing) {
    console.log('i am a null killer');
} else if (whoami instanceof Something) {
    console.log(whoami.name + ': ' + whoami.value);
}

Así que codificamos un valor faltante sin usar un valor nulo o indefinido. También tenga en cuenta que lo estamos haciendo de manera 100% explícita . Gracias a los guardias de tipos, no hay forma de que uno pueda perder un cheque antes de leer un valor.

¿Cuan genial es eso?

¿Cree que una instancia de verificación es más rápida que una verificación nula, para un
aplicación que tiene que hacer cientos de estos por segundo?
Déjame ponerlo de esta manera: hemos tenido que crear alias para funciones porque
es más rápido no usar '.' accesores.
El 21/02/2015 12:58 p. M., "Aleksey Bykov" [email protected] escribió:

También te estás perdiendo mis puntos. Para mantener su estado consistente y
ser capaz de representar valores perdidos, no es necesario utilizar nulos o
indefinido. Hay otras formas, especialmente ahora con el apoyo de tipos de sindicatos.
considerar:

class Nothing {public 'no soy nada': Nada; }
class Something {
constructor(
nombre público: cadena,
valor publico: numero
) {}
}
var nada = nuevo Nada ();
var whoami = Math.random ()> 0.5? Algo nuevo ('meh', 66): nada;

if (whoami instancia de Nothing) {
// whoami no es nada
console.log ('soy un asesino nulo');
} else if (whoami instancia de Algo) {
// whoami es una instancia de Algo
console.log (whoami.name + ':' + whoami.value);
}

Así que se codificó un valor faltante sin usar un valor nulo o indefinido.
También tenga en cuenta que lo estamos haciendo de manera _100% explícita_. Gracias por escribir
guardias, no hay forma de que uno pueda perder un cheque antes de leer un valor.

¿Cuan genial es eso?

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -75349967
.

@ aleksey-bykov Tenga en cuenta que ya abordé ese punto: la necesidad de modelar las definiciones de tipo para las bibliotecas existentes que ya usan valores nulos no está resuelta. En ese caso, no puede simplemente "dejar de usar" valores nulos.

Además, eso ni siquiera toca los problemas con las variables no inicializadas que serán undefined . En Haskell, el problema ni siquiera existe porque no puede dejar un valor "sin inicializar".

@Griffork , no creo, hago pruebas, las pruebas dicen que depende del navegador.
http://jsperf.com/nullable-vs-null-vs-undefined-vs-instanceof
Sin embargo, creo que es necesario encontrar un equilibrio entre la seguridad y el rendimiento. Luego, debe medir cuidadosamente su rendimiento antes de intentar optimizarlo. Lo más probable es que esté arreglando algo que no está roto y sus controles nulos que tanto le preocupan podrían constituir menos del 2% del rendimiento general; de ser así, si lo duplica más rápido, solo obtendrá un 1%.

@spion , considerando todas las cosas, es una forma mucho mejor que seguir usando nulos en su código, dada la situación actual y los problemas que los no nulables traen consigo

Nuestra base de código tiene más de 800 archivos TypeScript y más de 120000 líneas de código
y nunca necesitamos nulos o indefinidos cuando se trataba de modelar negocios
entidades de dominio. Y aunque tuvimos que usar nulos para manipulaciones DOM,
Todos estos lugares están cuidadosamente aislados, de modo que los nulos no tengan forma de filtrarse.
en. No compro su argumento de que es posible que se necesiten valores nulos, hay
lenguajes listos para producción sin nulos en absoluto (Haskell) o con nulos
prohibido (F #).
El 22 de febrero de 2015 a las 9:31 a. M., "Jon" [email protected] escribió:

@ aleksey-bykov https://github.com/aleksey-bykov De una práctica
perspectiva, no puede ignorar null. Aquí hay algunos antecedentes sobre por qué
necesita un tipo anulable de todos modos:
https://www.google.com/patents/US7627594?ei=P4XoVPCaEIzjsATm4YGoBQ&ved=0CFsQ6AEwCTge

Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -75437927
.

Sí, y lo más importante que puede entender aquí es que NO estamos modelando entidades de dominio empresarial, somos MUY sensibles al tiempo y nuestra base de código no es mucho más pequeña que la suya.

Para su propósito, no necesita nulos, lo cual está bien, puede continuar usando Typecript sin ellos.
Para nuestro propósito, necesitamos nulos, y no podemos permitirnos no hacerlo.

Oh, acabo de ver tu otro comentario, lo siento.
Si lee un poco antes en este hilo, notará que he dicho (esencialmente) que siempre hemos usado valores nulos, y rara vez hemos tenido problemas con que se filtren en otros bits de código.

Si también leyó más arriba, la propuesta que no acepta valores NULL también cubre undefined.

Las variables nulas e indefinidas por su velocidad se ven similares, pero tan pronto como están presentes en un objeto (particularmente uno con una cadena de prototipo) la historia es muy diferente, por lo que usamos nulos en su lugar.

Una vez más, probablemente no valga la pena preocuparse, a menos que (como nosotros), esté haciendo 10,000 comprobaciones en <10 ms.

(Y sí, en realidad cambiamos debido al seguimiento del rendimiento de nuestra aplicación y al encontrar un cuello de botella)

Me gustaría estimular una discusión sobre esto. Ayer mismo, en Build video , vi analizadores implementados en C #. Abrí un problema # 3003 para implementar similar a TypeScript.

Básicamente, el analizador es una biblioteca que puede realizar comprobaciones de sintaxis adicionales, etiquetar líneas como errores / advertencias y son interpretadas por el servicio de idiomas. Esto permitiría efectivamente hacer cumplir comprobaciones nulas con una biblioteca externa.

De hecho, veo que los analizadores en TypeScript también serían geniales para otras cosas. Hay bibliotecas realmente extrañas, más que en C #.

Para cualquiera que esté siguiendo este problema: mantengo una biblioteca llamada monapt que le permite declarar la nulabilidad de una variable. Incluye definiciones de TypeScript, por lo que todo se verifica en tiempo de compilación.

¡Comentarios bienvenidos!

Acabo de leer todo el hilo.

Supongo que la propuesta debería usar los términos non-voidable y voidable , porque void en la especificación TS es un tipo para el null y undefined . (Supongo que la propuesta también cubre un valor indefinido: sonrisa :)

También estoy de acuerdo en que el acceso no definido o una excepción nula es la raíz de todos los males en la programación, agregar esta función ahorrará innumerables horas para las personas.

¡Por favor agregue esto! Y sería mejor si esto se convirtiera en una característica, podría pasar una bandera para que sea la predeterminada. Ahorraría un montón de depuración para mucha gente.

Si bien puedo estar de acuerdo en que los ADT se pueden usar en lugar de los tipos que aceptan valores NULL; ¿Cómo podemos estar seguros de que el ADT que pasamos no es nulo en sí mismo? Cualquier tipo de TS puede ser nulo, incluidos los ADT, ¿verdad? Lo veo como una falla importante al pensar en los ADT como una solución al problema de los tipos que no aceptan nulos (no anulables). Los ADT solo funcionan tan bien como lo hacen en lenguajes que permiten (o prohíben) tipos que no aceptan valores NULL.

Otro tema clave son las definiciones de bibliotecas. ¡Podría tener una biblioteca que espera una cadena como argumento, pero el compilador de TS puede escribir check pasando null a la función! Eso no está bien ...

Lo veo como una falla importante al pensar en los TDA como una solución al problema ...

  1. Obtenga ADT disponibles a través de un patrón de diseño (debido a la falta de
    soporte de TypeScript)
  2. prohibir la palabra clave null
  3. proteja todos los lugares donde los nulos pueden filtrarse a su código desde el exterior
  4. disfruta el problema null desaparecido para siempre

En nuestro proyecto logramos hacer los pasos 1, 2 y 3. Adivina lo que estamos haciendo
ahora.

@ aleksey-bykov

¿Cómo le va (2) con undefined ? Puede surgir en una variedad de situaciones que no involucran la palabra clave undefined : miembros de clase no inicializados, campos opcionales, declaraciones de retorno faltantes en ramas condicionales ...

Compare el texto mecanografiado con el tipo de flujo . Al menos el flujo se ocupa de la mayoría de los casos.

Además, si estás dispuesto a decir "Olvídate de los modismos comunes en el idioma. No me importa, me aislaré del resto del mundo", entonces probablemente sea mejor que uses purescript .

Puede surgir en una variedad de situaciones que no involucran la palabra clave indefinida ...

  • miembros de clase no inicializados - clases prohibidas
  • campos opcionales - campos / parámetros opcionales prohibidos
  • declaraciones de devolución faltantes: una regla tslint personalizada que se asegura de que todas las rutas de ejecución regresen

mejor usando purescript

  • lo deseo, pero el código que genera se refiere al rendimiento lo menos

@ aleksey-bykov es gracioso que mencione el rendimiento, porque la sustitución de tipos que aceptan valores NULL por ADT impone una sobrecarga significativa de CPU y memoria en comparación con la simple aplicación de comprobaciones nulas (como tipo de flujo). Lo mismo ocurre con no usar clases (puede ser prohibitivamente costoso si crea una instancia de muchos objetos con muchos métodos en ellos)

Mencionas renunciar a los campos opcionales, pero son utilizados por muchas bibliotecas JS. ¿Qué hiciste para lidiar con esas bibliotecas?

También mencionas no usar clases. Supongo que también evita las bibliotecas que dependen (o dependerán) de clases (como, por ejemplo, React).

En cualquier caso, no veo ninguna razón para renunciar a tantas funciones cuando es muy probable que se pueda implementar una solución perfectamente razonable (que realmente se ajuste al lenguaje subyacente sin escribir).

es gracioso que piense que el único propósito de ADT es representar un valor faltante que de otra manera habría sido codificado por null

Prefiero decir que haber desaparecido el problema "nulo" (y las clases también) es un subproducto de todas las cosas buenas que nos trae ADT

Para abordar su curiosidad, para piezas críticas para el rendimiento, como bucles ajustados y grandes estructuras de datos, utilizamos la llamada abstracción Nullable<a> al engañar a TypeScript para que piense que se trata de un valor envuelto, pero de hecho (en tiempo de ejecución) ese valor es solo una primitiva que puede tomar nulos, hay un conjunto especial de operaciones en Nullable's que evita que el nulo se filtre

avísame si quieres conocer los detalles

interface Nullable<a> {
    'a nullable': Nullable<a>;
    'uses a': a;
}
/*  the following `toNullable` function is just for illustration, we don't use it in our code,
    because there are no values capable of holding naked null roaming around,
    instead we just alter the definition of all unsafe external interfaces:
    // before
    interface Array<T> {
       find(callback: (value: T, index: number, array: T[]) => boolean, thisArg?: any): T;
    }
    // after
    interface Array<a> {
       find(callback: (value: a, index: number, array: a[]) => boolean, thisArg: any): Nullable<a>;
    }
*/
function toNullable<a>(value: a) : Nullable<a> {
    return <any>value;
}
function toValueOrDefault<a>(value: Nullable<a>, defaultValue: a)  : a {
    return value != null ? <any>value : defaultValue;
}

¿Por qué utilizar una estructura de tipos recursivos? 'a nullable': a debería ser suficiente, ¿no?

Supongo que también tiene callFunctionWithNullableResult<T,U>(f: (T) => U, arg:T):Nullable<U> (incluidas todas las demás sobrecargas para funciones con arity diferente) para tratar con todas las funciones externas que devuelven nulos.

Y si la palabra clave nula está prohibida por un linter, ¿cómo se almacena un " Nothing " en un nullable? ¿tienes un caso especial toNullable(null) ?

Sé lo que son los tipos de datos algebraicos. ¿Puede darme una instancia (además de las comprobaciones exhaustivas y la torpeza) donde los ADT (o más bien, los tipos de suma) + la coincidencia de patrones pueden hacer algo que las uniones mecanografiadas + los protectores de tipos definidos por el usuario no pueden hacer?

¿Por qué utilizar una estructura de tipos recursivos? 'a nullable': a debería ser suficiente, ¿no?

bueno, en este ejemplo es suficiente, pero hacerlo así sobrecarga el propósito de dicho campo, que sirve a 2 casos distintos:

Personalmente, odio la sobrecarga, lo que significa que es por eso que voy con 2 pseudocampos separados donde cada uno resuelve un problema separado

Supongo que también tienes callFunctionWithNullableResult(f: T -> U, arg: T) => Nullable

no, como dije, modificamos los archivos de definición (* .d.ts) reemplazando cualquier cosa que tengamos evidencia que puede ser nula en el código externo con un Nullable "envoltorio", vea el ejemplo en el comentario en mi mensaje anterior

¿Cómo se almacena "Nothing" en un anulable

no almacenamos nulos en nullables, los nullables provienen del servidor (del cual tenemos un control limitado) o de las API externas, tan pronto como está en nuestro código, se envuelve en Nullable, pero nuestro código no es capaz de producir un anulable a voluntad, solo podemos transformar un anulable dado en otro anulable, pero no para crear uno de la nada

las uniones mecanografiadas + los protectores de tipos definidos por el usuario no pueden hacer

¡decir ah! No soy fanático de los sindicatos y puedo hablar durante horas de lo que los convierte en una mala idea.

volviendo a su pregunta, deme un equivalente reutilizable de Either a b usando uniones y protectores de tipo
(una pista: https://github.com/Microsoft/TypeScript/issues/2264)

Los tipos de su ejemplo no son más nominales solo porque son recursivos , siguen siendo "pseudo-nominales".

Sin embargo, para responder a su pregunta: no implementa un Either genérico, precisamente porque los tipos no son nominales. Simplemente usa

type MyType = A | B

y luego use guardias de tipo para A y B ... Como beneficio adicional, esto también funciona con todas las bibliotecas JS existentes: por ejemplo, la forma idiomática de verificar Thenable es si hay es un método then adjunto al objeto. ¿Funcionará un protector de tipos para eso? Seguramente.

¡Decir ah! Esa es una buena captura tuya. Bueno, algo debe haber cambiado en las versiones recientes del compilador. Aquí está el nominal a prueba de balas:

enum GottaBeNonimalBrand {}
interface GottaBeNonimal {
     branded: GottaBeNonimalBrand;
}
function toGottaBeNominal() : GottaBeNominal {
   return <any> {};
}

¡Espera un segundo! ¡Hiciste trampa ! El buen estilo antiguo todavía funciona a las mil maravillas.

Sin embargo, para responder a su pregunta: no implementa un Either genérico, precisamente porque los tipos no son nominales. Simplemente usa ...

eso es bueno lo que dices, ¿cómo es Either ? que el Either auténtico es GENÉRICO y hay unas 30 funciones de conveniencia estándar a su alrededor, ahora está diciendo que necesito resolverlo (nominal) y tener hasta 30 x número_de_combinaciones_de_cualquier_2_tipos_en_mi_aplicación de esas funciones para todos los pares posibles que podría ¿encuentro?

Es de esperar que este hilo todavía sea monitoreado por personas del equipo de TS. ¡Quiero intentar reiniciarlo!
Me parece que los tipos no nulos se atascaron cuando se hizo obvio que era imposible introducir esto de una manera ininterrumpida. El equipo de C # está pensando en tipos de referencia que no aceptan valores NULL y, por supuesto, se enfrenta a los mismos problemas, en particular la compatibilidad. Me gustan las ideas que están explorando en este momento y me pregunto si serían aplicables a TS.

Lluvia de ideas de C # vnext (brevemente)

Básicamente, en C # tiene tipos de valores no nulos, como int . Luego, en C # 2.0 obtuvimos los tipos de valor que aceptan valores NULL, como int? . Las referencias siempre han sido anulables, pero están pensando en cambiar esto y aplicar la misma sintaxis: string nunca es nulo, string? puede serlo.

Por supuesto, el gran problema es que esto cambia el significado de string y rompe el código. Donde estaba bien asignar / devolver null a string , ahora es un error. El equipo de C # está pensando en "deshabilitar" esos errores a menos que usted opte por participar. Saltar en string? siempre es un error, porque la sintaxis es nueva y no estaba permitida antes (por lo tanto, no es un cambio rotundo).
El otro problema es con otras bibliotecas, etc. Para abordar eso, quieren hacer el indicador de suscripción por archivo o ensamblado. Por lo tanto, el código externo que opta por el sistema de tipos más estricto obtiene los beneficios, y el código antiguo y las bibliotecas siguen funcionando.

¿Cómo podría eso transponer a TS?

  1. Introducimos una marca de inclusión voluntaria de tipos no nulos. Podría ser global (pasado al compilador) o por archivo. Siempre debe ser por archivo por .d.ts .
  2. Los tipos básicos no aceptan valores NULL, por ejemplo, number . Hay un nuevo tipo null .
  3. number? es simplemente un atajo para number | null .
  4. La función introduce nuevos errores para los tipos que aceptan valores NULL, como (x: string?) => x.length sin un tipo de protección.
  5. La función introduce nuevos errores para tipos que no aceptan valores NULL, como asignar let x: string = null;
  6. Cuando se manipula un tipo no nulo que se declara fuera del alcance de inclusión voluntaria, se ignoran los errores informados en 5.. _Esto es ligeramente diferente a decir que string todavía se considera un string? ._

¿De qué sirve esto?

Cuando escribo un código nuevo, puedo suscribirme y obtener comprobaciones nulas estáticas completas en mi código y en las definiciones de biblioteca actualizadas. Puedo seguir usando definiciones de bibliotecas heredadas sin errores.

Cuando utilizo código antiguo, puedo optar por nuevos archivos. Puedo declarar string? todos modos y obtener errores al acceder a los miembros sin una verificación nula adecuada. También obtengo beneficios y controles estrictos para las bibliotecas cuyas definiciones se han actualizado. Una vez que .d.ts habilita, pasar null a una función definida como length(x: string): number convierte en un error.
_ (Nota: Pasar string? es un error, aunque pasar string del código de exclusión voluntaria no lo es.) _

Mientras no opte por participar, no habrá ningún cambio importante.

¿Qué piensas?

Por supuesto, hay mucho que considerar con respecto a cómo funcionaría la función en la práctica. Pero, ¿es un plan de inclusión voluntaria algo que el equipo de TS podría incluso considerar?

es asombroso cómo la gente quiere un caballo más rápido en lugar de un automóvil ...

No hay necesidad de ser condescendiente. He leído sus comentarios sobre ADT y no creo que sean lo que necesito. Podemos discutir eso si lo desea, pero en un nuevo número sobre "Soporte de ADT en TS". Puede escribir allí por qué son geniales, por qué los necesitamos y cómo alivian la necesidad de tipos que no aceptan valores NULL. Este problema se trata de tipos que no aceptan valores NULL y realmente está secuestrando el tema.

de lo que estás hablando se llama propagación constante y, si se implementa, no debería estar limitado solo por 2 constantes aleatorias que resultaron ser null y undefined . Lo mismo se puede hacer con cadenas, números y cualquier otra cosa, y de esta manera tiene sentido, de lo contrario, sería solo para cultivar algunos viejos hábitos que algunas personas no están dispuestas a hacer.

@ aleksey-bykov y, por supuesto, para obtener mejores restricciones de tipo, implementa tipos de kinded superiores, y para obtener instancias derivadas, implementa clases de tipos, etc. ¿Cómo encaja eso con el objetivo de diseño "alinearse con ES6 +" de TypeScript?

Y todavía es inútil para resolver el problema original de este problema. Es fácil de implementar Maybe (y Either) en TS usando clases genéricas, incluso hoy (sin tipos de unión). Será tan útil como agregar Optional a Java, que es: apenas. Porque alguien simplemente asignará null algún lugar o necesitará propiedades opcionales, y el lenguaje en sí no se quejará, pero las cosas explotarán en tiempo de ejecución.

No me queda claro por qué insistes en abusar del lenguaje para que se doble de una forma que nunca fue diseñada. Si desea un lenguaje funcional con tipos de datos algebraicos y sin valores nulos, simplemente use PureScript. Es un lenguaje excelente y bien diseñado con características que encajan coherentemente, probado en batalla a lo largo de los años en Haskell sin las verrugas acumuladas de Haskell (jerarquía de clases de tipos basada en principios, registros reales con polimorfismo de filas ...). Me parece que es mucho más fácil comenzar con un lenguaje que se adhiere a sus principios y necesita luego ajustar para el desempeño, en lugar de comenzar con algo que no lo hace (y que nunca tuvo la intención de hacerlo) y luego retorcer, girar y restringir para lograrlo. trabaja a tu manera.

¿Cuál es su problema de rendimiento con PureScript? ¿Son los tapones producidos por un curry omnipresente?

@ jods4 ¿eso significa que todos los archivos .d.ts existentes actualmente deberán actualizarse con un indicador de fuera de trabajo, o que la configuración de todo el proyecto no afecta .d.ts (y un .d.ts sin bandera siempre se asume que es la forma "antigua".
¿Qué tan comprensible es esto (va a causar problemas a las personas que trabajan en varios proyectos o confusión al leer archivos .d.ts)?
¿Cómo se verá la interfaz entre el código nuevo y el antiguo (biblioteca)? ¿Estará lleno de un montón de (en algunos casos) controles y lanzamientos innecesarios (probablemente no sea algo malo, pero podría alejar a los principiantes del idioma)?
En su mayoría (ahora) me gusta la idea de tipos que no aceptan valores NULL, y la sugerencia de ! es la única forma en que veo que funcionan los tipos que no aceptan valores NULL (ignorando adts).

Quizás se haya perdido en este hilo, pero ¿qué problema resuelve esto? Eso es algo que me ha aludido. Los argumentos que he visto recientemente en este hilo son "otros lenguajes lo tienen". También estoy teniendo dificultades para ver qué resuelve esto que es útil y que no requiere algún tipo de maquinaciones en TypeScript. Si bien puedo entender que los valores null y undefined pueden ser indeseables, tiendo a ver esto como una lógica de orden superior que quiero poner en mi código en lugar de esperar que el lenguaje subyacente lo maneje .

El otro problema menor es que todos tratan undefined como una constante o una palabra clave. Técnicamente tampoco lo es. En el contexto global, hay una variable / propiedad undefined que es del tipo primitivo undefined . null es un literal (que también es del tipo primitivo null , aunque ha sido un error de larga data que se implementó para devolver typeof "object" ). En ECMAScript, estas son dos cosas que son bastante diferentes.

@kitsonk
El problema es que todos los tipos pueden ser nulos y todos los tipos pueden ser indefinidos, pero no todas las operaciones que funcionan en un tipo específico no funcionan con valores nulos o indefinidos (por ejemplo, dividir), lo que provoca errores.
Lo que quieren es la capacidad de especificar un tipo como "es un número y nunca es nulo o indefinido" para que puedan asegurarse de que cuando usan una función que tiene la posibilidad de introducir nulos o indefinidos, los programadores sepan protegerse contra valores inválidos.
Hay dos argumentos principales para poder especificar un valor como "es de este tipo pero no puede ser nulo o indefinido" son los siguientes:
A) es obvio para todos los programadores que usan el código que este valor nunca debe ser nulo o indefinido.
B) el compilador ayuda a detectar los guardias de tipo que faltan.

hay una variable / propiedad indefinida que es del tipo primitivo indefinido. null es un literal (que también resulta ser del tipo primitivo null

@kitsonk este es un gran argumento para este problema; null no es number , no es más Person que es Car . Es de su propio tipo y este problema se trata de permitir que el mecanografiado haga esta distinción.

@kitsonk Creo que uno de los errores más comunes son los errores indefinidos de acceso / referencia nula. Tener la capacidad de especificar una propiedad o variable para que no sea nula / indefinida hará que el código del software sea más sólido. Y también permita que herramientas como LS detecten errores como este https://github.com/Microsoft/TypeScript/issues/3692.

Gracias por la aclaración. Tal vez también me esté perdiendo algo aquí, después de haber revisado esto ... Dado que ya existe un argumento de "opcionalidad" en TypeScript, ¿por qué lo siguiente no funcionaría como una propuesta?

interface Mixed {
    optional?: string;
    notOptional: string;
    nonNullable!: string;
}

function mixed(notOptional: string, notNullable!: string, optional?: string): void {}

Si bien hay una mayor funcionalidad en algunas de las soluciones propuestas, sospecho que la protección de tipos contra ellas se convierte en un desafío, mientras que esto (aunque no conozco bien el funcionamiento interno de TypeScript) parece una extensión de lo que ya se está verificando.

Dado que ya existe un argumento de "opcionalidad" en TypeScript, ¿por qué lo siguiente no funcionaría como propuesta?

Solo para aclarar, los opcionales en este momento en TS solo son opcionales durante la inicialización. Null y undefined todavía se pueden asignar a todos los tipos.

Estos son algunos casos poco intuitivos de opcionales.

interface A {
   a?: string;
}
let a: A = {} // ok as expected

interface B {
   b: string;
}
let b1: B = { b: undefined } //ok, but unintuitive
let b2: B = { b: null } // ok, but unintuitive
let b3: B = {} // error as expected

O simplemente mantenerlo simple optional en TS significa no inicializable. Pero esta propuesta dice que undefined y null no se pueden asignar a un tipo no anulable (no nulo, no indefinido).

El ? se coloca junto al nombre de propiedad / argumento porque se refiere a la _existencia_ de ese nombre o no. @tinganho lo explicó bien. Ampliando su ejemplo:

function foo(arg: string, another?: string) {
  return arguments.length;
}

var a: A = {};
a.hasOwnProperty('a') // false
foo('yo') // 1, because the second argument is _non-existent_.
foo(null) // this is also ok, but unintuitive

El ! no nulo no está relacionado con la _existencia_ de un nombre. Está relacionado con el tipo, por lo que debe colocarse por tipo. El tipo no admite valores NULL.

interface C {
  c: !string;
}

let c1: C = { }; // error, as expected
let c2: C = { c: null }; // error, finally!
let c3: C = { c: 'str' }; // ok! :)

function bar(arg: !string) {
  return arg.length;
}

bar(null) // type error
bar('foo') // 3

@Griffork Los .d.ts funcionarían bien y se pueden actualizar sin problemas para admitir definiciones más estrictas.
De forma predeterminada, .d.ts no se inscribe. Si un autor de biblioteca actualiza su biblioteca con definiciones no nulas fieles, lo indica colocando una bandera en la parte superior del archivo.
En ambos casos, todo simplemente funciona sin ningún pensamiento / configuración por parte del consumidor.

@kitsonk El problema que esto resuelve es reducir los errores porque la gente asume que algo no es nulo cuando podría serlo, o pasa nulos a funciones que no lo admiten. Si trabaja en proyectos grandes con muchas personas, puede consumir funciones que no escribió. O puede consumir bibliotecas de terceros que no conoce bien. Cuando lo hace: let x = array.sum(x => x.value) , ¿puede asumir que x nunca es nulo? ¿Quizás sea 0 si la matriz está vacía? ¿Cómo lo sabes? ¿Se permite que su colección tenga x.value === null ? ¿Es eso compatible con la función sum() o va a ser un error?
Con esta característica, esos casos se verifican estáticamente y se informa como errores al pasar un valor posiblemente nulo a una función que no lo admite o al consumir un valor posiblemente nulo sin una verificación.
Básicamente, en un programa completamente escrito, ya no puede haber "NullReferenceException" (menos los casos de esquina relacionados con undefined , desafortunadamente).

La discusión sobre los parámetros opcionales no está relacionada. Permitir nulos o no está relacionado con el sistema de tipos, no con las declaraciones de variables / parámetros y se integra muy bien con protectores de tipos, tipos de unión e intersección, etc.

type _void_ no tiene ningún valor, pero los valores null y undefined se le pueden asignar

aquí hay un buen resumen: https://github.com/Microsoft/TypeScript/issues/185#issuecomment -71942237

@jbondc Creo que revisé la especificación recientemente. void es un término general para null y undefined . No sé sobre void 0 , pero supongo que void también lo tiene bajo su paraguas.

Infiere any . pero null y undefined todavía se pueden asignar a todos los tipos.

void 0 siempre devuelve undefined también http://stackoverflow.com/a/7452352/449132.

@jbondc si la pregunta se refería a un sistema de tipo de conocimiento nulo potencial como el que describí anteriormente,

function returnsNull() {
  return null;
}

debe inferir el tipo null .

Hay muchos casos de esquina alrededor de undefined y, para ser honesto, no estoy (¿todavía?) Seguro de cuál es la mejor manera de abordarlo. Pero antes de pasar mucho tiempo pensando en ello, me gustaría saber si el equipo de TS estaría de acuerdo con una estrategia de inclusión voluntaria.

La última vez que participé en esta discusión terminó con: "No haremos cambios importantes y no queremos cambiar el significado del código fuente basado en opciones / indicadores ambientales". La idea que describo anteriormente no es un cambio rotundo y es relativamente buena con respecto a la interpretación de la fuente, aunque no al 100%. Esa zona gris es la razón por la que pregunto qué piensa el equipo de TS.

@ jods4 Preferiría el any nulo inferido

Preferiría una sintaxis explícita para tipos que no aceptan valores NULL. Para los objetos, rompería demasiadas cosas a la vez. Y también, los argumentos opcionales siempre deben ser anulables por razones obvias (y debería ser imposible de cambiar). Los argumentos predeterminados deben conservar su firma actual externamente, pero dentro de la función en sí, el argumento puede convertirse en no anulable.

Sin embargo, encuentro esto absurdo, ya que los números que aceptan valores NULL rompen muchas de las optimizaciones que pueden hacer los motores, y no es un caso de uso común fuera de los argumentos opcionales / predeterminados. Podría ser un poco más factible / no romper hacer que los números que no son argumentos opcionales no sean anulables de forma predeterminada a través de una marca de compilador. Esto también requeriría una sintaxis para marcar explícitamente los tipos que aceptan valores NULL, pero ya lo veo como un resultado probable de esta discusión.

[1] La introducción de una bandera para hacer que esto sea el predeterminado no funcionaría bien en primer lugar por razones prácticas. Solo mire los problemas con los que se encontraron las personas con --noImplicitAny , que resultaron en algunos peligros de migración, muchos problemas prácticos al probar la existencia de la propiedad que condujeron a --suppressImplicitAnyIndexErrors y varias definiciones rotas DefinitelyTyped.

@impinball

Preferiría el any nulo inferido

He editado mi respuesta aquí. Pensando más en esto, simplemente no veo ningún caso útil en el que pueda inferir implícitamente solo el tipo null . Como: () => null o let x = null o function y() { return null } .

Así que estoy de acuerdo en seguir deduciendo que any es probablemente algo bueno.

  1. Es compatible con versiones anteriores.
  2. Todos esos casos son errores por debajo de noImplicitAny todos modos.
  3. Si tiene un caso extraño en el que desea hacer eso y le resulta útil, puede declarar explícitamente el tipo: (): null => null o let x: null = null o function y(): null { return null } .

Preferiría una sintaxis explícita para tipos que no aceptan valores NULL. Para los objetos, rompería demasiadas cosas a la vez.

Decir que string realidad significa que string! | null puede ser otra opción. Cabe señalar que si lee todas las discusiones anteriores, esto se propuso al principio porque había la esperanza de que fuera más compatible con el código existente (sin cambiar el significado actual de string ). Pero, de hecho, la conclusión fue que, aun así, se romperían grandes cantidades de código.

Dado que la adopción de un sistema de tipos no anulables no será un cambio trivial para las bases de código existentes, elegiría la opción string? en lugar de string! porque se siente más limpia. Especialmente si observa el código fuente del compilador de TS, donde casi todos los tipos internos se nombrarían incorrectamente :(

Podría ser un poco más factible / no romper hacer que los números que no son argumentos opcionales no sean anulables de forma predeterminada a través de una marca de compilador. Esto también requeriría una sintaxis para marcar explícitamente los tipos que aceptan valores NULL, pero ya lo veo como un resultado probable de esta discusión.

Creo que las cosas se están volviendo confusas en este momento. Para mí, este parece un buen argumento para usar string? todas partes. No me gustaría ver mezclar number? y string! , esto se volvería demasiado confuso demasiado rápido.

[1] La introducción de una bandera para hacer que esto sea el predeterminado no funcionaría bien en primer lugar por razones prácticas.

Sí, no funcionaría bien para una base de código existente sin cambios. Pero:

  1. Funcionará muy bien para nuevas bases de código. Podemos construir un futuro mejor con esto.
  2. Las bases de código existentes obtendrán _algunos_ beneficios y comprobaciones adicionales, incluso si nunca se inscriben.
  3. Las bases de código existentes pueden optar por participar en cada archivo, lo que permite que el nuevo código sea más estricto y puede permitir la transición progresiva del código anterior, si se desea.

@ jods4

Creo que las cosas se están volviendo confusas en este momento. Para mí, esto parece un buen argumento para usar una cadena. En todas partes. ¿No me gustaría ver números mezclados? ¡y cuerda !, esto se volvería demasiado confuso demasiado rápido.

No lo quise decir en ese sentido. Me refiero a que hay menos casos de uso para los números que aceptan valores NULL que para las cadenas que aceptan valores NULL. No estoy tan apegado a esa sugerencia, de todos modos (no me importaría de ninguna manera).

Las bases de código existentes pueden optar por participar en cada archivo, lo que permite que el nuevo código sea más estricto y puede permitir la transición progresiva del código anterior, si se desea.

¿Con qué medios? No creo que actualmente pueda configurar el compilador archivo por archivo. Estoy abierto a que se demuestre que estoy equivocado aquí.

@impinball

¿Con qué medios? No creo que actualmente pueda configurar el compilador archivo por archivo. Estoy abierto a que se demuestre que estoy equivocado aquí.

Tal vez yo estoy equivocado. Nunca los he usado, pero cuando veo casos de prueba, veo muchos comentarios que parecen // <strong i="9">@module</strong> amd . Siempre asumí que era una forma de especificar opciones desde dentro de un archivo ts. Sin embargo, nunca intenté hacerlo, ¡así que tal vez esté completamente equivocado! Mira por ejemplo aquí:
https://github.com/Microsoft/TypeScript/blob/master/tests/cases/conformance/externalModules/amdImportAsPrimaryExpression.ts

Si esta no es una forma de especificar opciones por archivo, es posible que necesitemos algo nuevo. Es necesario especificar esta opción por archivo, _ al menos_ para .d.ts archivos. Un comentario con formato especial en la parte superior del archivo podría ser suficiente, después de todo, ya tenemos soporte para /// <amd-dependency /> y compañía.

EDITAR : Estoy equivocado. Hablar del código fuente me ha demostrado que esas cosas se llaman marcadores y el corredor FourSlash las analiza previamente. Entonces es solo para pruebas, no está dentro del compilador. Algo nuevo tendría que ser descubierto para eso.

También existen limitaciones prácticas en algunos casos; no es lo más práctico para algunos argumentos como las características de ES6, ya que requeriría volver a analizar completamente el archivo. Y IIUC, la verificación de tipos se realiza en el mismo paso que la creación de AST en primer lugar.

Si observa el código del analizador, los comentarios /// <amd-dependency /> y /// <amd-module /> se leen antes de analizar cualquier otra cosa en un archivo. Agregar algo como /// <non-null-types /> sería factible. Está bien, eso es terrible para nombrar y no estoy a favor de la proliferación de opciones "mágicas" arbitrarias, pero eso es solo una idea.

@ jods4 Creo que @I 'm pinball está diciendo que los IDE no lo admitirán sin grandes cambios en cómo funciona el resaltado de sintaxis.

¿Habrá alguna forma de tipo no anulable en 2.0?
Después de todo, mecanografiado trae muy pocas cosas al javascript si no puede garantizar el tipo. ¿Qué podemos obtener después de superar todos los problemas y reescribir introduciendo por mecanografiado?
¿Simplemente mejor IntelliSense? Dado que aún necesita verificar el tipo manualmente o por código en cada función de exportación a otros módulos.

Buenas noticias para todos, TypeScript 1.6 tiene suficiente poder expresivo para modelar y rastrear de manera segura un valor faltante (que de lo contrario están codificados por los valores null y undefined ). Básicamente, el siguiente patrón le brinda tipos que no aceptan valores NULL:

declare module Nothing { export const enum Brand {} }
interface Nothing { 'a brand': Nothing.Brand }
export type Nullable<a> = a | Nothing;
var nothing : Nothing = null;
export function isNothing(value: Nullable<a>): value is Nothing {
    return value == null;
}
var something = Math.random() > 0.5 ? 'hey!' : nothing;
if (isNothing(something)) {
    // missing value
    // there is no way you can get anything out of it
    // there is also NO WAY to get a null reference exception out of it
    // because it doesn't have any methods or properties that could be examined
    // it is 100% explicit and typesafe to use
} else {
    // value is present, it is 100% GUARANTEED being NON-NULL
    // you just CANT get a null reference exception here either
    console.log(something.toLowerCase());
}

/** turns any unsafe values into safe ones */
export function sanitize<a>(unsafe: a) : Nullable<a> {
    return unsafe;
}

var safe = sanitize(toResultFromExternalCodeYouCannotTrust()); // <-- 100% safe to use

Dicho esto, la solicitud debe cerrarse porque ya no existe ningún problema. Caso cerrado, clase despedida.

@ aleksey-bykov

Dicho esto, la solicitud debe cerrarse porque ya no existe ningún problema.

No puedes hablar en serio, ¿verdad? Les hemos dicho varias veces que un patrón para el tipo algebraico que acepta valores NULL no se trataba de este problema.

_No_ voy a envolver cada var x: number en mi código en un Nullable<number> o incluso mejor NonNullable<x> . Eso es demasiada sobrecarga. Sin embargo, quiero saber que hacer x *= 2 es 100% seguro, en cualquier lugar que suceda en mi código.

Su código no resuelve el problema de usar una biblioteca de terceros. No voy a llamar a sanitize en todas las bibliotecas de terceros, ni siquiera a la API DOM incorporada, a las que llamo. Además, no quiero agregar isNothing(safe) _por todo el lugar_. Lo que quiero es poder llamar a let squares = [1,2,3].map(x => x*x) y estar 100% seguro en el momento de la compilación de que squares.length es seguro. Con _cualquier_ API que utilice.

De manera similar, quiero documentación para bibliotecas JS de terceros de que una función acepta null como entrada o no. Quiero saber en tiempo de compilación si $(element).css(null) está bien o es un error.

Quiero trabajar en entornos de equipo, donde no puedo asegurarme de que todos utilicen patrones complejos como el suyo de manera constante. Su tipo Nulllable no hace absolutamente nada para evitar que un desarrollador haga let x: number = null; x.toString() (o algo menos estúpido, pero con el mismo efecto).

Y así sucesivamente y así sucesivamente. Este ticket está _ lejos_ de estar cerrado y el problema sigue ahí al 100%.

No puedes hablar en serio, ¿verdad?

Hablo muy en serio.

No voy a envolver todos y cada uno ...

¿Por qué no? Con la sintaxis ! o cualquier otra cosa que estén empujando, tendrían que hacerlo de todos modos.

o incluso mejor NonNullable

Debería ser Nullable, lo que estamos modelando son tipos que un valor que no admite nulos o un valor nulo y se indica explícitamente. Al contrario de los tipos convencionales que SIEMPRE pueden tener un valor o nulo y llamarse number lo que implica que debe verificar que no sea nulo antes de usarlo.

Eso es demasiada sobrecarga.

¿Donde?

Su código no resuelve el problema de usar una biblioteca de terceros.

Lo hace.

No voy a llamar a sanitize en todas las bibliotecas de terceros, o incluso en la API DOM incorporada, a las que llamo.

No tienes que hacerlo. Todo lo que necesita hacer es arreglar el archivo de definición de esa biblioteca de terceros reemplazando todo lo que pueda ser nulo con Nullable<*>

De manera similar, quiero documentación para bibliotecas JS de terceros que una función acepta nulo como entrada o no.

La misma cosa. Defina ese método como aceptar Nullable<string> lugar de un string simple.

Su tipo Nulllable no hace absolutamente nada para evitar que un desarrollador haga let x: number = null; x.toString ()

De hecho, no es así. Necesita prohibir la palabra clave null usando el linter.

Vamos, mi solución es 95% funcional y 100% práctica y está disponible HOY sin tener que tomar decisiones difíciles y poner a todos en la misma página. Es la pregunta qué está buscando: una solución de trabajo que no se ve como usted espera pero que, sin embargo, funciona, o que la obtenga exactamente de la manera que desea con todo lo que necesita y las cerezas en la parte superior

En 1.4 y 1.5, el tipo void no permite ningún miembro de Object, incluido como .toString (), por lo que type Nothing = void; debería ser suficiente en lugar de necesitar el módulo (a menos que esto cambie nuevamente en 1.6). http://bit.ly/1OC5h8d

@ aleksey-bykov eso no es realmente cierto. Aún debe tener cuidado de desinfectar todos los valores que posiblemente sean nulos. No recibirá una advertencia si se olvida de hacer eso. Es una mejora, seguro (hay menos lugares en los que tener cuidado).

Además, para ilustrar cómo los trucos no te llevarán muy lejos, intenta

if (isNothing(something)) {
  console.log(something.toString())
}

No voy a envolver todos y cada uno ...

¿Por qué no? Con ! sintaxis o cualquier otra cosa que estén empujando, tendrían que hacerlo de todos modos.

Bien, yo _podría_ hacer eso si todo lo demás está resuelto. Y si todo está resuelto, tal vez TS pueda incluso considerar un poco de azúcar en el lenguaje para eso.

o incluso mejor NonNullable

Debería ser Nullable, lo que estamos modelando son tipos que aceptan nulos que pueden tener un valor que no admite nulos o un valor nulo y se indica explícitamente. A diferencia de los tipos convencionales que SIEMPRE pueden tener un valor o nulo y ser llamados número, lo que implica que debe verificar que no sea nulo antes de usarlo.

No es que solo quiera deshacerme de las excepciones nulas. También quiero poder expresar tipos que no aceptan valores NULL, con seguridad estática.
Digamos que tiene un método que siempre devuelve un valor. Como toString() siempre devuelve una cadena no nula.
¿Cuáles son sus opciones allí?
let x: string = a.toString();
Esto no es bueno porque no existe una validación estática de que x.length sea ​​seguro. En este caso lo es, pero como dijiste, el string incorporado bien podría ser null así que ese es el _status quo_.
let x = sanitize(a.toString());
Bien, ahora no puedo usarlo sin una verificación nula, por lo que el código _es_ seguro. ¡Pero no quiero agregar if (isNothing(x)) todos los lugares donde uso x en mi código! Es feo e ineficiente, ya que muy bien podría saber que x no es nulo en tiempo de compilación. Hacer (<string>x).length es más eficiente, pero sigue siendo feo tener que hacerlo en cualquier lugar donde quieras usar x .

Lo que quiero hacer es:

let x = a.toString(); // documented, non-null type string (string! if you want to)
x.length; // statically OK

No puede lograr esto sin el soporte de lenguaje adecuado, porque todos los tipos en JS (y TS 1.6) son siempre anulables.

Me gustaría decir que programar con tipos no opcionales (que aceptan valores NULL) es una muy buena práctica, tanto como sea posible. Entonces, lo que estoy describiendo aquí es un escenario esencial, no una excepción.

Eso es demasiada sobrecarga.

¿Donde?

Vea mi respuesta anterior.

Su código no resuelve el problema de usar una biblioteca de terceros.

Lo hace.

Solo la mitad del problema está resuelto. Suponiendo que las definiciones de la biblioteca se actualizaron como propuso: cada entrada que acepta valores NULL o valor devuelto se reemplaza por Nullable<X> lugar de X.

Todavía puedo pasar null a una función que no acepta los parámetros null . OK, este hecho ahora está _documentado_ (que ya podemos hacer con los comentarios JSDoc o lo que sea) pero lo quiero _aplicado_ en tiempo de compilación.
Ejemplo: declare find<T>(list: T[], predicate: (T) => bool) . Ambos parámetros no deben ser nulos (no he usado Nullable ) pero puedo hacer find(null, null) . Otro posible error es que la declaración dice que debería devolver un bool no nulo del predicado, pero puedo hacer: find([], () => null) .

Su tipo Nulllable no hace absolutamente nada para evitar que un desarrollador haga let x: number = null; x.toString ()

De hecho, no es así. Necesita prohibir la palabra clave nula usando el linter.

Hacerlo y proporcionar una variable global nothing lugar no lo ayudará con los casos que enumeré en el punto anterior, ¿verdad?

Desde mi punto de vista, esto no es del 95%. Siento que la sintaxis empeoró mucho para muchas cosas comunes y todavía no tengo toda la seguridad estática que quiero cuando hablo de tipos que no aceptan valores NULL.

Aún debe tener cuidado de desinfectar todos los valores que posiblemente sean nulos.

Justo el mismo tipo de trabajo de separación que tendrías que hacer de todos modos con tipos hipotéticos que no aceptan valores NULL de los que están hablando aquí. Debería revisar todos sus archivos de definiciones y poner ! donde corresponda. ¿En qué se diferencia eso?

No recibirá una advertencia si se olvida de hacer eso.

La misma cosa. La misma cosa.

ilustra cómo los trucos no te llevarán muy lejos, solo intenta

Aunque está bien con la forma en que definí Nothing que tiene toString de Object , pero ... si tomamos la idea de @Arnavion (de usar void por Nothing ) todo hace clic de repente

Reloj

image

Desde mi punto de vista, esto no es del 95%. Siento que la sintaxis empeoró mucho para muchas cosas comunes y todavía no tengo toda la seguridad estática que quiero cuando hablo de tipos que no aceptan valores NULL.

hombre, me acabas de convencer, este patrón no es para ti y nunca lo será, por favor no lo uses nunca en tu código, sigue preguntando por los tipos reales que no aceptan nulos, buena suerte, perdón por molestarte con tales tonterías

@ aleksey-bykov
Veo lo que está tratando de hacer, aunque me parece que aún no ha resuelto todos los casos y parece que está inventando soluciones en el camino cuando alguien señala un agujero (como la sustitución de null por void en el último ejemplo).

Al final _quizás_ conseguirás que funcione como yo quiero. Utilizará una sintaxis compleja para los tipos que aceptan valores NULL, habrá prohibido con un linter cualquier fuente de null en el programa, lo que eventualmente puede hacer que los tipos que aceptan valores NULL no sean nulos. En el camino, tendrá que convencer a todos de que actualicen sus bibliotecas utilizando su convención no estándar; de lo contrario, no sirve de nada (fíjese, esto es cierto para _todas_ las soluciones al problema nulo, no solo la suya).

Al final, puede tener éxito en torcer el sistema de tipos para evitar el tipo null incorporado y tener los cheques que queremos adjuntos a su tipo Nothing .

En este punto, ¿no cree que el idioma debería admitirlo? El lenguaje podría implementar todo prácticamente de la misma manera que usted desea (internamente). Pero además de eso, tendrá una buena sintaxis, casos extremos resueltos, sin necesidad de linters externos y seguramente una mejor adopción por parte de definiciones de terceros. ¿No tiene eso más sentido?

En este punto, ¿no cree que el idioma debería admitirlo?

Hablemos de lo que pienso. Creo que sería bueno despertar mañana en el mundo donde TypeScript tiene tipos que no aceptan valores NULL. De hecho, este es el pensamiento con el que me acuesto todas las noches. Pero nunca sucede al día siguiente y me frustro. Luego me pongo a trabajar y me encuentro con el mismo problema con nulos una y otra vez hasta hace poco, cuando decidí buscar un patrón que pudiera hacerme la vida más fácil. Parece que lo encontré. ¿Todavía deseo que tuviéramos no nulables en TypeScript? Seguro que lo hago. ¿Puedo vivir sin ellos y sin frustración? Parece que puedo.

¿Todavía deseo que tuviéramos no nulables en TypeScript? Seguro que lo hago.

Entonces, ¿por qué quieres cerrar este problema? : smiley:

Dicho esto, la solicitud debe cerrarse porque ya no existe ningún problema. Caso cerrado, clase despedida.

Me alegro de que haya encontrado una solución para sus necesidades, pero al igual que usted, espero que algún día TS reciba el apoyo adecuado. Y creo que esto aún puede suceder (no pronto, claro). El equipo de C # actualmente está haciendo las mismas lluvias de ideas y tal vez esto pueda ayudar a avanzar. Si C # 7 logra obtener tipos no nulos (lo cual aún no es seguro), no hay razón para que TS no haga lo mismo.

@ aleksey-bykov

Entonces para simplificar

var nothing: void = null;
function isNothing<a>(value: a | void): value is void {
    return value == null;
}
var something = Math.random() > 0.5 ? 'hey!' : nothing;

y ahora también puede usarlo sin envoltorios de función: las definiciones de tipo adecuadas son suficientes.

Esa es básicamente la solución que originalmente me hizo un poco feliz hasta que de confiar en ella .

¡Tendría que revisar todos sus archivos de definiciones y ponerlos! donde corresponda. ¿En qué se diferencia eso?

La principal diferencia cuando se utilizan funciones estándar del lenguaje oficial en lugar de hacks es que la comunidad (DefinitelyTyped) está ahí para ayudar a escribir, probar y compartir los archivos de definición en lugar de que todo ese trabajo adicional se acumule en la parte superior de sus proyectos (bueno, en realidad :sonrisa:).

Abogo por " --noImplicitNull ". Eso haría que todos los tipos no aceptaran nulos de forma predeterminada, lo que daría retroalimentación de inmediato sobre posibles problemas en cualquier código existente. Si hay "!", Debería comportarse como Swift para hacer que el comprobador de tipos ignore la posibilidad de nulos (conversión insegura a no anulable), aunque eso está en conflicto con el operador ES7 + "eventual send" en promesas, por lo que podría no ser el mejor ocurrencia.

Si eso parece demasiado radical en términos de rotura de compatibilidad con versiones anteriores, es realmente mi culpa. Realmente debería intentar encontrar el tiempo y probarlo en una bifurcación para demostrar que no es tan radical como parece. De hecho agregando "!" es más radical: requeriría más cambios para aprovecharlo ya que la mayoría de los valores no son realmente nulos; --noImplicitNull es más gradual, ya que descubrirá más errores al corregir las definiciones de tipo que reclaman incorrectamente valores no nulos y sin esas correcciones obtendrá el mismo comportamiento excepto para la asignación nula, valores no inicializados y comprobaciones olvidadas para campos de objetos opcionales .

Añadiendo mi voz a la discusión, me gusta cómo Kotlin ha resuelto el problema, es decir

  • las propias variables no tienen tipos que aceptan valores NULL de forma predeterminada, un '?' cambia eso
  • las variables externas son anulables, un '!!' anula
  • un cheque nulo es un tipo de cast
  • El código externo puede ser analizado por una herramienta que genera anotaciones.

Comparar:
1
2

  • si a|void está prohibido, se puede cambiar a a|Nothing

La principal diferencia cuando se utilizan funciones estándar del lenguaje oficial en lugar de hacks es que la comunidad (DefinitelyTyped) está ahí para ayudar a escribir, probar y compartir los archivos de definición.

  • No puedo ver por qué usar funciones 100% legítimas es un truco
  • No sobrestime las habilidades de la comunidad, muchas (si no la mayoría) de las definiciones son de baja calidad e incluso aquellas como, digamos, jquery a menudo no se pueden usar tal como están escritas sin algunas modificaciones
  • de nuevo, un patrón nunca será tan bueno como una característica completa, espero que todos lo entiendan, sin duda
  • un patrón puede resolver sus problemas hoy, mientras que una característica puede o no estar cerca
  • Si una situación puede resolverse eficientemente (hasta un 95%) empleando AB y C, ¿por qué alguien necesitaría que D hiciera lo mismo? anhelando un poco de azúcar? ¿Por qué no centrarnos en algo que no puede resolverse mediante un patrón?

De todos modos, hay solucionadores de problemas y perfeccionistas, como se demostró que el problema tiene solución.

(Si no desea depender del tipo void, Nothing también puede ser class Nothing { private toString: any; /* other Object.prototype members */ } que tendrá el mismo efecto. Consulte el n. ° 1108)

_Nota: nada aquí se trata de eliminar la sintaxis. Por favor no lo tomes
como tal._

Lo diré simplemente: aquí está el problema con la solución TS 1.6 propuesta
aquí: puede funcionar de manera factible, pero no se aplica a la biblioteca estándar o
la mayoría de los archivos de definición. Tampoco se aplica a los operadores matemáticos. Si quieres
para quejarse sobre el tipo redundante / tiempo de ejecución repetitivo, intente hacer genérico
Las colecciones funcionan en C ++, esto palidece en comparación, especialmente si desea
iteración perezosa o colecciones que no son matrices (o peor aún, intente
combinando los dos).

El problema con la solución propuesta actualmente por @ aleksey-bykov es que
no se aplica en el núcleo del lenguaje. Por ejemplo, no puedes hacer
Object.defineProperty(null, "name", desc) - obtendrá un TypeError de
el objeto de destino null . Otra cosa: no puede asignar ninguna propiedad a un
null objeto, como en var o = null; o.foo = 'foo'; . Obtendrás un
ReferenceError IIRC. Esta solución propuesta no puede dar cuenta de esos casos.
Es por eso que necesitamos soporte de idiomas para ello.


_Ahora, un poco de leve desprendimiento de bicicletas ..._

Y en cuanto a la sintaxis, me gusta la concisión de la "? Cadena" y
sintaxis "! string", pero en cuanto al resultado final, no me importa mientras esté
sin escribir ThisIsANullableType<T, U, SomeRidiculousAndUnnecessaryExtraGeneric<V, W>> .

El miércoles 29 de julio de 2015 a las 16:10, Aleksey Bykov [email protected] escribió:

  • si se prohíbe un | vacío, se puede cambiar a un | Nada

    La principal diferencia al utilizar funciones de idioma oficial estándar
    en lugar de hacks es que la comunidad (DefinitelyTyped) está ahí para ayudar
    escribir, probar y compartir los archivos de definición

    No puedo ver por qué usar funciones 100% legítimas es un truco

    no sobreestime las habilidades de la comunidad, muchas (si no la mayoría)
    definiciones hay de baja calidad e incluso aquellas como, digamos, jquery bastante

    a menudo no se puede usar como está escrito sin algunas modificaciones

    de nuevo, un patrón nunca será tan bueno como una función completa, espero

    todo el mundo lo entiende, sin duda

    un patrón puede resolver sus problemas hoy, mientras que una característica puede o puede

    no estar cerca

    si una situación puede resolverse de manera eficiente (hasta un 95%) empleando AB
    y C, ¿por qué alguien necesitaría que D hiciera lo mismo? anhelando un poco de azúcar? por qué
    nos enfocamos en algo que no se puede resolver con un patrón?

De todos modos, hay solucionadores de problemas y perfeccionistas, como se demostró el
el problema tiene solución

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -126082053
.

¿Y podríamos al menos tener una idea de cómo se vería la estructura del cobertizo para bicicletas antes de obsesionarnos con el color de pintarlo? La mayor parte de lo que he visto hasta ahora después de los primeros ~ 10 comentarios es "¡Oye, queremos construir un cobertizo para bicicletas! ¿De qué color deberíamos pintarlo?" Nada sobre asegurarse de que el diseño sea estructuralmente sólido. (Consulte el n. ° 3192 y la primera mitad del n. ° 1206 para ver un par de ejemplos más de esto: la mayor parte del ruido se apagó cuando finalmente hice una propuesta seria con una sintaxis creada lógicamente y una semántica completamente especificada).

Tenga en cuenta: esto muy probablemente resultará en una refactorización importante y una reescritura parcial de las definiciones de tipo de biblioteca estándar. También resultará en la necesidad de reescribir la mayoría de las definiciones de tipo en DefinitelyTyped para la primera versión de TS que lo admita. Así que tenga en cuenta que esto definitivamente se romperá. (Una solución podría ser emitir siempre de forma predeterminada, incluso cuando se produzcan errores relacionados con nulos, pero proporcionar una marca a la --noImplicitAny para cambiar ese comportamiento).

Es cierto que estoy un poco por encima de mi cabeza aquí. Sin embargo, busqué en scholar.google "javascript anulable" y "javascript anulable":
Entendiendo TypeScript
tiene una buena tabla de tipos en TypeScript (módulos genéricos).

Tipos dependientes para JavaScript
parece ser importante. el código fuente desapareció

Confíe, pero verifique: escritura en dos fases para la dinámicaIdiomas
Tipos de refinamiento para lenguajes de secuencias de comandos
ofrece una solución basada en mecanografiado.
("La abreviatura t? Representa t + nulo"; suena como # 186)

@afrische Mucho de esto ya se está usando prácticamente en verificadores de tipo JS. Flow utiliza la mayoría de los modismos en "Confiar, pero verificar", por ejemplo. También está Infernu , un verificador de tipo WIP JS que se basa en gran medida en la inferencia de tipo 1 de Hindley-Milner para inferir sus tipos. Pero yo divago...

[1] Haskell, OCaml, etc. también utilizan una versión modificada para sus sistemas de tipos.

Actualmente estoy jugando con una bifurcación de TS que asume que los tipos no son anulables por defecto y eso hace que el tipo Null (existente) sea referenciable con null , por ejemplo, string | null . También agregué sintaxis para string? que es simplemente un atajo para el mismo tipo de unión.

Cuando lo piensas profundamente, es lo mismo que @ aleksey-bykov, pero donde null es el tipo Nothing , integrado.

¡Y eso ni siquiera es complicado de hacer porque el sistema de tipos ya es bastante bueno para manejar todo esto!

Donde las cosas se ponen difíciles es que queremos tener un camino de transición suave. Necesitamos cierta compatibilidad con versiones anteriores, _al menos_ con los archivos de definición existentes (la compatibilidad con el proyecto en sí podría ser una marca de inclusión voluntaria, aunque sería mejor si el significado de un fragmento de código, digamos en Internet, no lo hiciera ' Depende de los indicadores del compilador global).

La idea de @afrische de usar string? en su propio proyecto pero usar string! en .d.ts podría ser un nuevo enfoque. Aunque no me gusta mucho la dualidad arbitraria que crea. ¿Por qué string anulable y algunos archivos y no anulable en otros? Parece raro.

Si prefiere no tener cadenas anulables en algunos archivos y no en otros.
Me gusta la idea de @impinball de tener un indicador de exclusión

Este hilo tiene más de un año y es difícil averiguar cuáles son todos los diseños e inquietudes relevantes con casi 300 comentarios y solo unos pocos del propio equipo de TS.

Estoy planeando migrar una gran base de código a Flow, Closure Compiler o TypeScript, pero la falta de seguridad nula en TypeScript es un factor decisivo.

Sin haber leído todo el hilo, no puedo entender qué está mal al agregar los especificadores de nulabilidad ! y ? mientras se mantiene el comportamiento existente para los tipos que carecen de ellos, así que aquí está como una propuesta :

Propuesta

declare var foo:string // nullability unspecified
declare var foo:?string // nullable
declare var foo:!string // non-nullable

Con ?string un superconjunto de !string , y string tanto un superconjunto como un subconjunto de ambos, es decir, la relación entre string y ambos ?string y !string es la misma que la relación entre any y todos los demás tipos, es decir, un tipo simple sin ! o ? es como un any con respecto a la nulidad.

Se aplican las siguientes reglas:

| Tipo | Contiene | Proporciona | ¿Puede asignar null ? | ¿Se puede asumir que no null ? |
| --- | --- | --- | --- | --- |
| T | T , !T , ?T | T , ?T , !T | si | si |
| ?T | T , !T , ?T | T , ?T | si | no (se requiere protección de tipo) |
| !T | T , !T | T , ?T , !T | no | si |

Esto proporciona seguridad nula sin romper el código existente y permite que las bases de código existentes introduzcan seguridad nula de forma incremental, al igual que any permite que las bases de código existentes introduzcan seguridad de tipos de forma incremental.

Ejemplo

Aquí hay un código con un error de referencia nulo:

function test(foo:Foo) { foo.method(); }
test(null);

Este código todavía pasa. null aún se puede asignar a Foo y aún se puede suponer que Foo no es nulo.

function test(foo:!Foo) { foo.method(); }
test(null);

Ahora el test(null) es un error, ya que null no se puede asignar a !Foo .

function test(foo:?Foo) { foo.method(); }
test(null);

Ahora el foo.bar() es un error, ya que una llamada a un método en ?Foo no está permitida (es decir, el código debe verificar null ).

¡Gracias! Y me gusta mucho esta idea. Aunque, por razones de compatibilidad,
¿Podríamos hacer ?T y T solo alias, como Array<T> y T[] ? Eso
simplificaría las cosas, y la versión sin decorar todavía es técnicamente
anulable.

El viernes 28 de agosto de 2015 a las 07:14, Jesse Schalken [email protected] escribió:

Este hilo tiene más de un año y es difícil averiguar qué
Los diseños e inquietudes relevantes tienen casi 300 comentarios y solo unos pocos
del propio equipo de TS.

Estoy planeando migrar una gran base de código a Flow, Closure Compiler o
TypeScript, pero la falta de seguridad de tipos en TypeScript es un factor decisivo.

Sin haber leído todo el hilo, no puedo entender qué está mal
con la adición de ambos! y ? especificadores de nulabilidad manteniendo el
comportamiento existente para los tipos que carecen de ellos, así que aquí está como una propuesta:
Propuesta

declare var foo: string // nulabilidad sin especificar declarar var

Con? Encadena un superconjunto de! Cadena y encadena tanto un superconjunto como un subconjunto
de ambos, es decir, la relación entre cadena y? cadena y! cadena
es la misma que la relación entre cualquiera y todos los demás tipos, es decir, un
tipo desnudo sin! o ? es como cualquiera con respecto a la nulidad.

Se aplican las siguientes reglas:
Tipo Contiene Proporciona ¿Puede asignar un valor nulo? ¿Se puede asumir no nulo? TT,! T,? TT,
? T,! T si si? TT,! T,? TT,? T si no (se requiere protección de tipo)! TT,! TT,
? T,! T no si

Esto proporciona seguridad nula sin romper el código existente y permite
bases de código existentes para introducir seguridad nula de forma incremental, como cualquier
permite que las bases de código existentes introduzcan seguridad de tipos de forma incremental.
Ejemplo

Aquí hay un código con un error de referencia nulo:

prueba de función (foo: Foo) {foo.method (); } prueba (nulo);

Este código todavía pasa. null todavía es asignable a Foo y Foo todavía puede
se supondrá que no es nulo.

prueba de función (foo:! Foo) {foo.method (); } prueba (nulo);

Ahora la prueba (nula) es errónea, ya que nula no se puede asignar a! Foo.

prueba de función (foo:? Foo) {foo.method (); } prueba (nulo);

Ahora foo.bar () tiene un error, ya que una llamada de método en? Foo no está permitida
(es decir, el código debe comprobar si es nulo).

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -135743011
.

@impinball

¡Gracias! Y me gusta mucho esta idea. Aunque, por razones de compatibilidad, ¿podríamos hacer ?T y T solo alias, como Array<T> y T[] ? Simplificaría las cosas, y la versión sin decorar todavía es técnicamente anulable.

La _ idea completa_ es que T , ?T y !T son tres cosas distintas, y eso _es_ por motivos de compatibilidad (compatibilidad con el código TS existente). No sé cómo explicarlo mejor, lo siento. Quiero decir, hice una mesita y todo.

Bueno. Buen punto. Malinterpreté la tabla en esa parte, y pasé por alto
el caso de cambio de archivos de definición existentes, lo que causa problemas con
otras aplicaciones y bibliotecas.

El viernes 28 de agosto de 2015 a las 11:43, Jesse Schalken [email protected] escribió:

@impinball https://github.com/impinball

¡Gracias! Y me gusta mucho esta idea. Aunque, por razones de compatibilidad,
¿Podríamos hacer? T y T solo alias, como Arrayy T[]? Sería
simplifica las cosas, y la versión sin decorar sigue siendo técnicamente anulable.

Toda la idea es que T,? T y! T son tres cosas distintas,
y que _es_ por motivos de compatibilidad (compatibilidad con TS existente
código). No sé cómo explicarlo mejor, lo siento. Quiero decir, hice un
mesita y todo.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -135810311
.

perdón por ser un idiota, pero esto no tiene sentido

?string un superconjunto de !string , y string tanto un superconjunto como un subconjunto de ambos

de la teoría de conjuntos sabemos que un conjunto A es un subconjunto y un superconjunto de un conjunto B es cuando y solo cuando A = B

si es así, cuando dices "de ambos" solo puede significar todo-de-? cadena = todo-de-cadena y todo-de-! cadena = todo-de-cadena

entonces, en última instancia, todo-de-? cadena = todo-de-! cadena

desde donde se bajan todos, el autobús no va a ninguna parte, el autobús está fuera de servicio

@ aleksey-bykov Probablemente un caso de redacción deficiente. Creo que quiere decir que string es más como ?string | !string , tomando las restricciones más permisivas.

Todo esto es similar en alcance a las cada vez más comunes anotaciones de tipo @Nullable y @NonNull / @NotNull en tiempo de compilación de Java, y cómo funcionan con tipos no anotados.

Y definitivamente estaría a favor de una nueva bandera para los tipos que se toman implícitamente como no anulables, particularmente los primitivos.

Un indicador "no anulable por defecto" sería bueno, pero también rompería una gran cantidad de definiciones DefinitelyTyped. Esto incluye las definiciones de Angular y Node allí, y su reparación requeriría mucho trabajo tedioso. También puede requerir un backport para que los nuevos tipos aún no analicen como errores de sintaxis, pero no se verifica la nulabilidad. Tal backport sería la única forma práctica de mitigar la rotura con dicha bandera, especialmente cuando las definiciones se actualizan para los tipos que aceptan valores NULL. (La gente todavía usa TypeScript 1.4 en desarrollo, especialmente en proyectos más grandes).

@impinball

La gente todavía usa TypeScript 1.4 en el desarrollo, especialmente en proyectos más grandes.

Creo que la historia de compatibilidad para esta función ya es bastante difícil sin intentar hacer que las nuevas definiciones sean compatibles con los compiladores antiguos (los tipos no nulos de FWIW son casi triviales de agregar al compilador TS actual).

Si las personas continúan con el antiguo ST, entonces deberían utilizar las antiguas definiciones. (Sé que eso no es práctico).
Quiero decir que las cosas se van a romper de todos modos. Pronto TS 1.6 agregará tipos de intersección y las definiciones que lo usan tampoco serán compatibles.

Por cierto, me sorprende que la gente se quede en 1.4, hay mucho que amar en 1.5.

@ aleksey-bykov

perdón por ser un idiota, pero esto no tiene sentido

Supongo que te estás llamando "sabelotodo" porque sabes perfectamente bien por el resto de la publicación lo que se pretende que signifique "superconjunto y subconjunto de ambos". Por supuesto, no tiene sentido si se aplica a conjuntos reales.

any puede asignarse a cualquier tipo (se comporta como un superconjunto de todos los tipos) y tratarse como cualquier tipo (se comporta como un subconjunto de todos los tipos). any significa "optar por no verificar el tipo de este valor".

string se pueden asignar desde !string o ?string (se comporta como un superconjunto de ellos) y se pueden tratar como !string y ?string (se comporta como un subconjunto de ellos). string significa "excluirse de la comprobación de nulabilidad para este valor", es decir, el comportamiento actual de TS.

@impinball

Y definitivamente estaría a favor de una nueva bandera para los tipos que se toman implícitamente como no anulables, particularmente los primitivos.

@RyanCavanaugh dijo explícitamente:

Las banderas que cambian la semántica de un idioma son algo peligroso. [...] Es importante que alguien que esté mirando un fragmento de código pueda "seguir" el sistema de tipos y comprender las inferencias que se están haciendo. Si empezamos a tener un montón de banderas que cambian las reglas del lenguaje, esto se vuelve imposible.

El único tipo de cosa segura que se puede hacer es mantener la misma semántica de asignabilidad y cambiar lo que es un error y lo que no depende de una bandera, muy parecido a cómo funciona noImplicitAny hoy.

Es por eso que mi propuesta mantiene el comportamiento existente para los tipos que carecen de un especificador de nulabilidad ( ! o ? ). El indicador _only_ safe para agregar sería uno que no permita los tipos que carecen de un especificador de nulabilidad, es decir, solo toma el código de paso y hace que se produzca un error, no cambia su significado. Supongo que se permitió noImplicitAny por la misma razón.

@jesseschalken

Quise decir que en el contexto de variables tipadas implícitamente no inicializadas
a null o undefined en casos como estos:

var a = new Type(); // type: !Type
var b = 2; // type: !number
var c = 'string'; // type: !string
// etc...

Perdón por la confusion.

El viernes 28 de agosto de 2015 a las 19:54, Jesse Schalken [email protected] escribió:

@impinball https://github.com/impinball

Y definitivamente estaría a favor de una nueva bandera para los tipos que se
implícitamente tomados como no anulables, particularmente primitivos.

@RyanCavanaugh https://github.com/RyanCavanaugh dijo explícitamente:

Las banderas que cambian la semántica de un idioma son algo peligroso. [...]
Es importante que alguien que esté mirando un fragmento de código pueda "seguirlo"
con el sistema de tipos y comprender las inferencias que se están haciendo. Si
empezamos a tener un montón de banderas que cambian las reglas del idioma,
esto se vuelve imposible.

La única forma segura de hacer es mantener la semántica de
asignabilidad igual y cambiar lo que es un error frente a lo que no depende
en una bandera, al igual que noImplicitAny funciona hoy.

Por eso mi propuesta mantiene el comportamiento existente para tipos que carecen
un especificador de nulabilidad (! o?). La _única_ bandera segura para agregar sería
uno que no permite tipos que carecen de un especificador de nulabilidad, es decir, solo
toma el código de paso y hace que se produzca un error, no cambia su significado. I
suponga que noImplicitAny se permitió por la misma razón.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -135916676
.

@jeffmcaffer lo que está tratando de decir al alterar el significado original de las palabras superconjunto y subconjunto aún se puede articular en términos de conjuntos fácilmente: el conjunto de valores de string es una unión de valores de !string y ?string significan cualquier cosa desde !string o / y ?string pertenecen a string

@impinball Nuevamente, tal bandera cambiaría el significado del código existente, que (por leer los comentarios de @RyanCavanaugh ) no está permitido. export var b = 5; ahora exportará un !number donde anteriormente exportaba un number .

Sí, en cierto sentido. Para ser un poco más técnico, acepta la unión, pero
proporciona la intersección. Básicamente, cualquier tipo puede contar como
string , y se puede pasar string para cualquier tipo.

El viernes 28 de agosto de 2015 a las 20:20, Aleksey Bykov [email protected] escribió:

@impinball https://github.com/impinball lo que está tratando de decir por
alterar el significado original de las palabras superconjunto y subconjunto todavía puede ser
articulado en trminos de conjuntos fcilmente: el conjunto de valores de cadena es un
_unión_ de valores de! cadena y? cadena que significa cualquier cosa de! cadena
o / y? la cadena pertenece a la cadena

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -135920233
.

Obviamente, no está destinado a estar activado de forma predeterminada. Y la mayoría de los archivos de definición
no se vería afectado. Técnicamente, cualquier cambio que no sea puramente
aditivo (incluso agregar un nuevo argumento a una función no está en JavaScript) tiene
la capacidad de romper aplicaciones. Es similar en alcance a
noImplicitAny porque obliga a escribir un poco más explícitamente. Y yo
No creo que pueda romperse mucho más que eso, especialmente porque el único
La forma en que esto podría afectar a otros archivos es a través de exportaciones desde TypeScript real.
archivos fuente. (La otra bandera rompió numerosos archivos de definición y deshabilitó
una forma frecuente de prueba de patos.)

El viernes 28 de agosto de 2015 a las 20:21, Jesse Schalken [email protected] escribió:

@impinball https://github.com/impinball Nuevamente, esa bandera cambiaría
el significado del código existente, que (de leer @RyanCavanaugh
https://github.com/RyanCavanaugh's comments) no está permitido. exportar var
b = 5; ahora exportará un! número donde anteriormente exportaba un número.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -135920315
.

Obviamente, no está destinado a estar activado de forma predeterminada.

Todavía está cambiando el significado del código. Tan pronto como hagas eso, no podrás leer un código TypeScript y decir "Sé lo que esto significa", ni puedes escribir una biblioteca que otra persona usará, porque el significado del código depende de las banderas que se usen para compilarlo, y eso es inaceptable. Literalmente has dividido el idioma en dos.

Es por eso que mi propuesta mantiene el comportamiento existente para los tipos que carecen de un especificador de nulabilidad ( ? o ! ).

@jesseschalken
Si desea (y logra) preservar con 100% de fidelidad el significado del código existente, entonces debe renunciar a la idea de seguridad nula en el texto mecanografiado. Ninguna solución se podrá utilizar en la práctica.

Tener que poner ? y ! en cada tipo de anotación ya es lo suficientemente detallado como para que no quiera hacerlo, pero también tendrías que renunciar a todas las inferencias de tipos. let x = 3 infiere number hoy. Si no acepta un cambio aquí, significa que debe escribir todo explícitamente para obtener los beneficios de la seguridad nula. Tampoco es algo que esté dispuesto a hacer.

Cuando los beneficios superan a los inconvenientes, se pueden hacer algunas concesiones. Como ha señalado @impinball, el equipo de TS hizo exactamente eso con noImplicitAny . Es una bandera que crea nuevos errores en su código cuando lo enciende. Como tal, copiar y pegar código de Internet, o incluso simplemente usar bibliotecas de TS, puede fallar si el código que ingresa no se escribió bajo la suposición noImplicitAny (y me pasó a mí).

Creo que la seguridad nula se puede introducir de manera similar. El significado del código es el mismo y se ejecuta con una semántica exactamente idéntica. Pero bajo una nueva bandera (digamos noImplicitNull o lo que sea) obtendría verificaciones adicionales y advertencias / errores del código que no está escrito con la suposición noImplicitNull .

Creo que la seguridad nula se puede introducir de manera similar. El significado del código es el mismo
y se ejecuta con semántica exactamente idéntica. Pero bajo una nueva bandera (di noImplicitNull o lo que sea)
obtendría verificaciones adicionales y advertencias / errores de código que no está escrito con
el supuesto noImplicitNull.

Me gusta ese enfoque y parece una forma lógica de evolucionar el idioma. Espero que, con el tiempo, se convierta en el estándar de facto de la misma manera que las mecanografías se escriben normalmente sin tener en cuenta ningúnImplicitAny.

Sin embargo, creo que lo importante en lo que respecta a la adopción gradual es asegurarse de que el código existente se pueda migrar un módulo a la vez y que el nuevo código escrito con nulos explícitos en mente pueda funcionar fácilmente con el código existente que utiliza nulos implícitos.

Entonces, ¿qué tal esto?

  • Con -noImplicitNull, T convierte en un alias de !T . De lo contrario, se desconoce la posibilidad de nulidad de T .
  • La bandera debe ser reemplazable por módulo agregando una anotación en la parte superior, por ejemplo. <strong i="18">@ts</strong>:implicit_null . Esto es similar a la forma en que Flow habilita la verificación de tipos por módulo.
  • La conversión de una base de código existente se realiza agregando primero la opción del compilador -noImplicitNull, luego anotando todos los módulos existentes con el indicador '
  • Al importar un módulo, es necesario que exista una política sobre cómo convertir los tipos si los módulos importados e importados tienen diferentes configuraciones de implícita.

Hay diferentes opciones para ese último punto si un módulo con nulos explícitos importa uno con nulos implícitos.

  • Un enfoque extremo sería tratar la nulabilidad de los tipos T como desconocidos y requerir que el importador transmita explícitamente el tipo a ?T o !T . Eso requeriría muchas anotaciones en el destinatario, pero sea safar.
  • Otro enfoque sería tratar todos los tipos T importados como ?T . Esto también requeriría muchas anotaciones en la persona que llama.
  • Por último, todos los tipos T importados podrían tratarse como !T . Por supuesto, esto sería incorrecto en algunos casos, pero podría ser la opción más pragmática. Similar a la forma en que una variable de tipo any puede asignarse a un valor de tipo T .

¿Pensamientos?

@ jods4 La noImplicitAny _no_ cambia el significado del código existente, solo requiere que el código sea explícito sobre algo que de otra manera estaría implícito.

| Código | Bandera | Significado |
| --- | --- | --- |
| interface Foo { blah; } | | interface Foo { blah:any; } |
| interface Foo { blah; } | noImplicitAny | error, tipo explícito requerido |
| var foo = 'blah' | | var foo:string = 'blah' |
| var foo = 'blah' | noImplicitNull | var foo:!string = 'blah' |

Con noImplicitNull , antes tenía una variable en la que se podía escribir nulo. Ahora tiene una variable en la que no se puede escribir null _no_. Esa es una bestia completamente diferente a noImplicitAny .

@RyanCavanaugh ya ha descartado las banderas que cambian la semántica del código existente. Si va a ignorar rotundamente los requisitos expresos del equipo de TS, entonces este boleto va a durar un año más.

@jesseschalken Lo siento, pero no veo la diferencia.
Antes de noImplicitAny puede tener esto en su código:
let double = x => x*2;
Se compila y funciona bien. Pero una vez que enciende noImplicitAny , el compilador le arroja un error diciendo que x es implícitamente cualquiera. Tienes que modificar tu código para que funcione con la nueva bandera:
let double = (x: any) => x*2 o mejor aún let double = (x: number) => x*2 .
Tenga en cuenta que aunque el compilador generó un error, aún emitiría un código JS que funciona perfectamente (a menos que desactive la función Emitir en caso de errores).

En mi opinión, la situación con nulos es prácticamente la misma. Supongamos para la discusión que con la nueva bandera, T no admite nulos y T? o T | null denota la unión del tipo T y null .
Antes, podría haber tenido:
let foo: string; foo = null; o incluso solo let foo = "X"; foo = null que se inferiría string la misma manera.
Se compila y funciona bien. Ahora encienda la nueva bandera noImplicitNull . De repente, TS arroja un error que indica que no puede asignar null a algo que no se declaró explícitamente como tal. Pero a excepción del error de escritura, su código aún emite _el mismo_, código JS correcto.
Con la bandera, debe indicar su intención explícitamente y modificar el código:
string? foo; foo = null;

Entonces, ¿cuál es la diferencia, realmente? El código siempre se emite bien y su comportamiento en tiempo de ejecución no ha cambiado en absoluto. En ambos casos, obtiene errores del sistema de escritura y debe modificar su código para que sea más explícito en sus declaraciones de tipo para deshacerse de ellos.

Además, en ambos casos, es posible tomar código escrito bajo la bandera estricta y compilarlo con la bandera apagada y todavía funciona igual y sin errores.

@robertknight Muy cerca de mi pensamiento actual.

Para los módulos / definiciones que no han optado por los tipos estrictos no nulos, T básicamente debería significar: desactivar todo tipo de errores nulos en este tipo. Intentar forzarlo a T? aún puede crear problemas de compatibilidad.

El problema es que hoy en día algunos T son en realidad no anulables y algunos sí lo son. Considerar:

// In a strict module, function len does not accept nulls
function len(x: string): number { return x.length; }
// In a legacy module, some calls to len
let abc: string = "abc";
len(abc);

Si aplica string alias de string? en el módulo heredado, la llamada se convierte en un error porque pasa una variable posiblemente nula a un parámetro que no acepta valores NULL.

@ jods4 Leer mi comentario de nuevo. Mira la mesa. No sé cómo expresarlo con más claridad.

Su ejemplo se diseñó explícitamente para llegar a su conclusión poniendo la definición de foo y la asignación a foo lado de la otra. Con noImplicitAny , los únicos errores que resultan son específicamente del código que necesita cambiar (porque no ha cambiado su significado, solo ha requerido que se exprese de manera más explícita). Con noImplicitNull , el código que causó el error fue la _asignación_ a foo pero el código que necesitaba cambiar para solucionarlo (para tener el significado anterior) era la _definición_ de foo . Esto es de suma importancia, porque la bandera ha _cambiado el significado de la definición de foo _. La asignación y la definición obviamente pueden estar en lados diferentes del límite de una biblioteca, para lo cual la bandera noImplicitNull ha cambiado el _significado_ de la interfaz pública de esa biblioteca.

la bandera cambió el significado de la definición de foo.

Si eso es verdad. Cambió de "No tengo la menor idea de si la variable puede ser nula o no, y simplemente no me importa" a "Esta variable no tiene nulos". Existe una probabilidad del 50/50 de que fue correcto y, si no lo es, debe precisar su intención en la declaración. Al final, el resultado es el mismo que con noImplicitAny : debes hacer tu intención más explícita en la declaración.

La asignación y la definición obviamente pueden estar en lados diferentes del límite de una biblioteca.

De hecho, normalmente la declaración en la biblioteca y el uso en mi código. Ahora:

  1. Si la biblioteca ha optado por nulos estrictos, debe declarar sus tipos correctamente. Si la biblioteca dice que tiene tipos nulos estrictos y x no admite nulos, entonces intentar asignar un nulo _es de hecho un error que debería informarse_.
  2. Si la biblioteca no ha optado por nulos estrictos, el compilador no debería generar ningún error para su uso.

Esta bandera (al igual que noImplicitAny ) no se puede activar sin ajustar su código.

Entiendo su punto, pero diría que no _cambiamos_ el código de significado; más bien _expresamos un significado que no está capturado_ por el sistema de tipos de hoy.

Esto no solo es bueno porque detectará errores en el código actual, sino que diría que sin dar ese paso nunca habrá tipos no nulos utilizables en TS.

¡Buenas noticias para los tipos que no aceptan valores NULL! ¡Parece que el equipo de TS está bien con la introducción de cambios importantes en las actualizaciones de TS!
Si ves esto:
http://blogs.msdn.com/b/typescript/archive/2015/09/02/announcing-typescript-1-6-beta-react-jsx-better-error-checking-and-more.aspx
Introducen un cambio rotundo (sintaxis opcional mutuamente excluyente) con un nuevo tipo de archivo, e introducen un cambio rotundo _sin_ el nuevo tipo de archivo (afecta a todos).
Ese es un precedente con el que podemos discutir tipos que no aceptan valores NULL (por ejemplo, una extensión .sts o estricta de Typecript y los .sdts correspondientes).

Ahora solo necesitamos averiguar si queremos que el compilador intente buscar tipos indefinidos o solo tipos nulos (y qué sintaxis) y tenemos una propuesta sólida.

@jbondc Muy interesante lectura. Feliz de ver que mi intuición acerca de que la migración a NNBD (no anulable por defecto) es más fácil que la migración a opcional no anulable ha sido confirmada por estudios (un orden de magnitud menos cambios para migrar, y en el caso de Dart, 1- 2 anotaciones por cada 1000 líneas de código necesitaban cambios de nulidad, no más de 10 incluso en código con mucho nulo)

No estoy seguro de si la complejidad del documento realmente refleja la complejidad de los tipos que no aceptan valores NULL. Por ejemplo, en la sección de genéricos analizan 3 tipos de parámetros de tipo formal y luego muestran que en realidad no los necesita. En TS, null sería simplemente el tipo de registro completamente vacío (sin propiedades, sin métodos) mientras que {} sería el tipo raíz no nulo, y luego los genéricos que no aceptan nulos son simplemente G<T extends {}> - no es necesario discutir varios tipos de parámetros de tipo formal en absoluto.

Además parece que proponen una gran cantidad de azúcar no esencial, como var !x

Sin embargo, el estudio de los idiomas existentes que se han ocupado del mismo problema es la verdadera joya.

Al leer el documento, me di cuenta de que los tipos Optional / Maybe son más poderosos que los tipos que aceptan valores NULL, especialmente en un contexto genérico, principalmente debido a la capacidad de codificar Just(Nothing) . Por ejemplo, si tenemos una interfaz de mapa genérica que contiene valores de tipo T y admite get que puede o no devolver un valor dependiendo de la presencia de una clave:

interface Map<T> {
  get(s:string):Maybe<T>
}

no hay nada que impida que T sea ​​del tipo Maybe<U> ; el código funcionará perfectamente bien y devolverá Just(Nothing) si una clave está presente pero contiene un Nothing , y simplemente devolverá Nothing si la clave no está presente en absoluto.

Por el contrario, si usamos tipos que aceptan valores NULL

interface Map<T> {
  get(s:string):T?
}

entonces es imposible distinguir entre una clave faltante y un valor nulo cuando T es anulable.

De cualquier manera, la capacidad de diferenciar valores que aceptan valores NULL de los que no aceptan valores NULL y modelar los métodos y propiedades disponibles en consecuencia es un requisito previo para cualquier tipo de seguridad de tipos.

@jbondc Este es un hallazgo muy

Me reconforta que los estudios muestren que el 80% de las declaraciones en realidad no son nulas o que solo hay 20 anotaciones de nulidad por KLOC (p. 21). Como se señaló en el documento, este es un argumento sólido para que no sea nulo de forma predeterminada, lo que también fue mi sentimiento.

Otro argumento a favor de no nulo es que crea un sistema de tipos más limpio: null es su propio tipo, T no es nulo y T? es sinónimo de T | null . Debido a que TS ya tiene un tipo de unión, todo es agradable, limpio y funciona bien.

Al ver la lista de lenguajes recientes que abordaron ese problema, realmente creo que un nuevo lenguaje de programación moderno debería manejar este problema de larga data y reducir los errores nulos en las bases de código. Este es un problema demasiado común para algo que debería modelarse en el sistema de tipos. Todavía espero que TS lo consiga algún día.

Encontré la idea del operador T! intrigante y posiblemente útil. Estaba pensando en un sistema donde T es un tipo no nulo, T? es T | null . Pero me molestó que no pudieras crear una API genérica que garantizara un resultado no nulo incluso frente a una entrada nula. No tengo buenos casos de uso, pero en teoría no podría modelar esto fielmente: function defined(x) { return x || false; } .
Usando el operador de inversión no nulo, se podría hacer: function defined<T>(x: T): T! | boolean . Lo que significa que si defined devuelve un T , se garantiza que no será nulo, incluso si la restricción genérica T fuera anulable, digamos string? . Y no creo que sea difícil de modelar en TS: dado T! , si T es un tipo de unión que incluye null tipo, devuelve el tipo resultante de eliminar null del sindicato.

@espion

Al leer el documento, me di cuenta de que los tipos Opcionales / Quizás son más poderosos que los tipos que aceptan valores NULL

Puede anidar Maybe estructuras, no se pueden anidar null , por cierto.

Esta es una discusión interesante en el contexto de la definición de nuevas apis, pero al mapear apis existentes, hay pocas opciones. Hacer que el mapa de idioma sea nulo a Maybe no aprovechará ese beneficio, a menos que la función se reescriba por completo.

Maybe codifica dos piezas distintas de información: si hay un valor y cuál es el valor. Tomando su ejemplo Map y mirando C #, esto es obvio, desde Dictionary<T,K> :
bool TryGet(K key, out T value) .
Tenga en cuenta que si C # tiene tuplas (tal vez C # 7), esto es básicamente lo mismo que:
(bool hasKey, T value) TryGet(K key)
Que es básicamente un Maybe y permite almacenar null .

Tenga en cuenta que JS tiene su propia forma de lidiar con este problema y crea muchos problemas nuevos e interesantes: undefined . Un JS Map típico devolvería undefined si no se encuentra la clave, su valor en caso contrario, incluido null .

Propuesta relacionada para C # 7 - https://github.com/dotnet/roslyn/issues/5032

¿Se dan cuenta de que el problema no se resuelve a menos que modelen indefinido de la misma manera?
De lo contrario, todos sus problemas nulos serán reemplazados por problemas indefinidos (que en mi opinión son más frecuentes de todos modos).

@Griffork

todos sus problemas nulos serán reemplazados por problemas indefinidos

No, ¿por qué iban a hacerlo?
Mis problemas nulos desaparecerán y mis problemas indefinidos permanecerán.

Es cierto que undefined sigue siendo un problema. Pero eso depende mucho de tu estilo de codificación. Codifico casi sin undefined excepto cuando el navegador me los obliga, lo que significa que el 90% de mi código sería más seguro con verificaciones nulas.

Codifico casi sin indefinidos, excepto cuando el navegador me los obliga

Hubiera pensado que JavaScript fuerza undefined en uno en cada turno.

  • Variables no inicializadas. let x; alert(x);
  • Argumentos de función omitidos. let foo = (a?) => alert(a); foo();
  • Accediendo a elementos de matriz inexistentes. let x = []; alert(x[0]);
  • Acceder a propiedades de objetos inexistentes. let x = {}; alert(x['foo']);

Nulo, por otro lado, ocurre en menos situaciones y más predecibles:

  • Acceso DOM. alert(document.getElementById('nonExistent'));
  • Respuestas de servicios web de terceros (desde JSON.stringify tiras undefined ). { name: "Joe", address: null }
  • Regex.

Por esta razón, prohibimos el uso de null , convertimos todos los null recibidos por transferencia a undefined , y siempre usamos una verificación estricta de igualdad para undefined . Esto nos ha funcionado bien en la práctica.

En consecuencia, estoy de acuerdo en que el problema de undefined es el más frecuente.

@NoelAbrahams Estilo de codificación, te lo digo :)

Variables no inicializadas

Siempre inicializo variables y tengo noImplicitAny activado, por lo que let x sería un error de todos modos. El más cercano que usaría en mi proyecto es let x: any = null , aunque ese es un código que no escribiría a menudo.

Parámetros de función opcionales

Utilizo valores de parámetros predeterminados para parámetros opcionales, me parece que tiene más sentido (su código _will_ leerá y usará el parámetro de alguna manera, ¿no es así?). Entonces, para mí: function f(name?: string = null, options?: any = {}) .
Acceder al valor del parámetro undefined sin procesar sería un caso _excepcional_ para mí.

Accediendo a elementos de matriz inexistentes

Esto es algo que me esfuerzo por no hacer en mi código. Verifico la longitud de mis matrices para que no se salga de los límites y no utilizo matrices dispersas (o trato de llenar espacios vacíos con valores predeterminados como null , 0 , ...).
Una vez más, puede que se le ocurra un caso especial en el que haría eso, pero eso sería una _ excepción_, no la regla.

Acceder a propiedades de objetos inexistentes.

Casi lo mismo que para las matrices. Mis objetos están escritos, si un valor no está disponible, lo configuro en null , no undefined . Una vez más, puede encontrar casos extremos (como hacer una prueba de diccionario) pero son _ casos extremos_ y no representativos de mi estilo de codificación.

En todos los casos _excepcionales_ en los que obtengo una devolución de undefined , inmediatamente actúo sobre el resultado undefined y no lo propago más ni "trabajo" con él. Ejemplo típico del mundo real en un compilador de TS ficticio con comprobaciones nulas:

let cats: Cat[];
// Note that find returns undefined if there's no cat named Kitty
let myFavoriteCat = cats.find(c => c.name === 'Kitty'); 
if (myFavoriteCat === undefined) {
  // Immediately do something to compensate here:
  // return false; or 
  // myFavoriteCat = new Cat('Kitty'); or
  // whatever makes sense.
}
// Continue with assurance that myFavoriteCat is not null (it was an array of non-nullable cats after all).

Por esta razón, prohibimos el uso de nulos, convertimos todos los nulos recibidos a través del cable a indefinidos y siempre usamos una comprobación de igualdad estricta para indefinidos. Esto nos ha funcionado bien en la práctica.

Por esto, entiendo que usa un estilo de codificación muy diferente al mío. Si básicamente usa undefined todas partes, entonces sí, se beneficiará de los tipos nulos comprobados estáticamente mucho menos que yo
haría.

Sí, la cosa no es 100% hermética debido a undefined y no creo que se pueda crear un lenguaje razonablemente utilizable que sea 100% correcto a este respecto. JS introduce undefined en demasiados lugares.

Pero como espero que pueda deducir de mis respuestas anteriores, con el estilo de codificación apropiado, hay muchas ventajas para beneficiarse de las verificaciones nulas. Al menos mi opinión es que en mi base de código ayudaría a encontrar y prevenir muchos errores estúpidos y mejoraría la productividad en el entorno de mi equipo.

@ jods4 , fue interesante leer su enfoque.

Creo que la objeción que tengo a ese enfoque es que parece haber muchas reglas que deben cumplirse; es más bien como el comunismo frente al libre mercado: sonríe:

El equipo de TS tiene internamente

@NoelAbrahams "Usar undefined " es tanto una regla como "Usar null ".

En cualquier caso, la coherencia es clave y no me gustaría un proyecto en el que no estoy seguro de si se supone que las cosas son null o undefined (o una cadena vacía o cero). Especialmente porque TS actualmente no ayuda con este problema ...

Sé que TS tiene una regla para favorecer undefined sobre null , tengo curiosidad por saber si esta es una elección arbitraria "en aras de la coherencia" o si hay más argumentos detrás de la elección.

Por qué me gusta usar null lugar de undefined :

  1. Funciona de una manera familiar para nuestros desarrolladores, muchos provienen de lenguajes OO estáticos como C #.
  2. Las variables no inicializadas generalmente se consideran un olor a código en muchos lenguajes, no estoy seguro de por qué debería ser diferente en JS. Deja clara tu intención.
  3. Aunque JS es un lenguaje dinámico, el rendimiento es mejor con tipos estáticos que no cambian. Es más eficiente establecer una propiedad en null que delete it.
  4. Admite la clara diferencia entre null que está definido pero significa la ausencia de valor, y undefined , que es ... indefinido. Un lugar donde la diferencia entre los dos es obvia: parámetros opcionales. No pasar un parámetro da como resultado undefined . ¿Cómo _pasar_ el valor vacío si usa undefined para eso en su código base? Usando null no hay problema aquí.
  5. Establece una ruta limpia hacia la verificación nula, como se explica en este hilo, que no es realmente práctico con undefined . Aunque tal vez esté soñando despierto con este.
  6. Tienes que hacer una elección por coherencia, en mi humilde opinión null es tan bueno como undefined .

Creo que la razón para preferir undefined a null se debe a
argumentos predeterminados y coherencia con obj.nonexistentProp regresando
undefined .

Aparte de eso, no entiendo que el bikeshedding sobre lo que cuenta como nulo
suficiente para requerir que la variable sea anulable.

El martes, 8 de septiembre de 2015, 06:48 jods [email protected] escribió:

@NoelAbrahams https://github.com/NoelAbrahams "Usar indefinido" es como
una regla como "Usar nulo".

En cualquier caso, la coherencia es clave y no me gustaría un proyecto en el que estoy.
No estoy seguro de si se supone que las cosas son nulas o indefinidas (o un vacío
cadena o cero). Especialmente porque TS actualmente no ayuda con esto.
asunto...

Sé que TS tiene una regla para favorecer lo indefinido sobre nulo, tengo curiosidad por saber si esto
es una elección arbitraria "en aras de la coherencia" o si hay más
argumentos detrás de la elección.

Por qué _I_ me gusta usar nulo en lugar de indefinido:

  1. Funciona de una manera familiar para nuestros desarrolladores, muchos provienen de OO estático
    lenguajes como C #.
  2. Las variables no inicializadas se suelen considerar como un olor a código en
    muchos idiomas, no estoy seguro de por qué debería ser diferente en JS. Haz tu
    intención clara.
  3. Aunque JS es un lenguaje dinámico, el rendimiento es mejor con
    tipos estáticos que no cambian. Es más eficiente establecer una propiedad en
    nulo que eliminarlo.
  4. Admite la diferencia limpia entre nulo que está definido pero
    significa la ausencia de valor, e indefinido, que es ... indefinido.
    Un lugar donde la diferencia entre los dos es obvia: opcional
    parámetros. No pasar un parámetro da como resultado indefinido. Cómo
    _pass_ el valor vacío si usa undefined para eso en su código
    ¿base? Usar nulo no hay problema aquí.
  5. Establece un camino limpio hacia la verificación nula, como se explica en este
    hilo, que no es realmente práctico con undefined. Aunque tal vez
    Estoy soñando despierto con este.
  6. Tienes que hacer una elección por coherencia, en mi humilde opinión, null es tan bueno como
    indefinido.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -138514437
.

@impinball
¿Podemos dejar de usar "bikeshedding" en cada discusión de github? Podemos decir con seguridad que usar undefined o null es una preferencia del equipo; pero la cuestión de si intentar incluir undefined en las comprobaciones nulas (o no) y cómo funcionaría no es trivial. ¿Así que no entiendo cómo es eso de deshacerse de las bicicletas en primer lugar?

He construido una bifurcación de TS 1.5 con tipos que no aceptan valores NULL y fue sorprendentemente fácil. Pero creo que hay dos cuestiones difíciles que necesitan un consenso para tener tipos no anulables en el compilador TS oficial, ambos se han discutido en detalle anteriormente sin una conclusión clara:

  1. ¿Qué hacemos con undefined ? (mi opinión: todavía está en todas partes y sin marcar)
  2. ¿Cómo manejamos la compatibilidad con el código existente, en particular las definiciones? (Mi opinión: marca de suscripción, al menos por archivo de definición. Activar la marca es "romper" porque es posible que tenga que agregar anotaciones nulas).

Mi opinión personal es que se debe tratar nulo e indefinido
de forma equivalente a efectos de nulabilidad. Ambos se utilizan para ese caso de uso,
que representa la ausencia de un valor. Uno es que el valor nunca existió,
y el otro es que el valor una vez existió y ya no existe. Ambos
debe contar para la nulabilidad. La mayoría de las funciones de JS devuelven undefined, pero muchas
Las funciones DOM y de biblioteca devuelven nulo. Ambos sirven para el mismo caso de uso. Por lo tanto,
deben tratarse de manera equivalente.

La referencia de bikeshedding se refería a los argumentos de estilo de código sobre los que
debe usarse para representar la ausencia de un valor. Algunos están argumentando por
simplemente nulo, algunos defienden simplemente indefinido, y algunos defienden un
mezcla. Esta propuesta no debería limitarse a solo una de ellas.

El martes, 8 de septiembre de 2015, 13:28 jods [email protected] escribió:

@impinball https://github.com/impinball
¿Podemos dejar de usar "bikeshedding" en cada discusión de github? Podemos con seguridad
decir que el uso de undefined o null es una preferencia del equipo; pero el problema
si intentar incluir undefined en las comprobaciones nulas (o no) y cómo
Funcionaría no es trivial. Así que no entiendo cómo eso es derramar bicicletas en el
¿primer lugar?

He construido una bifurcación de TS 1.5 con tipos que no aceptan nulos y fue
sorprendentemente fácil. Pero creo que hay dos cuestiones difíciles que
necesita un consenso para tener tipos no anulables en el compilador TS oficial,
Ambos se han discutido en detalle anteriormente sin una conclusión clara:

  1. ¿Qué hacemos con undefined? (mi opinión: todavía está en todas partes
    y sin marcar)
  2. ¿Cómo manejamos la compatibilidad con el código existente, en particular?
    definiciones? (mi opinión: marca de inclusión voluntaria, al menos por archivo de definición.
    Encender la bandera es "romper" porque es posible que deba agregar un valor nulo
    anotaciones.)

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -138641395
.

@ jods4 De su punto 2,

¿Puedes vincularlo por favor? Me gustaría probarlo.

@impinball
Me encantaría ver (algo) de seguridad contra undefined también, pero es bastante generalizado.

En particular, ¿podemos definir una matriz de tipos que no aceptan valores NULL?
Dado que un acceso a la matriz fuera de los límites (o escaso) devuelve undefined , ¿podemos definir y usar matrices convenientemente?
Creo que exigir que todas las matrices sean anulables es una carga excesiva en la práctica.

¿Deberíamos diferenciar los tipos null y undefined ? Eso no es difícil: T | null , T | undefined , T | null | undefined y puede proporcionar una respuesta fácil a la pregunta anterior. Pero entonces, ¿qué pasa con la sintaxis taquigráfica? ¿Qué significa T? ? ¿Ambos nulos e indefinidos? ¿Necesitamos dos abreviaturas diferentes?

@Arnavion
Los tipos nulos e indefinidos ya existen en TS.
Mi opinión fue:

  1. Hacer que todos los tipos no admitan nulos (incluidos los tipos inferidos);
  2. Asigne al tipo nulo un nombre ( null ) que pueda usar en declaraciones de tipo;
  3. Elimine el ensanchamiento de null a any ;
  4. Introducir la abreviatura sintáctica T? que es lo mismo que T | null ;
  5. Elimine las conversiones implícitas de null a cualquier otro tipo.

Sin acceso a mis fuentes, creo que eso es lo esencial. El tipo nulo existente y el maravilloso sistema de tipo TS (especialmente los tipos de unión y los protectores de tipo) hacen el resto.

Todavía no he comprometido mi trabajo en github, por lo que no puedo compartir un enlace por ahora. Primero quería codificar el interruptor de compatibilidad, pero he estado muy ocupado con otras cosas :(
El cambio de compatibilidad es mucho más complicado <_ <. Pero es importante porque en este momento el compilador de TS se compila a sí mismo con muchos errores y fallan muchas pruebas existentes.
Pero parece funcionar muy bien en un código completamente nuevo.

Permítanme resumir lo que he visto de algunos comentaristas hasta ahora con la esperanza de poder ilustrar cómo esta conversación se desarrolla en círculos:
Problema: Ciertos valores de 'casos especiales' están encontrando su código que no está diseñado para tratar con ellos porque se tratan de manera diferente a cualquier otro tipo en el lenguaje (es decir, nulo e indefinido).
Yo: ¿por qué no establece estándares de programación para que esos problemas no sucedan?
Otro: porque sería bueno si la intención pudiera reflejarse en el tipeo, porque entonces no será documentado de manera diferente por cada equipo que trabaje en el tipo de script, y se producirán menos suposiciones falsas sobre bibliotecas de terceros.
Everone: ¿Cómo vamos a abordar este problema?
Otro: ¡hagamos nulos más estrictos y usemos estándares para lidiar con indefinidos!

¡No veo cómo esto puede considerarse una solución cuando indefinido es mucho más un problema que nulo!

Descargo de responsabilidad: esto no refleja las actitudes de todos aquí, es solo que ha surgido lo suficiente como para que quisiera resaltar esto.
La única forma en que esto será aceptado es si es una solución a un problema, no una pequeña solución a la parte más pequeña del problema.

¡Maldito teléfono!
*todos

* ha surgido lo suficiente como para que quisiera resaltarlo.

Además, el último párrafo debe decir "la única forma en que esta propuesta"

@ jods4 Yo diría que dependería de ciertas construcciones. En algunos casos, puede garantizar la no nulabilidad en esos casos, como los siguientes:

declare const list: T![];

for (const entry of list) {
  // `entry` is clearly immutable here.
}

list.forEach(entry => {
  // `entry` is clearly immutable here.
})

list.map(entry => {
  // `entry` is clearly immutable here.
})

En este caso, el compilador tendría que tener mucha lógica para asegurarse de que la matriz esté marcada dentro de los límites:

declare const list: T![]

for (let i = 0; i < list.length; i++) {
  // This could potentially fail if the compiler doesn't correctly do the static bounds check.
  const entry: T![] = list[i];
}

Y en este caso, no hay forma de que pueda garantizar que el compilador pueda verificar que el acceso para obtener entry está dentro de los límites sin evaluar realmente partes del código:

declare const list: T![]

const end = round(max, list.length);

for (let i = 0; i < end; i++) {
  const entry: T![] = list[i];
}

Hay algunos fáciles y obvios, pero hay algunos más difíciles.

@impinball De hecho, las API modernas como map , forEach o for..of están bien porque omiten elementos que nunca se inicializaron o eliminaron. (Incluyen elementos que se han establecido en undefined , pero nuestro TS hipotético seguro para nulos lo prohibiría).

Pero el acceso a la matriz clásica es un escenario importante y me gustaría ver una buena solución para eso. Es evidente que no es posible realizar un análisis complejo como sugirió, excepto en casos triviales (pero casos importantes, ya que son comunes). Pero tenga en cuenta que incluso si pudiera probar que i < array.length , no prueba que el elemento esté inicializado.

Considere el siguiente ejemplo, ¿qué cree que debería hacer TS?

let array: T![] = [];  // an empty array of non-null, non-undefined T
// blah blah
array[4] = new T();  // Fine for array[4], but it means array[0..3] are undefined, is that OK?
// blah blah
let i = 2;
// Note that we could have an array bounds guard
if (i < array.length) {
  let t = array[i];  // Inferred type would be T!, but this is actually undefined :(
}

También está el problema con Object.defineProperty.

let array = new Array(5)
Object.defineProperty(array, "length", 2, {
  get() { return 10 },
})

El miércoles, 9 de septiembre de 2015, a las 17:49 jods [email protected] escribió:

@impinball https://github.com/impinball De hecho, una API moderna como mapa,
forEach o for..of están bien porque omiten elementos que nunca fueron
inicializado o eliminado. (Incluyen elementos que se han configurado para
indefinido, pero nuestro hipotético TS nulo seguro lo prohibiría).

Pero el acceso a la matriz clásica es un escenario importante y me gustaría
vea una buena solución para eso. Hacer un análisis complejo como sugirió es
claramente no es posible excepto en casos triviales (pero casos importantes desde
son comunes). Pero tenga en cuenta que incluso si pudiera probar que yo <
array.length no prueba que el elemento esté inicializado.

Considere el siguiente ejemplo, ¿qué cree que debería hacer TS?

let matriz: T! [] = []; // una matriz vacía de T no nulo, no indefinido // bla, bla
matriz [4] = nueva T (); // Está bien para la matriz [4], pero significa que la matriz [0..3] no está definida, ¿está bien? // bla blahlet i = 2; // Tenga en cuenta que podríamos tener una matriz de límites guardif (i <array. longitud) {
sea ​​t = matriz [i]; // ¡El tipo inferido sería T !, pero en realidad no está definido :(
}

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -139055786
.

Todo lo que necesitas hacer es:

  1. Haga referencia a los tipos nulo e indefinido.
  2. Evite que los valores nulos e indefinidos se puedan asignar a cualquier cosa.
  3. Utilice un tipo de unión con Null y / o Undefined donde la nulabilidad esté implícita.

De hecho, es un cambio rotundo y audaz y parece más adecuado para un idioma.
extensión.
El 10 de septiembre de 2015 a las 9:13 a. M., "Isiah Meadows" [email protected] escribió:

También está el problema con Object.defineProperty.

let array = new Array(5)
Object.defineProperty(array, "length", 2, {
get() { return 10 },
})

El miércoles, 9 de septiembre de 2015, a las 17:49 jods [email protected] escribió:

@impinball https://github.com/impinball De hecho, una API moderna como
mapa,
forEach o for..of están bien porque omiten elementos que nunca fueron
inicializado o eliminado. (Incluyen elementos que se han configurado para
indefinido, pero nuestro hipotético TS nulo seguro lo prohibiría).

Pero el acceso a la matriz clásica es un escenario importante y me gustaría
vea una buena solución para eso. Hacer un análisis complejo como sugirió es
claramente no es posible excepto en casos triviales (pero casos importantes desde
son comunes). Pero tenga en cuenta que incluso si pudiera probar que yo <
array.length no prueba que el elemento esté inicializado.

Considere el siguiente ejemplo, ¿qué cree que debería hacer TS?

let matriz: T! [] = []; // una matriz vacía de T no nula ni indefinida //
bla bla
matriz [4] = nueva T (); // Está bien para la matriz [4], pero significa que la matriz [0..3] es
indefinido, ¿está bien? // blah blahlet i = 2; // Tenga en cuenta que podríamos tener un
límites de matriz guardif (i <matriz.longitud) {
sea ​​t = matriz [i]; // ¡El tipo inferido sería T !, pero en realidad es
indefinido :(
}

-
Responda a este correo electrónico directamente o véalo en GitHub
<
https://github.com/Microsoft/TypeScript/issues/185#issuecomment-139055786>
.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -139230568
.

@impinball
Me siento bien con tu ejemplo. Usar defineProperty de esta manera es salir de la caja de seguridad de TS y entrar en el reino dinámico de JS, ¿no crees? No creo que haya llamado nunca defineProperty directamente en el código TS.

@ aleksey-bykov

parece más adecuado para una extensión de idioma.

Otro problema con esta propuesta es que, a menos que sea ampliamente aceptada, tiene menos valor.
Eventualmente, necesitamos definiciones de TS actualizadas y no creo que suceda si se trata de una extensión o bifurcación de TS que se usa poco, es incompatible.

Sé que las matrices escritas tienen garantía, porque IIRC arrojan una
ReferenceError on fuera de los límites de carga y almacenamiento de matrices. Matrices regulares y
Los objetos de argumentos devuelven indefinidos cuando el índice está fuera de los límites. En mi
opinión, eso es un defecto del lenguaje JS, pero arreglarlo definitivamente
romper la Web.

La única forma de "arreglar" esto es en ES6, a través de un constructor que devuelve un proxy,
y su prototipo de instancia y autoprototipo establecido en el Array original
constructor. Algo como esto:

Array = (function (A) {
  "use strict";
  function check(target, prop) {
    const i = +prop;
    if (prop != i) return target[prop];
    if (i >= target.length) {
      throw new ReferenceError();
    }
    return i;
  }

  function Array(...args) {
    return new Proxy(new Array(...args), {
      get(target, prop) {
        return target[check(target, prop)];
      },
      set(target, prop, value) {
        return target[check(target, prop)] = value;
      },
    });
  }

  Array.prototype = A.prototype;
  Array.prototype.constructor = Array
  Object.setPrototypeOf(Array, A);
  return Array;
})(Array);

(nota: no está probado, escrito en un teléfono ...)

El jueves 10 de septiembre de 2015 a las 10:09 jods [email protected] escribió:

@impinball https://github.com/impinball
Me siento bien con tu ejemplo. Usar defineProperty de esta manera es
salir de la caja de seguridad de TS y entrar en el reino dinámico de JS, no
¿Crees? No creo que haya llamado a defineProperty directamente en el código TS.

@ aleksey-bykov https://github.com/aleksey-bykov

parece más adecuado para una extensión de idioma.

Otro problema con esta propuesta es que, a menos que sea ampliamente aceptada,
tiene menos valor.
Eventualmente, necesitamos definiciones de TS actualizadas y no creo que lo haga
sucederá si se trata de una extensión o bifurcación incompatible, poco utilizada, de TS.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -139245706
.

@impinball No estoy seguro de que haya algo que "arreglar" en primer lugar ...
Esa es la semántica de JS y TS debe adaptarse a ellos de alguna manera.

¿Qué pasa si solo tenemos una marca (ligeramente diferente) para declarar una matriz dispersa frente a una matriz no dispersa, y hacemos que la no dispersa se inicialice automáticamente (tal vez los programadores puedan proporcionar un valor o ecuación para la inicialización)? De esa manera, podemos forzar que los arreglos dispersos sean del tipo T|undefined (que cambiaría al tipo T usando for ... of y otras operaciones 'seguras') y dejar los tipos de arreglos no dispersos en paz.

//not-sparse
var array = [arrlength] => index*3;
var array = <number[]>[3];
//sparse
var array = [];

Obviamente, esta no es la sintaxis final.
El segundo ejemplo inicializaría cada valor al valor predeterminado del compilador para ese tipo.
Esto significa que para las matrices no dispersas debe escribirlas; de lo contrario, sospecho que tendría que convertirlas en algo después de que se hayan inicializado por completo.
También deberíamos necesitar un tipo no disperso para matrices para que los programadores puedan convertir sus matrices en no dispersas.

@Griffork
No lo sé ... se vuelve confuso.

De esa manera, podemos forzar que las matrices dispersas sean del tipo T|undefined (que cambiaría al tipo T usando for ... of y otras operaciones 'seguras')

Debido a una peculiaridad en JS, no funciona de esa manera. Suponga let arr: (T|undefined)[] .
Entonces soy libre de hacer: arr[0] = undefined .
Si hago eso, usando esas funciones "seguras" _will_ devolverá undefined para la primera ranura. Entonces, en arr.forEach(x => ...) no se puede decir que x: T . Todavía tiene que ser x: T|undefined .

El segundo ejemplo inicializaría cada valor al valor predeterminado del compilador para ese tipo.

Esto no es muy parecido a TS en espíritu. Tal vez me equivoque, pero me parece que la filosofía de TS es que los tipos son solo una capa adicional sobre JS y no afectan al codegen. Esto tiene implicaciones de rendimiento en aras de la corrección de tipo parcial que no me gusta del todo.

TS obviamente no puede protegerlo de todo en JS y hay varias funciones / construcciones que puede llamar desde TS válidas, que tienen efectos dinámicos en el tipo de tiempo de ejecución de sus objetos y rompen el análisis de tipo TS estático.

¿Sería malo si esto fuera un agujero en el sistema de tipos? Quiero decir, este código realmente no es común: let x: number[] = []; x[3] = 0; y si ese es el tipo de cosas que desea hacer, entonces tal vez debería declarar su matriz let x: number?[] .

No es perfecto, pero creo que es lo suficientemente bueno para la mayoría de los usos del mundo real. Si es un purista que quiere un sistema de tipo de sonido, entonces debería buscar en otro idioma, porque el sistema de tipo TS no es de todos modos. ¿Qué piensas?

Es por eso que dije que también necesita la capacidad de lanzar a una matriz no dispersa
escriba, para que pueda inicializar una matriz usted mismo sin el rendimiento
impacto.
Me conformaría con una diferenciación entre (lo que se supone que es) escaso
matrices y matrices no dispersas por tipo.

Para aquellos que no saben por qué esto es importante, es la misma razón por la que
querría una diferencia entre T y T|null .

El viernes 11 de septiembre de 2015 a las 9:11 a. M., Jods [email protected] escribió:

@Griffork https://github.com/Griffork
No lo sé ... se vuelve confuso.

De esa manera podemos forzar matrices dispersas a ser de tipo T | indefinido (lo que
cambiar a tipo T usando para ... de y otras operaciones 'seguras')

Debido a una peculiaridad en JS, no funciona de esa manera. Supongamos que arr:
(T | indefinido) [].
Entonces soy libre de hacer: arr [0] = undefined.
Si hago eso, entonces usando esas funciones "seguras" _will_ return undefined
para la primera ranura. Entonces, en arr.forEach (x => ...) no puede decir que x: T.
Todavía tiene que ser x: T | undefined.

El segundo ejemplo inicializaría cada valor en el compilador predeterminado
para ese tipo.

Esto no es muy parecido a TS en espíritu. Tal vez me equivoque pero me parece
que la filosofía de TS es que los tipos son solo una capa adicional sobre JS
y no afectan al codegen. Esto tiene implicaciones de rendimiento por el bien de
corrección de tipo parcial que no me gusta del todo.

TS obviamente no puede protegerlo de todo en JS y hay
varias funciones / construcciones que puede llamar desde TS válido, que tienen
efectos dinámicos en el tipo de tiempo de ejecución de sus objetos y romper TS estático
análisis de tipo.

¿Sería malo si esto fuera un agujero en el sistema de tipos? Quiero decir, este código
realmente no es común: sea x: número [] = []; x [3] = 0; y si ese es el
tipo de cosas que desea hacer, entonces tal vez debería declarar su matriz let
x: ¿número? [].

No es perfecto, pero creo que es lo suficientemente bueno para la mayoría de los usos del mundo real.
Si es un purista que quiere un sistema de tipo de sonido, entonces debería
mire otro idioma, porque el sistema de tipo TS no suena de todos modos. Qué
¿Tu crees?

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -139408240
.

@ jods4 Lo que quise decir con "arreglar" fue "arreglar" lo que en mi humilde opinión es un defecto de diseño del lenguaje JS. No en TypeScript, sino en _JavaScript en sí_.

@jods Me quejo de JS, no de TS. Admito que está fuera de tema.

El jueves 10 de septiembre de 2015 a las 19:19, Griffork [email protected] escribió:

Es por eso que dije que también necesita la capacidad de lanzar a una matriz no dispersa
escriba, para que pueda inicializar una matriz usted mismo sin el rendimiento
impacto.
Me conformaría con una diferenciación entre (lo que se supone que es) escaso
matrices y matrices no dispersas por tipo.

Para aquellos que no saben por qué esto es importante, es la misma razón por la que
querría una diferencia entre T y T|null .

El viernes 11 de septiembre de 2015 a las 9:11 a. M., Jods [email protected] escribió:

@Griffork https://github.com/Griffork
No lo sé ... se vuelve confuso.

De esa manera podemos forzar matrices dispersas a ser de tipo T | indefinido (lo que
cambiar a tipo T usando para ... de y otras operaciones 'seguras')

Debido a una peculiaridad en JS, no funciona de esa manera. Supongamos que arr:
(T | indefinido) [].
Entonces soy libre de hacer: arr [0] = undefined.
Si hago eso, entonces usando esas funciones "seguras" _will_ return undefined
para la primera ranura. Entonces, en arr.forEach (x => ...) no puede decir que x: T.
Todavía tiene que ser x: T | undefined.

El segundo ejemplo inicializaría cada valor en el compilador predeterminado
para ese tipo.

Esto no es muy parecido a TS en espíritu. Tal vez me equivoque pero me parece
que la filosofía de TS es que los tipos son solo una capa adicional por encima de
JS
y no afectan al codegen. Esto tiene implicaciones de rendimiento por el bien de
corrección de tipo parcial que no me gusta del todo.

TS obviamente no puede protegerlo de todo en JS y hay
varias funciones / construcciones que puede llamar desde TS válido, que
tener
efectos dinámicos en el tipo de tiempo de ejecución de sus objetos y romper TS estático
análisis de tipo.

¿Sería malo si esto fuera un agujero en el sistema de tipos? Quiero decir, este código
realmente no es común: sea x: número [] = []; x [3] = 0; y si ese es el
tipo de cosas que desea hacer, entonces tal vez debería declarar su matriz
dejar
x: ¿número? [].

No es perfecto, pero creo que es lo suficientemente bueno para la mayoría de los usos del mundo real.
Si eres un purista que quiere un sistema de tipos de sonido, entonces deberías
ciertamente
mire otro idioma, porque el sistema de tipo TS no suena de todos modos.
Qué
¿Tu crees?

-
Responda a este correo electrónico directamente o véalo en GitHub
<
https://github.com/Microsoft/TypeScript/issues/185#issuecomment-139408240>
.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -139409349
.

Y en cuanto a mi declaración con las longitudes de la matriz, podríamos operar asumiendo que todos los accesos a la matriz están dentro de los límites y que el acceso fuera de los límites no está definido a menos que se especifique explícitamente en la interfaz. Eso es muy parecido a lo que hace C / C ++, y permitiría una mejor escritura y potencialmente una gran cantidad de optimizaciones del compilador, si alguien decide eventualmente escribir un compilador de terceros que use la especificación del lenguaje, pero que no esté tan preocupado por haciendo coincidir el emit.

Sé que admitir el comportamiento indefinido de C / C ++ coincidente suena muy estúpido en la superficie, pero creo que, en este caso, podría valer la pena. Es raro ver algo que en realidad se haga _mejor_ al hacer un acceso fuera de los límites. El 99,99% de los usos que he visto son solo olores de código extremadamente picantes, casi siempre realizados por personas que casi no están familiarizadas con JavaScript.

(La mayoría de estas personas, en mi experiencia, ni siquiera han oído hablar de CoffeeScript, y mucho menos de TypeScript. Muchos de ellos ni siquiera conocen la nueva versión de JS que acaba de finalizar y estandarizar, ES2015).

¿Existe una resolución emergente para esto?

Aparte de tener un tipo que no acepta valores NULL, todavía parece útil que TypeScript falle si uno está tratando de acceder a una propiedad en una variable que es _aseguramente_ nula.

var o = null;
console.log(o.x);

... debería fallar.

Asegurarse a través del sistema de tipos de que todos los accesos a las matrices están controlados por límites parece derivar hacia el reino de los tipos dependientes. Si bien los tipos dependientes son bastante ingeniosos, parece una característica mucho más importante que los tipos que no aceptan valores NULL.

Parece que hay tres opciones asumiendo que la verificación de límites no se aplica en las matrices en tiempo de compilación:

  1. Se considera que la indexación de matrices (y cualquier acceso a elementos de matriz arbitrarios por índice) devuelve un tipo que acepta valores NULL, incluso en matrices de tipos que no aceptan valores NULL. Básicamente, el "método" [] tiene una firma de tipo T? . Si sabe que solo está indexando con límites comprobados, puede convertir el T? en un T! en el código de su aplicación.
  2. La indexación de matrices devuelve exactamente el mismo tipo (con la misma capacidad de nulabilidad) que el parámetro de tipo genérico de la matriz, y se supone que la aplicación comprueba los límites de todo el acceso a la matriz. El acceso fuera de límites devolverá undefined y no será detectado por el verificador de tipo.
  3. La opción nuclear: todas las matrices están codificadas en el lenguaje como anulables, y los intentos de utilizar matrices que no aceptan nulos fallan en las comprobaciones de tipo.

Todos estos también se aplican al acceso basado en índices de propiedades en objetos, por ejemplo, object['property'] donde object es del tipo { [ key: string ]: T! } .

Personalmente, prefiero la primera opción, donde la indexación en una matriz u objeto devuelve un tipo anulable. Pero incluso la segunda opción parece mejor que que todo sea anulable, que es el estado actual de las cosas. La opción nuclear es asquerosa pero, honestamente, también es mejor que todo lo que se pueda anular.

Hay una segunda pregunta sobre si los tipos deberían ser por defecto no anulables o por defecto anulables. Parece que en cualquier caso sería útil tener sintaxis tanto para tipos explícitamente anulables como explícitamente no anulables para manejar genéricos; Por ejemplo, imagina un método get en una clase de contenedor (por ejemplo, un Mapa) que tomó algún valor y posiblemente devolvió un tipo, _ incluso si el contenedor solo contiene valores que no aceptan valores NULL_:

class Container<K,V> {
  get(key: K): V? {
    // fetch from some internal data structure and return the value, if it exists
    // return null otherwise
  }
}

// only non-nullable values allowed in the container
const container = new Container<SomeKeyClass!, SomeValueClass!>();
const val: SomeValueClass!;
// ... later, we attempt to read from the container with a get() call
// even though only non-nullables are allowed in the container, the following should fail:
// get() explicitly returns null when the item can't be found
val = container.get(someKey);

De manera similar, podríamos (este es un argumento menos sólido) querer asegurarnos de que nuestra clase de contenedor solo acepta claves no nulas en inserciones, incluso cuando se usa un tipo de clave anulable:

class Container<K, V> {
  insert(key: K!, val: V): void {
    // put the val in the data structure
    // the key must not be null here, even if K is elsewhere a nullable type
  }
}

const container = new Container<SomeKeyClass?, SomeValueClass>();
container.insert(null, new SomeValueClass()); // fails

Entonces, independientemente de si cambia el valor predeterminado, parece que sería útil tener una sintaxis explícita tanto para los tipos que aceptan valores NULL como para los tipos que no aceptan valores NULL. ¿A menos que me esté perdiendo algo?

En el punto donde hay sintaxis para ambos, el valor predeterminado parece que podría ser un indicador de compilador similar a --noImplicitAny . Personalmente, votaría por que el valor predeterminado permanezca igual hasta una versión 2.0, pero cualquiera de los dos parece estar bien siempre que haya una escotilla de escape (al menos temporalmente).

Preferiría la segunda opción, ya que aunque haría que el acceso fuera de los límites fuera un comportamiento indefinido (en el ámbito de la escritura TS), creo que es un buen compromiso para esto. Puede aumentar considerablemente la velocidad y es más sencillo de manejar. Si realmente espera que sea posible un acceso fuera de los límites, debe usar un tipo anulable explícitamente o convertir el resultado en un tipo anulable (que siempre es posible). Y, en general, si la matriz no admite nulos, cualquier acceso fuera de los límites es casi siempre un error, uno que debería estallar violentamente en algún momento (es un defecto de JS).

Básicamente, requiere que el programador sea explícito en lo que espera. Es menos seguro para los tipos, pero en este caso, creo que la seguridad para los tipos puede terminar interfiriendo.

Aquí hay una comparación con cada opción, usando una función de suma como ejemplo (las primitivas son las más problemáticas):

// Option 1
function sum(numbers: !number[]) {
  let res = 0
  for (let i = 0; i < numbers.length; i++) {
    res += <!number> numbers[i]
  }
  return res
}

// Option 2
function sum(numbers: !number[]) {
  let res = 0
  for (let i = 0; i < numbers.length; i++) {
    res += numbers[i]
  }
  return res
}

// Option 3
function sum(numbers: number[]) {
  let res = 0
  for (let i = 0; i < numbers.length; i++) {
    res += <!number> numbers[i]
  }
  return res
}

Otro ejemplo: una función map .

// Option 1
function map<T>(list: !T[], f: (value: !T, index: !number) => !T): !T[] {
  let res: !T[] = []
  for (let i = 0; i < list.length; i++) {
    res.push(f(<!T> list[i], i));
  }
  return res
}

// Option 2
function map<T>(list: !T[], f: (value: !T, index: !number) => !T): !T[] {
  let res: !T[] = []
  for (let i = 0; i < list.length; i++) {
    res.push(f(list[i], i));
  }
  return res
}

// Option 3
function map<T>(list: T[], f: (value: !T, index: !number) => !T): T[] {
  let res: T[] = []
  for (let i = 0; i < list.length; i++) {
    const entry = list[i]
    if (entry !== undefined) {
      res.push(f(<!T> entry, i));
    }
  }
  return res
}

Otra pregunta: ¿cuál es el tipo de entry en cada uno de estos? !string , ?string o string ?

declare const regularStrings: string[];
declare const nullableStrings: ?string[];
declare const nonnullableStrings: !string[];

for (const entry of regularStrings) { /* ... */  }
for (const entry of nullableStrings) { /* ... */  }
for (const entry of nonnullableStrings) { /* ... */  }

La opción tres fue una sugerencia un poco irónica: stick_out_tongue:

Re: tu última pregunta:

declare const regularStrings: string[];
declare const nullableStrings: string?[];
declare const nonNullableStrings: string![]; // fails typecheck in option three

for(const entry of regularStrings) {
  // option 1: entry is of type string?
  // option 2: depends on default nullability
}

for(const entry of nullableStrings) {
  // option 1 and 2: entry is of type string?
}

for(const entry of nonNullableStrings) {
  // option 1: entry is of type string?
  // option 2: entry is of type string!
}

En algunos casos, en los que desea devolver un tipo que no acepta valores NULL y lo obtiene de una matriz, por ejemplo, tendrá que hacer un lanzamiento adicional con la opción uno suponiendo que haya garantizado en otro lugar que no hay valores indefinidos en la matriz (el requisito de esta garantía no cambia independientemente del enfoque que se adopte, solo la necesidad de escribir as string! ). Personalmente, todavía lo prefiero porque es más explícito (debe especificar cuándo está tomando un comportamiento posiblemente peligroso, en lugar de que suceda implícitamente) y más consistente con la forma en que funcionan la mayoría de las clases de contenedores: por ejemplo, un mapa get claramente devuelve tipos que aceptan valores NULL (devuelve el objeto si existe debajo de la clave, o nulo si no lo hace), y si Map.prototype.get devuelve un valor que admite valores NULL, entonces object['property'] probablemente debería hacerlo lo mismo, ya que ofrecen garantías similares acerca de la nulabilidad y se usan de manera similar. Lo que deja a las matrices como las extrañas donde los errores de referencia nulos pueden volver a entrar, y donde el sistema de tipos permite que el acceso aleatorio no sea anulable.

Definitivamente hay otros enfoques; por ejemplo, actualmente Flow usa la opción dos , y la última vez que verifiqué que SoundScript hizo que los arreglos dispersos fueran explícitamente ilegales en su especificación (bueno, el modo fuerte / "SaneScript" los hace ilegales, y SoundScript es un superconjunto de las nuevas reglas), que hasta cierto punto evita el problema, aunque todavía tendrán que averiguar cómo lidiar con los cambios de longitud manuales y con la asignación inicial. Sospecho que se acercarán más a la opción uno en el intercambio de conveniencia versus seguridad, es decir, será menos conveniente escribir pero más seguro, debido a su énfasis en la solidez del sistema de tipos, pero probablemente se verá algo diferente que cualquiera de estos enfoques debido a la prohibición de matrices dispersas.

Creo que el aspecto del rendimiento es extremadamente teórico en este punto, ya que AFAIK TypeScript continuará emitiendo el mismo JS independientemente de las transmisiones para cualquier elección aquí y las VM subyacentes continuarán verificando los límites de las matrices bajo el capó independientemente. Así que no estoy demasiado influenciado por ese argumento. En mi opinión, la pregunta gira principalmente en torno a la conveniencia frente a la seguridad; para mí, la victoria en seguridad aquí parece valer la pena el intercambio de conveniencia. Por supuesto, cualquiera es una mejora con respecto a que todos los tipos sean anulables.

Estoy de acuerdo en que la parte de la actuación es principalmente teórica, pero aún así
como la conveniencia de asumir. La mayoría de las matrices son densas y la nulabilidad por
el valor predeterminado no tiene sentido para matrices booleanas y numéricas. Si no es
destinado a ser una matriz densa, debe marcarse como explícitamente anulable para
la intención es clara.


TypeScript realmente necesita una forma de afirmar las cosas, ya que las afirmaciones a menudo son
se utiliza para ayudar en la verificación de tipos estáticos en otros idiomas. He visto en el
El código V8 basa una macro UNREACHABLE(); que permite que las suposiciones sean una
un poco más seguro, bloqueando el programa si se viola la invariante. C ++
tiene static_assert para aserciones estáticas para ayudar en la verificación de tipos.

El martes 20 de octubre de 2015 a las 4:01 a. M., Matt Baker [email protected]
escribió:

La opción tres fue una sugerencia un poco irónica [imagen:
: lengua_atascada:]

Re: tu última pregunta:

declare const regularStrings: string []; declare const nullableStrings: string? []; declare const nonNullableStrings: string! []; // falla la verificación de tipo en la opción tres
for (entrada constante de regularStrings) {
// opción 1: ¿la entrada es de tipo cadena?
// opción 2: depende de la nulabilidad predeterminada
}
for (entrada constante de nullableStrings) {
// opción 1 y 2: ¿la entrada es de tipo cadena?
}
for (entrada constante de nonNullableStrings) {
// opción 1: ¿la entrada es de tipo cadena?
// opción 2: ¡la entrada es de tipo cadena!
}

En algunos casos, cuando desea devolver un tipo que no acepta valores NULL y está
obteniéndolo de una matriz, por ejemplo, tendrás que hacer un lanzamiento adicional
con la opción uno asumiendo que en otro lugar ha garantizado que no hay indefinidos
valores en la matriz (el requisito de esta garantía no cambia
independientemente del enfoque que se adopte, ¡solo es necesario escribir como cadena!).
Personalmente, todavía lo prefiero porque es más explícito (tienes que
especificar cuándo está tomando un comportamiento posiblemente peligroso, en lugar de hacerlo
sucediendo implícitamente) y más coherente con la forma en que la mayoría de las clases de contenedores
trabajo: por ejemplo, la función get de un mapa claramente devuelve tipos que aceptan valores NULL
(devuelve el objeto si existe bajo la clave, o nulo si no existe),
y si Map.prototype.get devuelve un objeto anulable, entonces objeto ['propiedad']
probablemente debería hacer lo mismo, ya que ofrecen garantías similares sobre
nulability y se usan de manera similar. Lo que deja las matrices como las impares.
donde los errores de referencia nulos pueden volver a entrar, y donde el acceso aleatorio es
permitido que no sea anulable por el sistema de tipos.

Definitivamente hay otros enfoques; por ejemplo, actualmente Flow usa
opción dos http://flowtype.org/docs/nullable-types.html , y la última
comprobado SoundScript hizo matrices dispersas explícitamente ilegal en su especificación
https://github.com/rwaldron/tc39-notes/blob/master/es6/2015-01/JSExperimentalDirections.pdf
(bueno, el modo fuerte / "SaneScript" los hace ilegales, y SoundScript es un
superconjunto de las nuevas reglas), que en cierta medida elude el problema
aunque todavía necesitarán descubrir cómo lidiar con la longitud manual
cambios y con asignación inicial.

Creo que el aspecto del rendimiento es extremadamente teórico en este punto,
ya que AFAIK TypeScript continuará emitiendo el mismo JS independientemente de
conversiones para cualquier elección aquí y las VM subyacentes continuarán
arreglos de verificación de límites bajo el capó independientemente. Entonces no estoy demasiado influenciado por
ese argumento. La pregunta en mi mente es principalmente sobre conveniencia vs.
seguridad; para mí, la victoria en seguridad aquí parece valer la pena el intercambio de conveniencia. De
Por supuesto, cualquiera de las dos es una mejora con respecto a que todos los tipos sean anulables.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -149468527
.

Isiah Meadows

¿Deberíamos empezar a llamarlos tipos non-void ? En cualquier caso, creo que definir explícitamente non-void con T! o !T es un error. Es difícil de leer como humano y también es difícil lidiar con todos los casos para el compilador de TypeScript.

El problema principal que veo con los tipos non-void de forma predeterminada es que se trata de un cambio importante. Bueno, ¿qué pasa si agregamos un análisis más estático, similar a Flow, que no cambia el comportamiento en absoluto, pero detectará más errores? Entonces podemos detectar la mayoría de los errores de esta clase ahora, pero no cambie la sintaxis, y en el futuro, será mucho más fácil introducir una bandera del compilador o un comportamiento predeterminado que sea un cambio menos importante.

`` .ts
// la función se compila felizmente
función len (x: cadena): número {
return x.length;
}

len ("trabaja"); // 5
len (nulo); // error, sin propiedad longitud de nulo

``` .ts
function len(x: string): number {
    if (x === null) {
        return -1;
    }
    return x.length;
}

len("works"); // 5
len(null); // null

Lo que realmente estaría sucediendo aquí es modelar los datos de entrada como non-void , pero agregando void implícitamente cuando se maneja en la función. De manera similar, el tipo de retorno es non-void menos que pueda devolver explícitamente null o undefined

También podemos agregar el tipo ?T o T? , que fuerza la verificación nula (y / o indefinida) antes de su uso. Personalmente me gusta T? , pero existe un precedente de usar ?T con Flow.

`` .ts
función len (x:? cadena): número {
return x.length; // error: no hay propiedad de longitud en el tipo? cadena, debe usar un protector de tipo
}

One more example -- what about using function results?

``` .js
function len(x: string): number {
    return x.length;
}

function identity(f: string): string {
    return f;
}

function unknown(): string {
    if (Math.random() > 0.5) {
        return null;
    }
    return "maybe";
}

len("works"); // 5
len(null); // error, no property length of null

identity("works"); // "works": string
identity(null); // null: void
unknown(); // ?string

len(identity("works")); // 5
len(identity(null)); // error, no property length of null
len(unknown()); // error: no length property on type ?string, you must use a type guard

Debajo del capó, lo que realmente está sucediendo aquí es que TypeScript está infiriendo si un tipo puede ser nulo o no al ver si maneja nulo y si se le da un valor posiblemente nulo.

La única parte complicada aquí es cómo interactuar con archivos de definición. Creo que esto se puede resolver si el valor predeterminado es asumir que un archivo de definición que declara una comprobación function(t: T) es nula / void , al igual que en el segundo ejemplo. Esto significa que esas funciones podrán tomar valores nulos sin que el compilador genere un error.

Esto ahora permite dos cosas:

  1. Adopción gradual de la sintaxis de tipo ?T , de la cual ya se cambiarían los parámetros opcionales.
  2. En el futuro, se podría agregar un indicador de compilador --noImplicitVoid , que trataría los archivos de declaración de la misma manera que los archivos de código compilados. Esto sería "romper", pero si se hace en el futuro, la mayoría de las bibliotecas adoptarán la mejor práctica de usar ?T cuando el tipo puede ser void y T cuando no puede. También sería opt-in, por lo que solo se verían afectados aquellos que elijan usarlo. Esto también podría requerir que se use la sintaxis ?T en el caso de que un objeto sea nulo.

Creo que este es un enfoque realista, ya que brinda una seguridad mucho mayor cuando la fuente está disponible en TypeScript, encontrando esos problemas complicados, al mismo tiempo que permite una integración fácil, bastante intuitiva y compatible con versiones anteriores con archivos de definición.

El prefijo ? variante también se usa en las anotaciones del compilador de cierre, IIRC.

El martes 17 de noviembre de 2015 a las 13:37, Tom Jacques [email protected] escribió:

¿Deberíamos empezar a llamarlos tipos no vacíos? En cualquier caso, creo
definiendo explícitamente no vacío con T! o! T es un error. Es difícil
leer como un ser humano, y también difícil de tratar con todos los casos de
el compilador de TypeScript.

El problema principal que veo con los tipos no nulos de forma predeterminada es que es un
cambio de ruptura. Bueno, ¿y si agregamos un análisis más estático?
similar a Flow, eso no cambia el comportamiento en absoluto, pero captará
más errores? Entonces podemos detectar la mayoría de los errores de esta clase ahora, pero
no cambie la sintaxis y, en el futuro, será mucho más fácil
introducir una bandera del compilador o un comportamiento predeterminado que sea menos rompedor
cambio.

// función compila felizmente función len (x: cadena): número {
return x.length;
}

len ("trabaja"); // 5
len (nulo); // error, sin propiedad longitud de nulo

función len (x: cadena): número {
si (x === nulo) {
return -1;
}
return x.length;
}

len ("trabaja"); // 5
len (nulo); // nulo

Lo que realmente estaría sucediendo aquí es modelar los datos de entrada como no vacíos,
pero agregando vacío implícitamente cuando se maneja en la función. Similar,
el tipo de retorno no es nulo a menos que pueda devolver explícitamente nulo o
indefinido

También podemos agregar la? T o T? tipo, que fuerza el nulo (y / o
undefined) comprobar antes de usar. Personalmente me gusta T ?, pero hay precedente
utilizar? T con Flow.

función len (x:? cadena): número {
return x.length; // error: no hay propiedad de longitud en el tipo? cadena, debe usar un protector de tipo
}

Un ejemplo más: ¿qué pasa con el uso de resultados de funciones?

función len (x: cadena): número {
return x.length;
}
identidad de la función (f: cadena): cadena {
return f;
}
función desconocida (): cadena {
if (Math.random ()> 0.5) {
devolver nulo;
}
volver "tal vez";
}

len ("trabaja"); // 5
len (nulo); // error, sin propiedad longitud de nulo

identidad ("obras"); // "funciona": cadena
identidad (nulo); // nulo: vacío
desconocido(); // ?cuerda

len (identidad ("obras")); // 5
len (identidad (nulo)); // error, sin propiedad longitud de nulo
len (desconocido ()); // error: no hay propiedad de longitud en el tipo? cadena, debe usar un protector de tipo

Debajo del capó, lo que realmente está sucediendo aquí es que TypeScript es
inferir si un tipo puede ser nulo al ver si maneja
nulo, y se le da un valor posiblemente nulo.

La única parte complicada aquí es cómo interactuar con archivos de definición. I
Creo que esto se puede resolver teniendo por defecto que se asuma que un
archivo de definición que declara una función (t: T) hace una verificación nula / nula, mucho
como el segundo ejemplo. Esto significa que esas funciones podrán tomar
valores nulos sin que el compilador genere un error.

Esto ahora permite dos cosas:

  1. Adopción gradual de la sintaxis de tipo? T, de las cuales es opcional
    los parámetros ya se cambiarían a.
  2. En el futuro, se podría agregar un indicador de compilador --noImplicitVoid
    que trataría los archivos de declaración de la misma manera que los archivos de código compilados. Esta
    sería "romper", pero si se hace en el futuro, la mayoría de
    las bibliotecas adoptarán la mejor práctica de usar? T cuando el tipo pueda
    sea ​​nulo y T cuando no pueda. También sería opcional, por lo que solo aquellos
    quienes decidan usarlo se verían afectados.

Creo que este es un enfoque realista, ya que ofrece una seguridad mucho mayor.
cuando la fuente está disponible en TypeScript, encontrar esos problemas complicados,
al mismo tiempo que permite que sea fácil, bastante intuitivo y compatible con versiones anteriores
integración con archivos de definición.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -157463734
.

Buen punto. También hay muchas similitudes con las declaraciones de parámetros opcionales existentes:

`` .ts
interfaz conOptionalProperty {
o ?: cuerda
}
interfaz withVoidableProperty {
o:? cuerda
}

función conOptionalParam (o ?: string) {}
función conVoidableParam (o:? string) {}
''

De hecho, usan prefijo . Usan ? para tipos explícitos que aceptan valores NULL y ! para tipos que no aceptan valores NULL, siendo el valor predeterminado el valor predeterminado.

La distinción anulable vs anulable tiene mucho sentido. : +1:

Siento que esto está dando vueltas.

¿Han leído todos arriba por qué una definición anulable / no anulable no va a resolver el problema subyacente?

@Griffork He leído todos los comentarios. Admito que lo que dije es una especie de repetición / combinación de lo que otros han dicho, pero creo que es el camino más realista a seguir. No sé cuál ve como el problema subyacente, pero para mí el problema subyacente es que null y undefined son parte de todos los tipos, y el compilador no garantiza actualmente la seguridad al intentar para usar un argumento de tipo T . Según mi ejemplo:

`` .ts
función len (x: cadena): número {
return x.length;
}

len ("trabaja");
// Sin error - 5

len (nulo);
// El compilador permite esto, pero debería producirse un error aquí con algo como
// error: no hay propiedad 'longitud' de nulo

len (indefinido);
// El compilador permite esto, pero debería producirse un error aquí con algo como
// error: no hay propiedad 'longitud' de indefinida
''

Esa es mi opinión sobre el problema fundamental del _lenguaje_: la falta de seguridad. Una gran cantidad de esto se puede solucionar observando el flujo de argumentos en funciones usando análisis estático y de tipo, y dando un error cuando algo podría hacerse de manera insegura. No es necesario que haya ningún tipo ?T en absoluto cuando el código está disponible porque ese análisis se puede realizar (aunque no en todos los casos con perfecta precisión todo el tiempo). La razón para agregar un tipo ?T es porque fuerza la verificación de seguridad y deja muy clara la intención del programador.

El problema con la _implementación_ es la compatibilidad con versiones anteriores. Si el equipo de TS publicara mañana un cambio que ahora un tipo T no es nulo de forma predeterminada, rompería el código existente que actualmente acepta y maneja entradas nulas con esas firmas de tipo. Los miembros del equipo de TS han declarado en este mismo número que no están dispuestos a hacer un cambio tan importante. Están dispuestos a romper algunas cosas, si el efecto es lo suficientemente pequeño y el beneficio es lo suficientemente grande, pero esto tendría un impacto demasiado grande.

Mi propuesta está en dos partes separadas, una de las cuales creo que sería una gran adición al lenguaje que no cambia nada de la sintaxis / semántica existente excepto para encontrar errores verdaderos, y la otra es una forma posible de reducir el impacto de la romper el cambio para obtener las garantías más sólidas de los tipos no nulos en el futuro. Sigue siendo un cambio radical, pero con suerte de un tamaño y una naturaleza más aceptables.

Parte uno:

  • Agregue análisis para identificar cuándo los tipos nulos / indefinidos / vacíos podrían pasarse a funciones que no los manejan (solo funciona cuando el código TS está presente, no en archivos de definición).
  • Agregue el tipo ?T que fuerza la verificación nula antes de usar el argumento. En realidad, esto es solo azúcar sintáctico en torno a un tipo de opción a nivel de idioma.
  • Estas dos características se pueden implementar de forma independiente ya que cada una tiene su propio mérito individual

La segunda parte:

  • Esperar. Más adelante, después de que se presente la Parte Uno, y la comunidad y los usuarios de TS hayan tenido tiempo de usar esas funciones, el estándar será usar T cuando el tipo no sea nulo, y ?T cuando el tipo podría ser nulo. Esto no está garantizado, pero creo que sería una práctica recomendada obvia.
  • Como característica separada, agregue una opción de compilador --noImplicitVoid que requiere que los tipos sean ?T si pueden ser nulos. Este es solo el compilador que aplica las mejores prácticas ya existentes. Si hay un archivo de definición que no se adhiere a las mejores prácticas, sería incorrecto, pero por eso es opt-in.
  • Si realmente quisiera ser estricto, la bandera podría aceptar argumentos que especifiquen a qué directorios / archivos debe aplicarse. Luego, podría aplicar el cambio solo a su código y excluir node_modules .

Creo que esta es la opción más realista porque la primera parte se puede hacer incluso sin la segunda parte. Sigue siendo una buena característica que mitigará en gran medida este problema. Seguro que no es perfecto, pero lo tomaré lo suficientemente bueno si eso significa que puede suceder. También deja la opción sobre la mesa para tipos verdaderos no nulos en el futuro. Un problema en este momento es que cuanto más persista este problema, más importante será el cambio para solucionarlo porque se está escribiendo más código en TS. Con la Parte Uno, en el peor de los casos, debería ralentizar drásticamente eso y, en el mejor de los casos, reducir el impacto con el tiempo.

@tejacques Yo, por mi parte, estoy totalmente de acuerdo con esto.

@tejacques - FWIW, estoy completamente de acuerdo con tu valoración y propuesta. Esperemos que el equipo de TS esté de acuerdo :)

En realidad, dos cosas:

Uno, no estoy seguro de que el análisis similar al de Flow mencionado en la primera parte sea necesario. Si bien es muy interesante y útil, ciertamente no me gustaría que contenga tipos voidables / ?T , que parecen mucho más factibles en el diseño actual del lenguaje y brindan mucho más valor a largo plazo.

También expresaría --noImplicitVoid un poco diferente; digamos que no permite asignar null y undefined a tipos no anulables (es decir, predeterminados) en lugar de "requerir que los tipos sean ?T si se pueden anular ". Estoy bastante seguro de que queremos decir lo mismo, solo semántica; se centra en el uso en lugar de la definición, que, si entiendo cómo funciona TS, es lo único que realmente puede hacer cumplir.

Y algo me vino a la mente ahora: tendríamos cuatro niveles de voidabilidad (esto también es cierto para Flow, que creo que está inspirando gran parte de esta conversación):

interface Foo {
  w: string;
  x?: string;
  y: ?string;
  z?: ?string;
}

Por debajo de --noImplicitVoid , w solo puede ser una cadena válida. Esta es una gran ventaja para la seguridad de tipos. ¡Adiós, error de mil millones de dólares! Sin --noImplicitVoid , por supuesto, la única restricción es que debe especificarse, pero puede ser nulo o indefinido. Este es un comportamiento bastante peligroso del lenguaje, creo, porque parece que está garantizando más de lo que realmente es.

x es completamente indulgente con la configuración actual. Puede ser una cadena, nula, indefinida y ni siquiera necesita estar presente en los objetos que la implementan. En --noImplicitVoid , las cosas se vuelven un poco más complejas ... es posible que no lo defina, pero si _hace_ establecer algo, ¿no se anulará? Creo que la forma en que Flow maneja esto es que puede establecer x en undefined (imitando la inexistencia), pero no null . Sin embargo, esto podría ser un poco obstinado para TypeScript.

Además, tendría sentido lógico requerir la verificación de nulos x antes de usarlo, pero eso nuevamente sería un cambio importante. ¿Podría este comportamiento ser parte de la bandera --noImplicitVoid ?

y se puede establecer en cualquier cadena o valor nulo, pero _ debe_ estar presente de alguna forma, incluso si es nulo. Y, por supuesto, acceder a él requiere una verificación nula. Este comportamiento puede resultar un poco sorprendente. ¿Deberíamos considerar _no_ configurarlo como lo mismo que configurarlo en undefined ? Si es así, ¿qué lo haría diferente de x , excepto que requiere un cheque nulo?

Y finalmente, z no necesita ser especificado, se puede configurar para absolutamente cualquier cosa (bueno, excepto una no cadena, por supuesto), y (por una buena razón) requiere una verificación nula antes de acceder a ella. ¡Todo tiene sentido aquí!

Hay un poco de superposición entre x y y , y sospecho que x eventualmente quedaría en desuso por la comunidad, prefiriendo el formulario z para máxima seguridad .

@tejacques

No es necesario que haya ningún tipo? T en absoluto cuando el código está disponible porque ese análisis se puede realizar (aunque no en todos los casos con una precisión perfecta todo el tiempo).

Esta afirmación es incorrecta. Muchos aspectos de la nulabilidad no se pueden tener en cuenta incluso con el código fuente completo disponible.

Por ejemplo: al llamar a una interfaz (estáticamente), no puede saber si pasar nulo está bien o no si la interfaz no está anotada en consecuencia. En un sistema de tipo estructural como TS, ni siquiera es fácil saber qué objetos son implementaciones de la interfaz y cuáles no. En general, no le importa, pero si desea deducir la nulabilidad del código fuente, lo haría.

Otro ejemplo: si tengo una matriz list y una función unsafe(x) cuyo código fuente muestra que no acepta un argumento null , el compilador no puede decir si eso la línea es segura o no: list.filter(unsafe) . Y de hecho, a menos que pueda saber estáticamente cuáles serán todos los posibles contenidos de list , esto no se puede hacer.

Otros casos están relacionados con la herencia y más.

No estoy diciendo que una herramienta de análisis de código que marque la violación flagrante de contratos nulos no tenga valor (lo tiene). Solo estoy señalando que minimizar la utilidad de las anotaciones nulas cuando el código fuente está disponible es en mi humilde opinión un error.

Dije en algún lugar de esta discusión que creo que la inferencia de nulabilidad podría ayudar a reducir la compatibilidad con versiones anteriores en muchos casos simples. Pero no puede reemplazar completamente las anotaciones de origen.

@tejacques mi mal, void===null :( culpo al despertarme).
Sin embargo, gracias por la publicación adicional, tenía mucho más sentido para mí que la publicación original. De hecho, me gusta bastante esa idea.

Les responderé a los tres por separado porque escribirlo en una publicación es una mancha ilegible.

@Griffork No hay problema. A veces es difícil transmitir todo correctamente a través del texto y creo que valió la pena aclararlo de todos modos.

@dallonf Tu reformulación es exactamente a lo que me refiero: estamos en la misma página.

Creo que la diferencia entre x?: T y y: ?T estaría en la información sobre herramientas / uso de funciones, y en la escritura y la protección utilizadas.

Declarar como un argumento opcional cambia la información sobre herramientas / uso de la función, por lo que está claro que es opcional:
a?: T no necesita pasarse como argumento en la llamada a la función, se puede dejar en blanco
Si una declaración no tiene ?: , debe pasarse como argumento en la llamada a la función y no puede dejarse en blanco.

w: T es un argumento obligatorio que no es void (con --noImplicitVoid )
no requiere guardia

x?: T es un argumento opcional, por lo que el tipo es realmente T | undefined
requiere un protector de if (typeof x !== 'undefined') .
Tenga en cuenta el glifo triple !== para la comprobación exacta de undefined .

y: ?T es un argumento obligatorio, y el tipo es realmente T | void
requiere un protector de if (y == null) .
Tenga en cuenta el doble glifo == que coincide con null y undefined es decir void

z?: ?T es un argumento opcional, y el tipo es realmente T | undefined | void que es T | void
requiere un protector de if (z == null) .
Note nuevamente el doble glifo == que coincide con null y undefined es decir void

Al igual que con todos los argumentos opcionales, no puede tener argumentos obligatorios que sigan a los opcionales. Entonces esa sería la diferencia. Ahora, también podría hacer un null guard en el argumento opcional, y eso también funcionaría, pero una diferencia clave es que no podría pasar el null a la función si llámalo; sin embargo, podría pasar undefined .

Creo que todos estos realmente tienen un lugar, por lo que no necesariamente desaprobaría la sintaxis de argumento opcional actual, pero estoy de acuerdo en que la forma z es la más segura.

Editar: se actualizó la redacción y se corrigieron algunos errores tipográficos.

@ jods4 Estoy de acuerdo con casi todo lo que dijiste. No estoy tratando de minimizar la importancia de no escribir void . Solo estoy tratando de impulsarlo una fase a la vez. Si el equipo de TS no puede hacerlo más tarde, al menos estaremos mejor, y si pueden hacerlo en el futuro después de implementar más verificaciones y ?T entonces esa misión se cumplió.

Creo que el caso de Arrays es realmente complicado. Siempre puedes hacer algo como esto:

`` .ts
function numToString (x: number) {
return x.toString ();
}
var nums: number [] = Array (100);
numToString (nums [0]); // ¡Estás jodido!

You can try to do something specifically for uninitialized arrays, like typing the `Array` function as `Array<?T>` / `?T[]` and upgrading it to `T[]` after a for-loop initializing it, but I agree that you can't catch everything. That said, that's already a problem anyway, and arrays typically don't even send uninitialized values to `map`/`filter`/`forEach`.

Here's an example -- the output is the same on Node/Chrome/IE/FF/Safari.

``` .ts
function timesTwo(x: number) {
    return x * 2;
}
function all(x) {
    return true;
}
var nums: number[] = Array(100);
nums.map(timesTwo);
// [undefined x 100]
nums.filter(all);
// []
nums.forEach(function(x) { console.log(x); })
// No output

Eso realmente no te ayuda mucho, ya que es inesperado, pero hoy en día no es un error en JavaScript real.

La única otra cosa que quiero enfatizar es que puedes progresar incluso con interfaces, es mucho más trabajo y esfuerzo a través del análisis estático que a través del sistema de tipos, pero no es muy diferente de lo que ya sucede ahora.

He aquí un ejemplo. Supongamos que --noImplicitVoid está apagado

`` .ts
interfaz ITransform{
(x: T): U;
}

interfaz IHaveName {
nombre: cadena;
}

función de transformación(x: T, fn: ITransform) {
return fn (x);
}

var named = {
nombre: "Foo"
};

var wrongName = {
nombre: 1234
};

var namedNull = {
nombre: nulo
};

var someFun = (x: IHaveName) => x.name;
var someFunHandlesVoid = (x: IHaveName) => {
if (x! = null && x.name! = null) {
return x.name;
}
devuelve "Sin nombre";
};

All of the above code compiles just fine -- no issues. Now let's try using it

``` .ts
someFun(named);
// "Foo"
someFun(wrongName);
// error TS2345: Argument of type '{ name: number; }' is not assignable to parameter
// of type 'IHaveName'.
//   Types of property 'name' are incompatible.
//     Type 'number' is not assignable to type 'string'.
someFun(null);
// Not currently an error, but would be something like this:
// error TS#: Argument of type 'null' is not assignale to parameter of type 'IHaveName'.
someFun(namedNull);
// Not currently an error, but would be something like this:
// error TS#: Argument of type '{ name: null; }' is not assignable to parameter of
// type 'IHaveName'.
//   Types of property 'name' are incompatible.
//     Type 'null' is not assignable to type 'string'.

someFunHandlesVoid(named);
// "Foo"
someFunHandlesVoid(wrongName);
// error TS2345: Argument of type '{ name: number; }' is not assignable to parameter
// of type 'IHaveName'.
someFunHandlesVoid(null);
// "No Name"
someFunHandlesVoid(namedNull);
// "No Name"

transform(named, someFun);
// "Foo"
transform(wrongName, someFun);
// error TS2453: The type argument for type parameter 'T' cannot be inferred from the usage.
// Consider specifying the type arguments explicitly.
//   Type argument candidate '{ name: number; }' is not a valid type argument because it
//   is not a supertype of candidate 'IHaveName'.
//     Types of property 'name' are incompatible.
//       Type 'string' is not assignable to type 'number'.
transform(null, someFun);
// Not currently an error, but would be something like this:
// error TS#: The type argument for type parameter 'T' cannot be inferred from the usage.
// Consider specifying the type arguments explicitly.
//   Type argument candidate 'null' is not a valid type argument because it
//   is not a supertype of candidate 'IHaveName'.
transform(namedNull, someFun);
// Not currently an error, but would be something like this:
// error TS#: The type argument for type parameter 'T' cannot be inferred from the usage.
// Consider specifying the type arguments explicitly.
//   Type argument candidate '{ name: null; }' is not a valid type argument because it
//   is not a supertype of candidate 'IHaveName'.
//     Types of property 'name' are incompatible.
//       Type 'string' is not assignable to type 'null'.

transform(named, someFunHandlesVoid);
// "Foo"
transform(wrongName, someFunHandlesVoid);
// error TS2453: The type argument for type parameter 'T' cannot be inferred from the usage.
// Consider specifying the type arguments explicitly.
//   Type argument candidate '{ name: number; }' is not a valid type argument because it
//   is not a supertype of candidate 'IHaveName'.
transform(null, someFunHandlesVoid);
// "No Name"
transform(namedNull, someFunHandlesVoid);
// "No Name"

Tienes razón en que no puedes atrapar todo, pero puedes atrapar muchas cosas.

Nota final: ¿cuál debería ser el comportamiento de lo anterior cuando --noImplicitVoid está activado?

Ahora someFun y someFunHandlesVoid se revisan de la misma manera y producen los mismos mensajes de error que someFun produjo. Aunque someFunHandlesVoid maneja void, llamarlo con null o undefined es un error porque la firma indica que no toma void . Debería escribirse como (x: ?IHaveName) : string para aceptar null o undefined . Si cambiamos su tipo, seguirá funcionando como antes.

Esta es la parte que es un cambio importante, pero todo lo que tenemos que hacer para solucionarlo fue agregar un solo carácter ? a la firma de tipo. Incluso podemos tener otra bandera --warnImplicitVoid que hace lo mismo que una advertencia para que podamos migrar lentamente.

Me siento como un idiota por hacer esto, pero voy a hacer una publicación más.

En este punto, no estoy seguro de qué hacer para continuar. ¿Existe una idea mejor? Deberíamos:

  • sigue discutiendo / especificando cómo debería comportarse esto?
  • convertir esto en tres nuevas propuestas de funciones?

    • Análisis mejorado

    • Quizás / Tipo de opción ?T

    • --noImplicitVoid opción del compilador

  • hacer ping a los miembros del equipo de TypeScript para obtener información?

Me inclino hacia nuevas propuestas y continúo la discusión allí, ya que es casi inhumano pedirle al equipo de TypeScript que se ponga al día con este hilo considerando cuánto tiempo es.

@tejacques

  • Falta un typeof en el ejemplo de triple igual a dallonf.
  • Parece que le faltan algunos ? en el ejemplo de jods4.

Por mucho que creo que las cosas deberían permanecer en este hilo, creo que este hilo ya no está siendo "observado" (tal vez más como un vistazo ocasional). Por lo tanto, crear algunos hilos nuevos definitivamente generaría tracción.
Pero espere unos días a la semana para que las personas asomen la cabeza y proporcionen comentarios primero. Querrá que su propuesta sea bastante sólida.

Editar: existe una rebaja recordada.

Comentar este hilo es bastante inútil en esta etapa. Incluso si se hace una propuesta que el equipo de TypeScript considera aceptable (intenté esto arriba en agosto) no hay forma de que la encuentren entre el ruido.

Lo mejor que puede esperar es que el nivel de atención sea suficiente para que el equipo de TypeScript presente su propia propuesta y la implemente. De lo contrario, olvídalo y usa Flow.

+1 para dividir esto, pero por ahora, la opción --noImplicitVoid puede
espere a que se implemente el tipo que acepta valores NULL.

Hasta ahora, hemos llegado a un acuerdo sobre la sintaxis y la semántica de
tipos que aceptan valores NULL, por lo que si alguien pudiera escribir una propuesta y una implementación
de eso, eso sería dorado. Tengo una propuesta de un proceso similar
con respecto a enumeraciones de otros tipos, pero simplemente no he tenido tiempo de
implementarlo debido a otros proyectos.

El miércoles 18 de noviembre de 2015 a las 21:24, Tom Jacques [email protected] escribió:

Me siento como un idiota por hacer esto, pero voy a hacer uno más.
correo.

En este punto, no estoy seguro de qué hacer para continuar. ¿Existe una idea mejor?
Deberíamos:

  • sigue discutiendo / especificando cómo debería comportarse esto?
  • convertir esto en tres nuevas propuestas de funciones?

    • Análisis mejorado

    • ¿Quizás / tipo de opción? T

    • --noImplicitVoid opción del compilador

  • hacer ping a los miembros del equipo de TypeScript para obtener información?

Me inclino hacia nuevas propuestas y continúo la discusión allí desde
es casi inhumano pedirle al equipo de TypeScript que se ponga al día con este hilo
considerando cuánto tiempo es.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -157928828
.

+1 para la opción --noImplicitNull (no permitir asignación nula y nula).

Intenté mitigar este problema con un tipo especial Op<A> = A | NullType . Parece funcionar bastante bien. Vea aquí .

+1 para _-- noImplicitNull_ también POR FAVOR: +1:

+1 para --noImplicitNull

¿Debería estar cerrado?

@Gaelan Dado # 7140 está fusionado, si desea presentar un nuevo problema dedicado por --noImplicitNull como lo sugirieron algunas personas aquí, entonces probablemente sea seguro hacerlo ahora.

@isiahmeadows Probablemente sería mejor dejar esto abierto entonces.

¿Debería estar cerrado?

Creemos que https://github.com/Microsoft/TypeScript/issues/2388 es la parte de cambio de nombre de este trabajo. Es por eso que aún no hemos declarado que esta función esté completa.

Si desea presentar un problema nuevo y exclusivo por --noImplicitNull como lo sugirieron algunas personas aquí, probablemente sea seguro hacerlo ahora.

No estoy seguro de entender cuál es la semántica solicitada de esta nueva bandera. Recomendaría abrir un nuevo número con una propuesta clara.

@mhegazy La idea planteada anteriormente en este número para --noImplicitNull era que todo tenía que ser explícitamente ?Type o !Type . En mi humilde opinión, no creo que valga la pena el texto estándar cuando hay otra bandera que infiere que no se aceptan nulos de forma predeterminada que IIRC ya se implementó cuando los tipos que aceptan valores nulos sí lo eran.

Cerrando ahora que # 7140 y # 8010 están fusionados.

Lo siento si comento sobre un problema cerrado, pero no conozco un lugar mejor donde preguntar y no creo que valga la pena un nuevo número si no hay interés.
¿Sería factible manejar nulos implícitos por archivo?
Como, manejar un montón de archivos td con noImplicitNull (porque provienen de definitivamente tipo y fueron concebidos de esa manera) pero manejar mi fuente como implicitNull?
¿Alguien podría encontrar esto útil?

@ massimiliano-mantione, consulte https://github.com/Microsoft/TypeScript/issues/8405

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