Typescript: Sugestão: tipo não anulável

Criado em 22 jul. 2014  ·  358Comentários  ·  Fonte: microsoft/TypeScript

Apresente duas novas sintaxes para declaração de tipo com base em 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 tipo padrão são anuláveis.

Dois novos sinalizadores de compilador:

  • inferNonNullableType faz o compilador inferir um tipo não anulável:
var myString3 = 'hello world' // typeof myString is '!string', non-nullable
  • nonNullableTypeByDefault (acho que pode haver um nome melhor):
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

Comentários muito úteis

Desculpe se eu comento sobre um assunto encerrado, mas não conheço melhor lugar onde perguntar e não acho que valha a pena um novo exemplar se não houver interesse.
Seria viável lidar com nulos implícitos por arquivo?
Tipo, lidar com um monte de arquivos td com noImplicitNull (porque eles vêm de um tipo definitivo e foram concebidos dessa forma), mas lidar com minha fonte como implícitaNull?
Alguém acharia isso útil?

Todos 358 comentários

Eu sugiro usar um tipo diferente de string como exemplo, uma vez que é anulável por natureza. : P
Eu posso perceber tipos não anuláveis ​​sendo problemáticos desde o usuário e compilador de "!" espera que o tipo seja sempre não nulo, o que nunca pode ser verdadeiramente declarado em JavaScript. Um usuário pode definir algo assim:

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

E porque é definido como não nulo, tudo pode agradar ao compilador, mas então alguém que o usa em javascript passa algo nulo e kaboom.

Dito isso, talvez a solução seja que, para métodos voltados ao público, ele possa declarar automaticamente não nulo. Mas então o compilador também pode afirmar que você não pode ter propriedades públicas (ou privadas, na verdade) que podem ter uma declaração! Não nula, uma vez que não podem ser aplicadas.

Isso pode ir mais fundo na discussão dos contratos de código para que isso seja devidamente aplicado.

Perdoe meus críticos, acho que há muito pouca necessidade de tipos não anuláveis ​​se / assim que os tipos de dados algébricos estiverem aqui. A razão pela qual as pessoas usam null para representar um valor ausente é porque não há melhor maneira de fazer isso em JavaScript e na maioria das linguagens OOP. Portanto, os ADTs de imagem já estão aqui. Então, quanto às antigas bibliotecas escritas antes dos não anuláveis, tê-las não tornará a vida melhor. Quanto aos novos libs, com ADTs em vigor, pode-se modelar com muita precisão o que um valor pode assumir de acordo com a especificação do domínio de negócios, sem usar nulos. Acho que o que estou dizendo é que o ADT é uma ferramenta muito mais poderosa para resolver o mesmo problema.

Pessoalmente, acabei de escrever uma pequena interface Maybe<T> e uso disciplina para garantir que variáveis ​​desse tipo nunca sejam nulas.

Eu sugiro usar um tipo diferente de string como exemplo, uma vez que é anulável por natureza. : P
Eu posso perceber tipos não anuláveis ​​sendo problemáticos desde o usuário e compilador de "!" espera que o tipo seja sempre não nulo, o que nunca pode ser verdadeiramente declarado em JavaScript. Um usuário pode definir algo assim:

function (myNonNull:! myClass): void {
meuNãoNulo.foo ();
}
E porque é definido como não nulo, tudo pode agradar ao compilador, mas então alguém que o usa em javascript passa algo nulo e kaboom.

Eu realmente não entendo que você também pode definir:

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

e se alguém passar um int para essa função, ele também resultará em um erro, uma vantagem do typescript é delegar ao compilador passar coisas que você verificaria manualmente em javascript, tendo outra verificação para anulável / não -tipo anulável parece razoável para mim. A propósito, SaferTypeScript e ClosureCompiler já fazem esse tipo de verificação.

Com tipos de união, poderíamos ter uma especificação bem mais simples para isso.
Digamos que agora temos um tipo básico 'nulo', podemos ter um modo 'mais estrito' onde 'nulo' e 'indefinido' não são compatíveis com nenhum tipo, então se quisermos expressar um valor anulável faríamos:

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

Com o script de tipo ativado 'modo estrito' deve-se verificar se todas as variáveis ​​não anuláveis ​​são inicializadas, também por padrão os parâmetros opcionais são anuláveis.

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, pelo que o Facebook mostrou do Flow, que usa sintaxe TypeScript, mas com tipos não anuláveis ​​por padrão, eles suportam uma abreviação para (null | T) como em sua postagem original - acho que era ?T ou T? .

var myString: string; // error

Isso pode ser potencialmente muito irritante no caso em que você deseja inicializar uma variável condicionalmente, por exemplo:

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

Em Rust, por exemplo, isso é bom, desde que o compilador possa ver que myString será inicializado antes de ser usado, mas a inferência de TypeScript não suporta isso no momento.

Honestamente, fazer algo como var myString = '' vez de var myString: string não me incomoda tanto, mas tenho certeza de que esse tipo de regra é sempre possível.

@fdecampredon +1 para isso - eu gosto muito da ideia. Para bases de código 100% JavaScript, essa seria uma restrição útil apenas em tempo de compilação. (Pelo que entendi sua proposta, não há intenção de código gerado para impor isso?)

Quanto ao atalho para (null | string) sure ?string está bem.
E com certeza @johnnyreilly é apenas uma verificação de tempo de compilação

Os tipos de soma tornam os tipos não anuláveis ​​por padrão uma possibilidade muito interessante. As propriedades de segurança de não anulável por padrão não podem ser exageradas. Os tipos de soma mais o planejado "if / typeof desestruturação" (não tenho certeza de como isso deve ser chamado) até mesmo tornam o tipo seguro para integrar APIs anuláveis ​​e não anuláveis.

No entanto, tornar os tipos não anuláveis ​​por padrão é uma grande alteração significativa, que exigiria a alteração de quase todos os arquivos de definição de tipo de terceiros existentes. Embora eu seja 100% favorável à mudança significativa, ninguém é capaz de atualizar as definições de tipo que estão por aí.

É bom que um grande consenso dessas definições seja coletado no repositório DefinitelyTyped, mas ainda tenho preocupações práticas sobre esse recurso.

@samwgoldman a idéia é ter tipos não anuláveis ​​apenas sob um sinalizador de compilador especial como nonImplicitAny este sinalizador poderia ser nomeado strict ou nonNullableType . Portanto, não haveria alterações significativas.

@fdecampredon E sobre as definições de tipo para bibliotecas não TypeScript, como aquelas em DefinitelyTyped? Essas definições não são verificadas pelo compilador, portanto, qualquer código de terceiros que pudesse retornar nulo precisaria ser anotado novamente para funcionar corretamente.

Posso imaginar uma definição de tipo para uma função atualmente anotada como "retorna string", mas às vezes retorna nulo. Se eu dependesse dessa função em meu código nonNullableType 'ed, o compilador não reclama (como poderia?) E meu código não é mais seguro para nulos.

A menos que esteja faltando alguma coisa, não acho que essa seja uma funcionalidade que possa ser ativada e desativada com um sinalizador. Parece-me que esta é uma mudança semântica do tipo tudo ou nada para garantir a interoperabilidade. Eu ficaria feliz se provassem que estou errado, porque acho que um recurso de mudança de sinalizador é mais provável de acontecer.

À parte, não há muita informação disponível ainda no compilador Flow do Facebook, mas a partir da gravação do vídeo da apresentação, parece que eles foram com não anulável por padrão. Nesse caso, pelo menos há alguma precedência aqui.

Ok, vamos supor que existe uma abreviação ? type para type | null | undefined .

@fdecampredon E sobre as definições de tipo para bibliotecas não TypeScript, como aquelas em DefinitelyTyped? Essas definições não são verificadas pelo compilador, portanto, qualquer código de terceiros que pudesse retornar nulo precisaria ser anotado novamente para funcionar corretamente.

Posso imaginar uma definição de tipo para uma função atualmente anotada como "retorna string", mas às vezes retorna nulo. Se eu dependesse dessa função em meu código nonNullableType'ed, o compilador não reclama (como poderia?) E meu código não é mais seguro para nulos.

Não vejo o problema, tenho certeza de que alguns arquivos de definição não serão válidos com o modo nonNullableType , mas na maioria das vezes uma boa biblioteca evita retornar null ou undefined portanto, a definição ainda estará correta na maioria dos casos.
De qualquer forma, eu pessoalmente raramente consigo escolher uma definição DefinitelyTyped sem ter que verificá-la / modificá-la, você terá apenas um pouco de trabalho extra para adicionar um prefixo ? com algumas definições.

A menos que esteja faltando alguma coisa, não acho que essa seja uma funcionalidade que possa ser ativada e desativada com um sinalizador. Parece-me que esta é uma mudança semântica do tipo tudo ou nada para garantir a interoperabilidade. Eu ficaria feliz se provassem que estou errado, porque acho que um recurso de mudança de sinalizador é mais provável de acontecer.

Não vejo por que não poderíamos ter um recurso de troca de sinalizador, as regras seriam simples:

  • no modo normal ? string é equivalente a string e null ou undefined podem ser atribuídos a todos os tipos
  • no modo nonNullableType ? string é equivalente a string | null | undefined e null ou undefined não podem ser atribuídos a nenhum outro tipo além de null ou undefined

Onde está a incompatibilidade com um recurso de mudança de bandeira?

Sinalizadores que mudam a semântica de um idioma são perigosos. Um problema é que os efeitos são potencialmente muito não locais:

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

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

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

É importante que alguém olhando para um trecho de código possa "acompanhar" o sistema de tipos e entender as inferências que estão sendo feitas. Se começarmos a ter um monte de sinalizadores que mudam as regras da linguagem, isso se torna impossível.

O único tipo de coisa segura a fazer é manter a mesma semântica de atribuibilidade e mudar o que é um erro versus o que não depende de um sinalizador, da mesma forma que noImplicitAny funciona hoje.

Eu sei que isso quebraria a retro-compatibilidade, e eu entendo o ponto de vista do @RyanCavanaugh , mas depois de provar isso com o flowtype é, honestamente, um recurso realmente inestimável, espero que acabe fazendo parte do texto datilografado

Além do comentário de RyanCavanaugh -> Pelo que li em algum lugar, a especificação / proposta ES7 menciona o uso de sobrecarga de função (mesmo nome de função, mas tipo de dados de parâmetro de entrada diferente). Esse é um recurso extremamente necessário para Javascript.

Dos documentos de fluxo :

O fluxo considera nulo como um valor distinto que não faz parte de nenhum outro tipo

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

Qualquer tipo T pode ser feito para incluir nulo (e o valor relacionado indefinido) escrevendo? T

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

[Flow] entende os efeitos de alguns testes de tipo dinâmico

(ou seja, no jargão do TS entende protetores de tipo)

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

Limitações

  • As verificações nas propriedades do objeto são limitadas devido à possibilidade de aliasing:

Além de ser capaz de ajustar os tipos de variáveis ​​locais, o Flow às vezes também pode ajustar os tipos de propriedades do objeto, especialmente quando não há operações intermediárias entre uma verificação e um uso. Em geral, porém, o aliasing de objetos limita o escopo dessa forma de raciocínio, uma vez que uma verificação na propriedade de um objeto pode ser invalidada por uma gravação nessa propriedade por meio de um alias, e é difícil para uma análise estática rastrear aliases com precisão

  • As verificações de estilo de proteção de tipo podem ser redundantes para propriedades de objeto.

[D] não espere que um campo anulável seja reconhecido como não nulo em algum método porque uma verificação nula é realizada em algum outro método em seu código, mesmo quando está claro para você que a verificação nula é suficiente para segurança em tempo de execução (digamos, porque você sabe que as chamadas para o primeiro método sempre seguem as chamadas para o último).

  • undefined não está marcado.

Valores indefinidos, assim como nulos, também podem causar problemas. Infelizmente, valores indefinidos são onipresentes em JavaScript e é difícil evitá-los sem afetar seriamente a usabilidade da linguagem. Por exemplo, os arrays podem ter orifícios para elementos; as propriedades do objeto podem ser adicionadas e removidas dinamicamente. O fluxo faz uma troca neste caso: ele detecta variáveis ​​locais indefinidas e valores de retorno, mas ignora a possibilidade de indefinidos resultantes de acessos de propriedade de objeto e elemento de array

E se a opção for adicionada ao mesmo tempo ao introduzir o tipo null (e a abreviação do ponto de interrogação)? A presença de um tipo null em um arquivo forçaria o compilador a um modo não anulável para aquele arquivo, mesmo se o sinalizador não estiver presente na linha de comando. Ou isso é um pouco mágico demais?

@jbondc parece bom. entretanto, o problema com isso é que terminará com ! todos os lugares: p

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

O que isto significa? Não existem tipos estáticos em js. Então, sim, as strings são "anuláveis", mas não vamos esquecer que elas também são numeráveis ​​e objetáveis ​​e fooable, etc. Qualquer valor pode ter qualquer tipo.

Portanto, ao colocar em camadas um sistema de tipo estático em cima do javascript, escolher se os tipos estáticos são anuláveis ​​ou não é apenas uma decisão de design. Parece-me que os tipos não anuláveis ​​são um padrão melhor, porque geralmente é apenas em casos especiais que você deseja que uma assinatura de função, por exemplo, aceite um valor nulo além do tipo especificado.

Diretivas como "use strict", que causam alterações de escopo na semântica, já fazem parte da linguagem; Acho que seria razoável ter uma diretiva "usar tipos não anuláveis" no TypeScript.

@metaweta Não acho que seja o suficiente, por exemplo, o que acontece se um _não nulo módulo_ consumir um anulável:

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

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

data no módulo B é de fato anulável, mas como 'use nonnull' não foi usado no módulo A, devemos relatar um erro?
Não vejo uma maneira de resolver esse problema com o recurso baseado em diretivas.

Sim,

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

causaria um erro. Em vez disso, você teria que fornecer um valor padrão para quando getData() retornar null :

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

@metaweta ok, mas como você sabe que é um erro? :)
tipo de getData ainda é '() => string []' você trataria automaticamente tudo o que vem de um 'módulo anulável' como 'anulável'?

Sim, exatamente (a menos que um tipo do módulo anulável seja explicitamente marcado de outra forma).

Parece que agora você deseja um sinalizador por arquivo que determina se o padrão do arquivo é anulável ou não.

Pessoalmente, acho que é um pouco tarde para introduzir essa mudança, e @RyanCavanaugh está certo, a mudança tornaria o Typescript menos previsível, pois você não seria capaz de determinar o que estava acontecendo apenas olhando para um arquivo.

Os projetos começam com este sinalizador do compilador ativado ou desativado por padrão? Se alguém estiver trabalhando em um projeto sem valor anulável padrão e criar / mudar para um projeto anulável padrão, isso causará confusão?
Atualmente trabalho com No Implicit Any na maioria dos meus projetos, e sempre que me deparo com um projeto que não tem essa opção ativada, fico surpreso.

O no impicit any é bom, mas em termos de sinalizadores que mudam a forma como a linguagem se comporta, acho que deve ser essa a linha. Mais do que isso, as pessoas que estão trabalhando em vários projetos iniciados por pessoas diferentes com regras diferentes vão perder muita produtividade devido a suposições falsas e deslizes.

@RyanCavanaugh estava preocupado com a não localidade e as diretivas têm escopo léxico. Você não pode obter mais local a menos que anote cada site. Não sou particularmente a favor da diretiva; Eu estava apenas apontando que a opção existe e que é pelo menos tão razoável quanto "use strict" no ES5. Pessoalmente, sou a favor dos tipos não anuláveis ​​por padrão, mas, na prática, é tarde demais para isso. Dadas essas restrições, sou a favor do uso! de alguma maneira. A proposta de @jbondc permite distinguir nulo de indefinido; considerando que os back-ends de Java continuam fazendo as pessoas usarem ambos os valores, parece o mais útil para mim.

Me desculpe se não fui claro, eu estava concordando com Ryan e adicionando minhas próprias preocupações.

Honestamente, se adicionar use not-null é o preço para evitar toda a exceção de ponteiro nulo, eu pagaria sem nenhum problema, considerando null ou undefined como atribuíveis a qualquer tipo é o pior erro aquele texto datilografado feito em minha opinião.

@jbondc Eu não usei 'use strict' e, portanto, estou fazendo algumas suposições, corrija-me se minhas suposições estiverem erradas:

Not null não afeta a sintaxe que o programador escreve, mas as capacidades do próximo programador que tenta usar aquele código (assumindo que o criador e o usuário são pessoas diferentes).

Portanto, o código:

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

(digitar em um telefone, pode estar errado)
É um código válido em um projeto normal e em um projeto diferente. A única maneira de o codificador saber se o código está funcionando ou não é examinando os argumentos da linha de comando.

No trabalho, temos um projeto de teste (que inclui a prototipagem de novos recursos) e um projeto principal (com nosso código real). Quando os protótipos estão prontos para serem movidos de um projeto para outro (normalmente com grandes refratores), não haveria erros no código, mas erros no uso do código. Este comportamento é diferente de nenhum implícito e usa estrito, o que ambos gerariam um erro imediato.

Agora eu tenho um certo domínio nesses projetos, então posso alertar os responsáveis ​​para não usarem esse novo 'recurso' porque não economizaria tempo, mas não tenho essa capacidade em todos os projetos em trabalhos.
Se quisermos habilitar esse recurso em apenas um projeto, então temos que habilitá-lo em todos os nossos outros projetos porque temos uma quantidade muito significativa de compartilhamento de código e migração de código entre projetos, e esse 'recurso' nos causaria muito de tempo voltando e 'consertando' funções que já estavam concluídas.

Direito @jbondc. @Griffork : Desculpe, não entendi esse mal-entendido; uma diretiva é uma expressão de string literal que aparece como a primeira linha de uma produção de programa ou produção de função e seus efeitos têm como escopo essa produção.

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

contra

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

Tipos não anuláveis ​​são inúteis. Os tipos não anuláveis ​​são useles. Eles são inúteis. Sem utilidade! Você não percebe, mas realmente não precisa deles. Há muito pouco sentido em se restringir proclamando que de agora em diante não usaremos NULLs. Como você representaria um valor ausente, por exemplo, em uma situação em que está tentando encontrar uma substring que não existe? Não ser capaz de expressar um valor ausente (o que NULL faz agora) não resolverá seu problema. Você vai trocar um mundo hostil com NULLs em todos os lugares por outro igualmente hostil sem nenhum valor ausente. O que você realmente precisa são chamados de tipos de dados algébricos que (entre muitas outras coisas interessantes) apresentam a capacidade de representar um valor ausente (o que você está procurando em primeiro lugar e o que é representado por NULL no mundo imperativo). Eu sou fortemente contra adicionar não anuláveis ​​à linguagem, porque parece um lixo sintático / semântico inútil que é uma solução ingênua e estranha para um problema conhecido. Leia sobre opcionais em F # e Maybe in Haskell, bem como variantes (também conhecidas como uniões marcadas, uniões discriminadas) e correspondência de padrões.

@ aleksey-bykov Parece que você não sabe que JavaScript tem dois valores nulos, undefined e null . O null em JavaScript só é retornado em uma regexp não correspondente e ao serializar uma data em JSON. A única razão pela qual está na linguagem é para interação com miniaplicativos Java. Variáveis ​​que foram declaradas mas não inicializadas são undefined , não null . Propriedades ausentes em um objeto retornam undefined , não null . Se você deseja explicitamente que undefined seja um valor válido, você pode testar propName in obj . Se você quiser verificar se uma propriedade existe no próprio objeto em vez de se ela é herdada, use obj.hasOwnProperty(propName) . Subseqüências ausentes retornam -1: 'abc'.indexOf('d') === -1 .

Em Haskell, Maybe é útil precisamente porque não existe um subtipo universal. O tipo inferior de Haskell representa não terminação, não um subtipo universal. Concordo que os tipos de dados algébricos são necessários, mas se quero uma árvore rotulada por inteiros, quero que cada nó tenha um inteiro, não null ou undefined . Se eu quiser, usarei uma árvore marcada por Maybe int ou um zíper.

Se adotarmos uma diretiva "use not-null", também gostaria de "use not-void" (nem nulo nem indefinido).

Se você quiser garantir seu próprio código de nulos, apenas proíba os nulos
literais. É muito mais fácil do que desenvolver tipos não anuláveis. Indefinido é um
um pouco mais complicado, mas se você sabe do que eles estão vindo, então
você sabe como evitá-los. Bottom in Haskell é inestimável! eu desejo
JavaScript (TypeScript) tinha um supertipo global sem um valor. tenho saudade
mal quando preciso lançar uma expressão. Tenho usado o TypeScript
desde v 0.8 e nunca usou nulos, muito menos teve uma necessidade deles. Só ignore
como você faz com qualquer outro recurso de linguagem inútil, como with
declaração.

@aleksey-bykov Se estou escrevendo uma biblioteca e quero garantir que as entradas não sejam nulas, tenho que fazer testes de tempo de execução em todos os lugares. Eu quero testes em tempo de compilação para isso, não é difícil fornecer, e tanto Closure quanto Flow fornecem suporte para tipos não nulos / indefinidos.

@metaweta , você não pode se garantir de nulos. Antes de seu código ser compilado, existem zilhões de maneiras de fazer sua lib gritar: pleaseNonNullablesNumbersOnly(<any> null) . Depois de compilado para js, não existem regras. Em segundo lugar, por que você se importaria? Diga em alto e bom som nulls are not supported, you put a null you will get a crash , como um aviso de isenção de responsabilidade, você não pode garantir a si mesmo de todos os tipos de pessoas, mas pode definir o escopo de suas responsabilidades. Em terceiro lugar, dificilmente consigo pensar em uma grande lib principal que seja à prova de balas para qualquer usuário que possa colocar como entrada, mas ainda assim é muito popular. Então, o seu esforço vale a pena?

@ aleksey-bykov Se os clientes da minha biblioteca também forem verificados quanto ao tipo, então certamente posso garantir que não receberei um valor nulo. Esse é o ponto principal do TypeScript. Pelo seu raciocínio, não há necessidade de tipos: apenas "diga alto e bom som" em sua documentação qual é o tipo esperado.

Fora do tópico, os nulos são extremamente valiosos para nós porque verificá-los é mais rápido do que verificar em undefined.
Embora não os usemos em todos os lugares, tentamos usá-los sempre que possível para representar valores não inicializados e números ausentes.

Sobre o assunto:
Nunca tivemos problemas com nulos 'escapando' para outro código, mas tivemos problemas com undefinedes aleatórios ou NaNs aparecendo. Acredito que o gerenciamento cuidadoso do código é melhor do que um sinalizador neste cenário.

No entanto, para tipagens de biblioteca, seria bom ter o tipo redundante null para que possamos escolher anotar funções que podem retornar null (isso não deve ser imposto pelo compilador, mas pelas práticas de codificação).

@metaweta , pelo meu raciocínio seus clientes não devem usar nulos em sua base de código, não é tão difícil, faça uma busca completa por nulos (diferencia maiúsculas de minúsculas, palavra inteira) e exclua todos eles. Muito desajeitado? Adicione uma opção de compilador --noNullLiteral para fantasia. Todo o resto permanece intacto, o mesmo código, sem problemas, solução muito mais leve com pegada mínima. De volta ao meu ponto, suponha que seus tipos não anuláveis ​​encontrem seu caminho para TS e estejam disponíveis em 2 sabores diferentes:

  • pode-se usar a sintaxe ! para denotar um tipo que não pode ser nulo, por exemplo string! não pode aceitar nulo
  • A chave noNullsAllowed está ligada

então você obtém um pedaço de json de seu servidor através de ajax com nulos em todos os lugares, moral: a natureza dinâmica do javascript não pode ser corrigida por uma anotação de tipo em cima dele

@ aleksey-bykov Da mesma forma, se estou esperando um objeto com uma propriedade numérica x e obtenho {"x":"foo"} do servidor, o sistema de tipos não será capaz de evitá-lo . Isso é necessariamente um erro de tempo de execução e um problema inevitável ao usar algo diferente do TypeScript no servidor.

Se, no entanto, o servidor estiver escrito em TypeScript e em execução no nó, ele poderá ser transpilado na presença de um arquivo .d.ts para meu código de front end e a verificação de tipo garantirá que o servidor nunca enviará JSON com nulos nele ou em um objeto cuja propriedade x não seja um número.

@metaweta , tipos não anuláveis ​​com certeza seriam outra medida de segurança de tipo, não estou questionando isso, estou dizendo que, ao impor uma disciplina muito básica (evitando literais nulos em seu código), você pode eliminar 90% dos seus problemas sem solicitar qualquer ajuda do compilador. Bem, mesmo se você tiver recursos suficientes para fazer cumprir essa medida, ainda não será capaz de eliminar os 10% restantes dos problemas. Afinal, qual é a questão? Eu pergunto: nós realmente precisamos tanto disso? Não, aprendi a viver sem nulos com sucesso (pergunte-me como), não me lembro quando recebi uma exceção de referência nula da última vez (além dos dados do nosso servidor). Há coisas muito mais legais que eu gostaria que tivéssemos. Este em particular é tão insignificante.

Sim, precisamos muito disso. Veja O erro de um bilhão de dólares de Hoare. A exceção de ponteiro nulo (NPE) é o erro mais comum encontrado em linguagens de programação digitadas que não discriminam tipos anuláveis ​​de não anuláveis. É tão comum que o Java 8 adicionou Optional em uma tentativa desesperada de combatê-lo.

Modelar anuláveis ​​no sistema de tipos não é apenas uma preocupação teórica, é uma grande melhoria. Mesmo que você tome muito cuidado para evitar nulos em seu código, as bibliotecas que você usa podem não fazer isso e, portanto, é útil ser capaz de modelar seus dados adequadamente com o sistema de tipos.

Agora que existem sindicatos e guardas de tipos no TS, o sistema de tipos é poderoso o suficiente para fazer isso. A questão é se isso pode ser feito de maneira compatível com as versões anteriores. Pessoalmente, acho que esse recurso é importante o suficiente para que o TypeScript 2.0 seja incompatível com as versões anteriores.

Implementar esse recurso corretamente provavelmente apontará para o código que já está quebrado em vez de quebrar o código existente: ele simplesmente apontará para as funções que vazam nulos fora deles (provavelmente não intencionalmente) ou classes que não inicializam corretamente seus membros (esta parte é mais difícil, pois o sistema de tipos pode precisar levar em consideração os valores dos membros a serem inicializados nos construtores).

Não se trata de _não usar_ nulos. Trata-se de modelar adequadamente todos os tipos envolvidos. De fato, esse recurso permitiria o uso de nulos de forma segura - não haveria mais razão para evitá-los! O resultado final seria muito semelhante à correspondência de padrões em um tipo Maybe algébrico (exceto que seria feito com uma verificação if em vez de uma expressão case)

E não se trata apenas de literais nulos. null e undefined são estruturalmente iguais (afaik não há funções / operadores que funcionam em um, mas não no outro), portanto, eles poderiam ser modelados suficientemente bem com um único tipo nulo em TS.

@metaweta ,

O valor nulo em JavaScript só é retornado em uma regexp não correspondente e ao serializar uma data em JSON.

Não é verdade.

  • A interação com o DOM produz nulos:
  console.log(window.document.getElementById('nonExistentElement')); // null
  • Como @ aleksey-bykov apontou acima, as operações ajax podem retornar nulo. Na verdade, undefined não é um valor JSON válido:
 JSON.parse(undefined); // error
 JSON.parse(null); // okay
 JSON.stringify({ "foo" : undefined}); // "{}"
 JSON.stringify({ "foo" : null}); // '{"foo":null}'

NB: Podemos fingir que undefined é retornado via ajax, porque acessar uma propriedade inexistente resultará em undefined - é por isso que undefined não é serializado.

Se, no entanto, o servidor estiver escrito em TypeScript e em execução no nó, ele poderá ser transpilado na presença de um arquivo .d.ts para meu código de front end e a verificação de tipo garantirá que o servidor nunca enviará JSON com nulos nele ou um objeto cuja propriedade x não é um número.

Isso não é inteiramente correto. Mesmo se o servidor for escrito em TypeScipt, não se pode de forma alguma garantir a introdução de nulos sem verificar cada propriedade de cada objeto obtido do armazenamento persistente.

Eu meio que concordo com @aleksey-bykov sobre isso. Embora fosse absolutamente brilhante se pudéssemos fazer com que o TypeScript nos alertasse em tempo de compilação sobre erros introduzidos por null e undefined, temo que isso apenas induza uma falsa sensação de confiança e acabe pegando trivialidades enquanto as fontes reais de null não são detectadas.

Mesmo se o servidor for escrito em TypeScipt, não se pode de forma alguma garantir a introdução de nulos sem verificar cada propriedade de cada objeto obtido do armazenamento persistente.

Este é de fato um argumento _para_ tipos não anuláveis. Se o seu armazenamento puder retornar nulos Foo's, então o tipo do objeto recuperado desse armazenamento é Nullable <Foo>, não Foo. Se você tem uma função que retorna com o objetivo de retornar Foo, então você _tem_ que assumir a responsabilidade ao lidar com o nulo (ou você o lança porque sabe melhor ou verifica se há nulo).

Se você não tivesse tipos não anuláveis, não pensaria necessariamente em verificar se há nulos ao retornar o objeto armazenado.

Temo que isso apenas induza a uma falsa sensação de confiança e acabe pegando curiosidades enquanto as verdadeiras fontes de null passam despercebidas.

Que tipo de coisas não triviais você acha que os tipos não anuláveis ​​vão perder?

Isso não é inteiramente correto. Mesmo se o servidor for escrito em TypeScipt, não se pode de forma alguma garantir a introdução de nulos sem verificar cada propriedade de cada objeto obtido do armazenamento persistente.

Se o armazenamento persistente for compatível com dados digitados, não haverá necessidade. Mas, mesmo se esse não fosse o caso, você teria verificações apenas nos pontos de busca de dados e, então, teria uma garantia em _todo_ o seu outro código.

Eu meio que concordo com @aleksey-bykov sobre isso. Embora fosse absolutamente brilhante se pudéssemos fazer com que o TypeScript nos alertasse em tempo de compilação sobre erros introduzidos por null e undefined, temo que isso apenas induza uma falsa sensação de confiança e acabe pegando trivialidades enquanto as fontes reais de null não são detectadas.

Usar tipos anuláveis ​​não seria um requisito absoluto. Se você achar que é desnecessário modelar os casos em que um método retorna nulo porque eles são "insignificantes", você pode simplesmente não usar um tipo anulável nessa definição de tipo (e obter a mesma insegurança de sempre). Mas não há razão para pensar que essa abordagem irá falhar - há exemplos de linguagens que já a implementaram com sucesso (por exemplo, Kotlin da JetBrains )

@aleksey-bykov Honestamente, você entendeu errado, uma das melhores coisas sobre tipos não anuláveis ​​é _a possibilidade de expressar um tipo como anulável_.
Com sua estratégia de nunca usar null para evitar erro de ponteiro nulo, você perde completamente a possibilidade de usar null por medo de introduzir um erro, isso é completamente bobo.

Outra coisa, por favor, em uma discussão sobre um recurso de linguagem, não vá com comentários estúpidos como:

Tipos não anuláveis ​​são inúteis. Os tipos não anuláveis ​​são useles. Eles são inúteis. Sem utilidade! Você não percebe, mas realmente não precisa deles.

Isso me faz sentir que devo ignorar tudo o que você postar em qualquer lugar da web, estamos aqui para discutir sobre um recurso, posso entender e aceitar de bom grado que seu ponto de vista não é o meu, mas não se comporte como um criança.

Não sou contra a introdução de anotações de tipo não nulo. Tem se mostrado útil em C # e outras linguagens.

O OP mudou o curso da discussão com o seguinte:

Honestamente, se adicionar use não nulo é o preço para evitar toda a exceção de ponteiro nulo, eu pagaria sem nenhum problema, considerando nulo ou indefinido como atribuível a qualquer tipo é o pior erro que o texto digitado cometeu em minha opinião.

Eu estava apenas apontando a prevalência de null e undefined na natureza.

Devo também acrescentar que uma das coisas que realmente aprecio no TypeScript é a atitude laissez-faire da linguagem. Foi uma lufada de ar fresco.

Insistir que os tipos não são anuláveis ​​por padrão vai contra a corrente desse espírito.

Tenho visto uma série de argumentos flutuando sobre por que precisamos / não precisamos disso e quero ver se entendo todos os casos subjacentes que foram discutidos até agora:

1) deseja saber se uma função pode ou não retornar nulo (causado por seu padrão de execução, não por sua digitação).
2) deseja saber se um valor pode ser nulo.
3) deseja saber se um objeto de dados pode conter valores nulos.

Agora, existem apenas dois casos em que a situação 2 pode ocorrer: você está usando nulos ou se uma função retorna um nulo. Se você eliminar todos os nulos de seu código (assumindo que você não os deseja), então, na verdade, a situação 2 só pode ocorrer como resultado da situação 1.

Acho que a situação 1 é melhor resolvida anotando o tipo de retorno da função para mostrar a presença de um valor nulo. _Isso não significa que você precisa de um type_ não nulo. Você pode anotar a função (por exemplo, usando tipos de união) e não ter tipos não nulos, é como a documentação, mas provavelmente mais claro neste caso.

A solução 2 também é resolvida por isso.

Isso permite que os programadores que trabalham em sua empresa usem processos e padrões para fazer com que os tipos nulos sejam marcados, e não a equipe do Typecript (exatamente da mesma forma que todo o sistema de digitação é opcional, então os tipos anuláveis ​​explícitos seriam uma opção dentro).

Quanto ao cenário 3, o contrato entre o servidor e o cliente não é para ser executado pela Typescript, ser capaz de marcar os valores afetados como possivelmente nulos pode ser uma melhoria, mas, eventualmente, você obterá o mesmo garentee disso que as do texto datilografado o conjunto de ferramentas fornece todos os outros valores (ou seja, nenhum, a menos que você tenha bons padrões ou práticas de codificação!

(postagem do telefone, desculpe pelos erros)

@fdecampredon , não é o medo em primeiro lugar, é que usar null é desnecessário. Eu não preciso deles. Como um bom bônus, eu consegui eliminar um problema de exceções de referência nula. Como tudo isso é possível? Ao empregar um tipo de soma com uma caixa vazia. Tipos de soma são um recurso nativo de todas as linguagens FP como F #, Scala, Haskell e junto com tipos de produtos chamados tipos de dados algébricos. Exemplos padrão de tipos de soma com uma caixa vazia seriam opcionais de F # e Maybe from Haskell. O TypeScript não tem ADTs, mas em vez disso tem uma discussão em andamento sobre a adição de não anuláveis ​​que modelariam um caso especial do que os ADTs teriam coberto. Portanto, minha mensagem é, livre-se dos não anuláveis ​​para os ADTs.

@spion , Más notícias: F # obteve nulos (como legado do .NET). Boas notícias: ninguém os usa. Como você pode não usar null quando ele está lá? Eles têm opcionais (assim como o Java mais recente, como você mencionou). Então você não precisa do null se tiver uma escolha melhor à sua disposição. Em última análise, estou sugerindo isso: deixe os nulos (não nulos) em paz, apenas esqueça que existem e implemente os ADTs como um recurso de linguagem.

Usamos null, mas não da mesma forma que vocês usam. O código-fonte da minha empresa vem em 2 partes.

1) Quando se trata de dados (como dados de banco de dados), substituímos nulos por dados em branco no momento da declaração da variável.

2) Todos os outros, como objetos programáveis, usamos null para sabermos quando há um bug e uma lacuna no código-fonte onde os objetos não são criados ou atribuíveis que não são objetos javascript necessários. O indefinido é o problema do objeto javascript em que há um bug ou lacuna no código-fonte.

Os dados não queremos que sejam anuláveis ​​porque são dados do cliente e eles verão formulações nulas.

@aleksey-bykov typescript tem adt com tipo de união, a única coisa que falta é a correspondência de padrões, mas esse é um recurso que simplesmente não pode ser implementado com a filosofia do typescript de gerar javascript próximo à fonte original.

Por outro lado, é impossível definir com o tipo de união a exclusão do valor nulo, por isso precisamos do tipo não nulo.

@fdecampredon , TS não tem ADT's, tem uniões que não são tipos de soma, porque, como você disse, 1. eles não podem modelar uma caixa vazia corretamente porque não há tipo de unidade, 2. não há maneira confiável de desestruturar eles

a correspondência de padrões para ADTs pode ser implementada de forma alinhada com o JavaScript gerado; de qualquer forma, espero que esse argumento não seja um ponto de inflexão

@ aleksey-bykov isso não é F #. É uma linguagem que visa modelar os tipos de JavaScript. Bibliotecas JavaScript usam valores nulos e indefinidos. Portanto, esses valores devem ser modelados com os tipos apropriados. Visto que nem mesmo o ES6 oferece suporte a tipos de dados algébricos, não faz sentido usar essa solução, dados os objetivos de design do TypeScript

Além disso, os programadores de JavaScript normalmente usam as verificações if (em conjunto com os testes typeof e de igualdade), em vez de correspondência de padrões. Eles já podem restringir os tipos de união do TypeScript. A partir deste ponto, é apenas um pequeno passo para oferecer suporte a tipos não anuláveis ​​com benefícios comparáveis ​​ao algébrico Maybe etc.

Estou surpreso que ninguém realmente mencionou as grandes mudanças que lib.d.ts pode precisar para introduzir e problemas potenciais do estado nulo transitório dos campos de classe durante a invocação do construtor. Esses são alguns problemas potenciais reais para implementar tipos não anuláveis ​​...

A ideia do

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

A ideia é que você verifique se o nome é nulo apenas uma vez e execute todo o resto do código sem se preocupar com a adição de verificações de nulos.

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

E agora você está ótimo! O sistema de tipos garante que doThingsWith será chamado com um nome que não é nulo

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

Nenhuma dessas funções precisa verificar se há um valor nulo, e o código ainda funcionará sem lançar. E, assim que você tentar passar uma string anulável para uma dessas funções, o sistema de tipos avisará imediatamente que você cometeu um erro:

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

Este é um grande benefício: agora o sistema de tipos rastreia se as funções podem manipular valores anuláveis ​​ou não e, além disso, ele rastreia se eles realmente precisam ou não. Código muito mais limpo, menos verificações, mais segurança. E não se trata apenas de strings - com uma biblioteca como verificações de tipo em tempo de execução, você também pode construir protetores de tipo para dados muito mais complexos

E se você não gosta do rastreamento porque acha que não vale a pena modelar a possibilidade de um valor nulo, você pode voltar ao bom e velho comportamento inseguro:

declare function getName(personId:number):string;

e, nesses casos, o texto datilografado só irá avisá-lo se você fizer algo que está obviamente errado

uppercasedName(null);

Francamente, não vejo desvantagens, exceto quanto à compatibilidade com versões anteriores.

@fdecampredon Os tipos de união são apenas isso, sindicatos. Eles não são sindicatos _disjoint_, também conhecidos como somas. Veja # 186.

@ aleksey-bykov

Observe que adicionar um tipo de opção ainda será uma alteração significativa.

// 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>

Afinal, você pode obter os tipos de opções de um homem pobre agora mesmo com a desestruturação

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

mas o único valor disso é se lib.d.ts (e qualquer outro .d.ts, e toda a sua base de código, mas assumiremos que podemos consertar isso) também usá-lo, caso contrário, você voltará a não saber se um função que não usa Option pode retornar nulo ou não, a menos que você olhe seu código.

Observe que também não sou a favor de tipos não nulos por padrão (não para TS 1.x de qualquer maneira). É uma mudança muito grande.

Mas digamos que estejamos falando sobre 2.0. Se vamos ter uma alteração significativa de qualquer maneira (adicionando tipos de opção), por que não tornar os tipos não anuláveis ​​por padrão também? Tornar os tipos não anuláveis ​​por padrão e adicionar tipos de opção não é exclusivo. O último pode ser autônomo (por exemplo, em F #, como você indicou), mas o primeiro requer o último.

@Arnavion , há algum mal-entendido, eu não disse que precisamos substituir as assinaturas, todas as assinaturas existentes permanecem intactas, é para os novos desenvolvimentos que você está livre para ir com o ADT ou o que quiser. Portanto, sem alterações significativas. Nada é tornado não nulo por padrão.

Se os ATDs estiverem aqui, cabe ao desenvolvedor envolver todos os lugares onde os nulos podem vazar para o aplicativo, transformando-os em opcionais. Esta pode ser uma ideia para um projeto autônomo.

@ aleksey-bykov

Eu não disse que precisamos substituir as assinaturas, todas as assinaturas existentes permanecem intactas

_I_ disse que as assinaturas precisam ser substituídas, e já dei o motivo:

mas o único valor disso é se lib.d.ts (e qualquer outro .d.ts, e toda a sua base de código, mas assumiremos que podemos consertar isso) também usá-lo, caso contrário, você voltará a não saber se um função que não usa Option pode retornar nulo ou não, a menos que você olhe seu código.


Nada é tornado não nulo por padrão. Sempre.

Para TS 1.x, concordo, apenas porque é uma alteração muito grande. Para 2.0, usar o tipo de opção nas assinaturas padrão (lib.d.ts etc.) já seria uma alteração importante. Tornar os tipos não anuláveis ​​por padrão _além disso_ vale a pena e não traz desvantagens.

Eu discordo, a introdução de opcionais não deve quebrar nada, não é como se usássemos opcionais ou anuláveis ​​ou não anuláveis. Todo mundo usa o que quiser. A maneira antiga de fazer as coisas não deve depender de novos recursos. Cabe ao desenvolvedor usar uma ferramenta apropriada para suas necessidades imediatas.

Então você está dizendo que se eu tenho uma função foo que retorna Option <number> e uma função bar que retorna um número, não tenho permissão para ter certeza de que bar não pode retornar nulo a menos que eu olhe para a implementação de bar ou mantenha a documentação "Esta função nunca retorna nulo."? Você não acha que isso pune funções que nunca retornarão nulas?

a função bar do seu exemplo era conhecida como anulável desde o início, foi usada em cerca de 100.500 aplicativos e todos trataram o resultado como anulável, agora você deu uma olhada dentro dela e descobriu que nulo é impossível, isso significa que você deve prosseguir e alterar a assinatura de anulável para não anulável? Eu acho que você não deveria. Porque esse conhecimento, embora valioso, não vale a pena frear 100.500 aplicações. O que você deve fazer é criar uma nova biblioteca com assinaturas revisadas que faça assim:

old_lib.d.ts

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

revisado_lib.d.ts

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

agora os aplicativos legados continuam usando old_lib.d.ts , para novos aplicativos, um desenvolvedor pode escolher revised_libs.d.ts

Infelizmente, revision_libs.d.ts tem 50 outras funções que ainda não examinei, e todas elas retornam número (mas não sei se é realmente um número ou um número anulável). E agora?

Bem, não tenha pressa, peça ajuda, use versionamento (dependendo do nível de conhecimento que você adquiriu até agora, você pode querer liberá-lo gradativamente com o número de versão cada vez maior: revised_lib.v-0.1.12.d.ts )

Na verdade, não é necessário. Uma função que retorna um tipo anulável na assinatura, mas um tipo não anulável na implementação, apenas resulta na verificação de erro redundante pelo responsável pela chamada. Não compromete a segurança. Anotando gradualmente mais e mais funções com! conforme você os descobrir, funcionará muito bem, como você disse.

Eu não sou fã de! apenas porque é mais bagagem para digitar (tanto em termos de teclas como a necessidade de lembrar de usá-la). Se quisermos não nulidade em 1.x, então! é uma das opções já discutidas acima, mas eu ainda diria que vale a pena ter uma eventual mudança de rompimento com 2.0 e tornar a não nulidade o padrão.

Por outro lado, talvez isso leve a uma situação no estilo Python 2/3, onde ninguém atualiza para o TS 2.0 por anos porque não pode se dar ao luxo de passar por sua base de código de um milhão de linhas, certificando-se de que cada declaração e classe de variável membro e parâmetro de função e ... é anotado com? se pode ser nulo. Mesmo 2to3 (a ferramenta de migração Python 2 para 3) não precisa lidar com mudanças abrangentes como essa.

Se 2.0 pode ser uma mudança significativa depende da equipe de TS. Eu votaria a favor, mas não tenho uma base de código de um milhão de linhas que precise ser corrigida, então talvez meu voto não conte.

Talvez devêssemos perguntar ao pessoal do Funscript como eles reconciliam a API DOM que retorna nulos com F # (Funscript usa lib.d.ts e outros .d.ts do TypeScript para uso do código F #). Eu nunca usei, mas olhando para http://funscript.info/samples/canvas/index.html por exemplo, parece que o provedor de tipo não acha que document.getElementsByTagName ("canvas") [0] pode ser Indefinido.

Edit: aqui parece que não se espera que document.getElementById () retorne null. No mínimo, ele não parece estar retornando Option <Element> visto que está acessando .onlick no resultado.

@spion
Obrigado, eu não tinha pensado nisso.

No trabalho, nossa base de código não é pequena, e essa mudança significativa que as pessoas querem nos atrasaria muito com muito pouco ganho. Por meio de bons padrões e comunicação clara entre nossos desenvolvedores, não tivemos problemas com nulos aparecendo onde não deveriam.
Estou sinceramente surpreso que algumas pessoas pressionem tanto por isso.
Nem é preciso dizer que isso nos traria poucos benefícios e nos custaria muito tempo.

@Griffork olhou para esta lista filtrada de problemas no compilador de texto digitado para uma estimativa de quão grande benefício essa mudança poderia trazer. Todos os bugs de "travamento" listados nesse link podem ser evitados usando tipos não anuláveis. E estamos falando sobre o incrível nível de padrões, comunicação e revisão de código da Microsoft aqui.

Com relação à quebra, eu acho que se você continuar usando as definições de tipo existentes, é possível que você não obtenha nenhum erro, exceto o compilador apontando variáveis ​​potencialmente não inicializadas e campos vazando. Por outro lado, você pode obter muitos erros, particularmente, por exemplo, se os campos de classe forem frequentemente deixados não inicializados em construtores em seu código (para serem inicializados mais tarde). Portanto, entendo suas preocupações e não estou pressionando por uma alteração incompatível com versões anteriores para o TS 1.x. Ainda espero ter conseguido persuadi-lo de que, se alguma mudança na linguagem valeu a pena quebrar a compatibilidade com versões anteriores, é esta.

Em qualquer caso, o Flow do Facebook tem tipos não anuláveis. Quando estiver mais maduro, pode valer a pena investigar como um substituto do TS para aqueles de nós que se preocupam com esse problema.

@spion , o único número que sua lista nos dá é quantas vezes nulo ou indefinido foram mencionados por algum motivo, basicamente diz apenas que nulo e indefinido foram falados, espero que esteja claro que você não pode usá-lo como um argumento

@aleksey-bykov Não estou - eu olhei para _todos_ os problemas naquela lista filtrada e cada um que tinha a palavra "travar" estava relacionado a um rastreamento de pilha que mostra que uma função tentou acessar uma propriedade ou um método de um valor indefinido ou nulo.

Tentei restringir o filtro com diferentes palavras-chave e (acho que) consegui obter todas

@spion
Pergunta: quantos desses erros são causados ​​em locais que eles precisariam para marcar a variável como anulável ou indefinível?

EX .: Se um objeto pode ter um pai e você inicializa o pai como nulo, e sempre vai ter um objeto com um pai que é nulo, você ainda terá que declarar o pai como possivelmente nulo.
O problema aqui é se um programador escreve algum código com a suposição de que um loop sempre será interrompido antes de atingir o pai nulo. Isso não é um problema com null estar no idioma, exatamente a mesma coisa que aconteceria com undefined .

Razões para manter o valor nulo tão fácil de usar como agora:
• É um valor padrão melhor do que indefinido.
. 1) é mais rápido verificar em alguns casos (nosso código deve ter muito desempenho)
. 2) faz com que os loops in funcionem de forma mais previsível nos objetos.
. 3) faz com que o uso de array faça mais sentido ao usar nulos para valores em branco (em oposição a valores ausentes). Observe que delete array[i] e array[i] = undefined têm comportamentos diferentes ao usar indexOf (e provavelmente outros métodos de array populares).

O que eu sinto como resultado de fazer nulos requer uma marcação extra para uso na linguagem:
• Recebi um erro indefinido em vez de um erro nulo (que é o que aconteceria na maioria dos cenários de texto digitado).

Quando eu disse que não temos problemas com o escape de nulos, quis dizer que as variáveis ​​que não são inicializadas como nulas nunca se tornam nulas, ainda obteremos erros de exceção nula exatamente no mesmo lugar em que obteríamos erros indefinidos (como faz a equipe do Typescript ) Tornar null mais difícil de usar (exigindo sintaxe extra) e deixar o mesmo indefinido irá causar mais problemas para alguns desenvolvedores (por exemplo, nós).

Adicionar sintaxe extra para usar nulo significa que, por várias semanas / meses, os desenvolvedores que usam muito nulo estarão cometendo erros à esquerda, à direita e no centro enquanto tentam se lembrar da nova sintaxe. E será outra maneira de escorregar no futuro (anotando algo ligeiramente incorreto). [É hora de apontar que odeio a ideia de usar símbolos para representar tipos, isso torna a linguagem menos clara]

Até que você possa me explicar uma situação em que null causa um erro ou problema que undefined não causaria, então não concordarei em tornar null significativamente mais difícil de usar do que undefined. Ele tem seu caso de uso, e só porque não está ajudando você , não significa que a mudança significativa que você deseja (que _fará_ prejudicará o fluxo de trabalho de outros desenvolvedores) deva seguir em frente.

Conclusão:
Não faz sentido ser capaz de declarar tipos não anuláveis ​​sem ser capaz de definir tipos não indefinidos. E os tipos não indefinidos não são possíveis devido à forma como o javascript funciona.

@Griffork quando digo não anulável, quero dizer não anulável ou indefinido. E não é verdade que não seja possível devido à forma como o JS funciona. Com os novos recursos de proteção de tipo, uma vez que você usa uma proteção de tipo, você sabe que o valor que flui de lá não pode mais ser nulo ou indefinido. Há uma compensação envolvida nisso também, e eu apresento a implementação do Fluxo do Facebook como prova de que é factível.

O _ único_ caso que se tornará um pouco mais difícil de usar é este: vou atribuir nulo temporariamente a esta variável e depois usá-lo neste outro método como se não fosse nulo, mas sei que nunca chamarei esse outro método antes inicializando a variável primeiro, portanto, não há necessidade de verificar se há nulos. Este é um código muito frágil, e eu definitivamente receberia bem o compilador me avisando sobre isso: é um refatorador longe de ser um bug de qualquer maneira.

@spion
Eu acredito que estou finalmente entendendo de onde você está vindo.

Acredito que você esteja querendo proteções de tipo para ajudar a determinar quando um valor não pode ser nulo e permitir que você chame funções que não verificam se há nulo dentro. E se a instrução if guarding for removida, isso se tornará um erro.

Eu posso ver isso sendo útil.

Também acredito que essa não será realmente a bala de prata que você espera.

Um compilador que não executa seu código e testa cada faceta dele não será melhor para determinar onde estão indefinidos / nulos do que o programador que escreveu o código. Estou preocupado com o fato de que essa mudança pode levar as pessoas a uma falsa sensação de segurança e, na verdade, tornar os erros nulos / indefinidos mais difíceis de rastrear quando ocorrerem.
Realmente, acho que a solução de que você precisa é um bom conjunto de ferramentas de teste que suportam o Typescript, que pode reproduzir esse tipo de bugs em Javascript, ao invés de implementar um tipo em um compilador que não pode cumprir sua promessa.

Você mencionou o Flow como tendo uma solução para este problema, mas ao ler seu link, vi algumas coisas preocupantes:

"O fluxo às vezes também pode ajustar os tipos de propriedades do objeto, especialmente quando não há operações intermediárias entre uma verificação e um uso. Em geral, porém, o aliasing de objetos limita o escopo desta forma de raciocínio , uma vez que uma verificação na propriedade de um objeto pode ser invalidado por uma gravação nessa propriedade por meio de um alias, e é difícil para uma análise estática rastrear os aliases com precisão . "

"Valores indefinidos, assim como nulos, também podem causar problemas. Infelizmente, valores indefinidos são onipresentes em JavaScript e é difícil evitá-los sem afetar seriamente a usabilidade da linguagem [...] O fluxo faz uma troca neste caso: ele detecta variáveis ​​locais indefinidas e valores de retorno, mas ignora a possibilidade de indefinidos resultantes de propriedade de objeto e acessos a elementos de array . "

Agora, indefinido e nulo funcionam de forma diferente, erros indefinidos ainda podem aparecer em todos os lugares, o ponto de interrogação não pode garantir que o valor não seja nulo e a linguagem se comporta de forma mais diferente do Javascript (que é o que o TS está tentando evitar pelo que vi )

ps

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 já é vulnerável a efeitos de aliasing (como qualquer linguagem que permite valores mutáveis). Isso será compilado sem erros:

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

Os documentos do Flow estão afirmando isso apenas para integridade.

Edit: Melhor exemplo.

Velho:

Sim, mas eu argumento que o meio de definir algo como indefinido é muito maior do que o meio de definir algo com outro valor.

Quero dizer,

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, neste ponto é basicamente uma preferência pessoal. Depois de usar javascript por muito tempo, não acho que essa sugestão vá ajudar a linguagem. Acho que vai acalmar as pessoas com uma falsa sensação de segurança, e acho que vai afastar o Typescript (como linguagem) do Javascript.

@Griffork Para obter um exemplo de como o texto datilografado atual leva você a uma falsa sensação de segurança, tente o exemplo que você apresentou no playground:

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

A propósito, o operador delete poderia ser tratado da mesma maneira que atribuir um valor do tipo null e isso seria suficiente para cobrir o seu caso também.

A afirmação de que a linguagem se comportará de maneira diferente do JavaScript é verdadeira, mas sem sentido. O TypeScript já tem um comportamento diferente do JavaScript. O objetivo de um sistema de tipos sempre foi desautorizar programas que não fazem sentido. A modelagem de tipos não anuláveis ​​simplesmente adiciona algumas restrições extras. Não permitir a atribuição de valores nulos ou indefinidos a uma variável do tipo não anulável é exatamente o mesmo que proibir a atribuição de um número a uma variável do tipo string. JS permite ambos, TS não pode permitir nenhum

@spion

a ideia é evitar verificações nulas em todo o seu código

Se eu entender o que você está defendendo:

A. Tornar todos os tipos não nulos por padrão.
B. Marque os campos e variáveis ​​que podem ser anulados.
C. Certifique-se de que o desenvolvedor do aplicativo / biblioteca verifique todos os pontos de entrada no aplicativo.

Mas isso não significa que o ônus de garantir que o código de alguém esteja livre de nulos recaia sobre a pessoa que o escreve, e não sobre o compilador? Estamos efetivamente dizendo ao compilador "dw, não estou permitindo a entrada de nulos no sistema."

A alternativa é dizer que os nulos estão em todos os lugares, então não se preocupe, mas se algo for não anulável, avisarei você.

O fato é que a última abordagem está sujeita a exceções de referência nula, mas é mais verdadeira. Fingir que um campo em um objeto obtido através do fio (ou seja, ajax) é não nulo implica ter fé em Deus: smiley:.

Acredito que haja uma forte discordância sobre este assunto porque, dependendo do que se está trabalhando, o item C acima pode ser trivial ou inviável.

@jbondc Estou feliz que você perguntou isso. Na verdade, CallExpression é marcada como indefinida ou anulável. No entanto, o sistema de tipos atualmente não tira nenhuma vantagem disso - ele ainda permite todas as operações em typeArguments como se não fosse nulo ou indefinido.

No entanto, ao usar os novos tipos de união em combinação com tipos não anuláveis, o tipo pode ser expresso como NodeArray<TypeNode>|null . Então, o sistema de tipo não permitirá nenhuma operação nesse campo, a menos que uma verificação nula seja aplicada:

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

// callSomethingOn doesn't need to perform any checks

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

Com a ajuda dos protetores do tipo TS 1.4, dentro do bloco if, o tipo da expressão será reduzido a NodeArray<TypeNode> que, por sua vez, permitirá todas as operações NodeArray naquele tipo; além disso, todas as funções chamadas nessa verificação serão capazes de especificar seu tipo de argumento como NodeArray<TypeNode> sem realizar mais verificações, nunca.

Mas se você tentar escrever

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

o compilador irá avisá-lo sobre isso em tempo de compilação, e o bug simplesmente não teria acontecido.

Então não, @NoelAbrahams , não se trata de saber tudo com certeza. É sobre o compilador ajudando você a dizer qual valor uma variável ou campo pode conter, assim como com todos os outros tipos.

Claro, com valores externos, como sempre, cabe a você especificar quais são seus tipos. Você sempre pode dizer que os dados externos contêm uma string em vez de um número, e o compilador não reclamará, mesmo assim, seu programa irá travar ao tentar fazer operações de string no número.

Mas sem tipos não anuláveis, você nem mesmo tem a capacidade de dizer que um valor não pode ser nulo. O valor nulo pode ser atribuído a cada tipo e você não pode fazer nenhuma restrição sobre ele. As variáveis ​​podem ser deixadas não inicializadas e você não receberá nenhum aviso, pois undefined é um valor válido para qualquer tipo. Portanto, o compilador não pode ajudá-lo a detectar erros nulos e relacionados a indefinidos em tempo de compilação.

Acho surpreendente que haja tantos equívocos sobre tipos não anuláveis. Eles são apenas tipos que não podem ser deixados sem inicializar ou não podem ser atribuídos aos valores null ou undefined . Isso não é tão diferente de ser incapaz de atribuir uma string a um número. Este é meu último post sobre esse assunto, pois sinto que não estou realmente chegando a lugar nenhum. Se alguém estiver interessado em saber mais, recomendo começar com o vídeo "O erro de um bilhão de dólares" que mencionei acima. O problema é bem conhecido e resolvido por muitas linguagens e compiladores modernos com sucesso.

@spion , concordo inteiramente com todos os benefícios de poder afirmar se um tipo pode ou não ser nulo. Mas a questão é: você deseja que os tipos sejam não nulos _por padrão _?

Fingir que um campo em um objeto obtido através do fio (ou seja, ajax) é não nulo implica ter fé em Deus

Portanto, não finja. Marque-o como anulável e você será forçado a testá-lo antes de usá-lo como não anulável.

Certo. Tudo se resume a se queremos marcar um campo como anulável (em um sistema onde os campos não são nulos por padrão) ou se queremos marcar um campo como não anulável (em um sistema onde os campos são anuláveis ​​por padrão).

O argumento é que a primeira é uma alteração significativa (que pode ou não ser significativa) e também insustentável porque exige que o desenvolvedor do aplicativo verifique e garanta todos os pontos de entrada.

@NoelAbrahams Eu não vejo porque é 'intenível' basicamente na maioria das vezes você não quer nulo, também quando um ponto de entrada pode retornar nulo você terá que verificá-lo , no final um sistema de tipo com tipo não nulo como padrão, permite que você faça menos checagens de nulos porque você poderá confiar em alguma api / biblioteca / ponto de entrada em seu aplicativo.

Quando você pensa um pouco sobre isso, marcar um tipo como não nulo em um sistema de tipo anulável tem valor limitado, você ainda poderá consumir variável digitada anulável / tipo de retorno sem ser forçado a testá-lo.
Isso também forçará o autor da definição a escrever mais código, já que na maioria das vezes uma biblioteca bem projetada nunca retorna um valor nulo ou indefinido.
Finalmente, mesmo o conceito é estranho, em um sistema de tipos com tipo não anulável, um tipo anulável é perfeitamente expressável como um tipo de união: ?string é o equivalente a string | null | undefined . Em um sistema de tipo com tipo anulável como padrão, onde você pode marcar o tipo como não anulável, como você expressaria !string ? string - null - undefined ?

No final, eu realmente não entendo a preocupação das pessoas aqui, null não é uma string, da mesma forma que 5 não é uma string, ambos os valores não serão capazes de ser usado onde uma string é esperada, e deixar escapar var myString: string = null é tão sujeito a erros quanto: var myString: string = 5 .
Ter atribuível nulo ou indefinido a qualquer tipo talvez seja um conceito com o qual o desenvolvedor esteja familiarizado, mas ainda é ruim.

Acho que não estava totalmente correto no meu post anterior: vou culpar a hora.

Acabei de examinar alguns de nossos códigos para ver como as coisas funcionariam e certamente ajudaria a marcar certos campos como anuláveis, por exemplo:

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

Mas o que eu faço objeções é o seguinte:

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

Acho que isso vai interferir na maneira natural de trabalhar com JavaScript.

O mesmo pode se aplicar com o 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

E ainda é válido em JavaScript

Razoavelmente, quantas vezes você voluntariamente atribui null a uma propriedade de um objeto?

Eu acho que a maioria das coisas seriam marcadas como nulas, mas você estaria contando com protetores de tipo para declarar que o campo agora é não anulável.

@fdecampredon
Bastante, na verdade.

@Griffork ,

Eu acho que a maioria das coisas seriam marcadas como nulas

Esse foi meu pensamento inicial. Mas depois de passar por algumas seções do nosso código, encontrei comentários como os seguintes:

interface MyType {

     name: string;

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

A ideia de que um campo pode ser anulado é freqüentemente usada para fornecer informações. E o TypeScript detectará erros se exigir um protetor de tipo ao acessar wikiDate .

@fdecampredon

foo.name = 5 // error
E ainda é válido em JavaScript

Verdade, mas isso é um erro porque o TypeScript sabe com 100% de certeza que não foi intencional.

enquanto

foo.name = undefined; // Não envie o nome para o servidor

é perfeitamente intencional.

Acho que a implementação que mais se adequaria aos nossos requisitos é não usar tipos de união, mas seguir a sugestão original:

 wikiDate: ?Date;

Eu concordo com @NoelAbrahams

foo.name = 5 // erro
E ainda é válido em JavaScript
Verdade, mas isso é um erro porque o TypeScript sabe com 100% de certeza que não foi intencional.

O compilador apenas sabe que você marcou o nome como string e não string | number se você quiser um valor anulável, basta marcá-lo como ?string ou string | null (que é bastante equivalente)

Acho que a implementação que mais se adequaria aos nossos requisitos é não usar tipos de união, mas seguir a sugestão original:

wikiDate:? Date;

Portanto, concordamos que os tipos não são nulos por padrão e você marcaria como anulável com ? :).
Observe que seria um tipo de união, já que ?Date seria o equivalente a Date | null | undefined :)

Oh, desculpe, eu estava tentando concordar em anulável por padrão e não nulo com digitação especial (os símbolos são confusos).

@fdecampredon , na verdade o que significa é que quando um campo ou variável é marcado como anulável, um tipo de proteção é necessário para o acesso:

var wikiDate: ?Date;

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

Esta não é uma alteração significativa, porque ainda devemos ser capazes de fazer isso:

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

Talvez você acredite que não podemos ter isso sem introduzir null nos tipos de união?

Seu primeiro exemplo está absolutamente certo quando você faz:

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

você usa um protetor de tipo e o compilador não deve avisar nada.

No entanto, o seu segundo exemplo não é bom

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

o compilador deve ter um erro aqui, um algoritmo simples pode apenas errar na primeira linha (variável unitializada não marcada como anulável), um mais complexo pode tentar detectar a atribuição antes do primeiro 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.

Não sei exatamente onde colocar a barreira, mas com certeza precisaria de algum tipo de ajuste sutil para não atrapalhar o desenvolvedor.

É uma alteração importante e não pode ser introduzida antes do 2.0, exceto talvez com a diretiva 'use nonnull' proposta por @metaweta

Por que não ter:

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

E tem um sinalizador de compilador (que só funciona se -noimplicitany estiver ativado) que diz -noinferrednulls, que desativa a sintaxe normal para tipos (como string e int) e você precisa fornecer um? ou ! com eles (nulo, indefinido e quaisquer tipos sendo exceções).

Dessa forma, os não anuláveis ​​são uma opção e você pode usar o sinalizador do compilador para forçar um projeto a ser explicitamente nulo.
O compilador sinaliza erros na atribuição de tipos , não depois (como na proposta anterior).

Pensamentos?

Edit: eu escrevi isso porque força a ideia de usar não nulo para ser explícito em todas as ações. Qualquer pessoa que ler o código proveniente de qualquer outro projeto de TS saberá exatamente o que está acontecendo. Além disso, o sinalizador do compilador se torna muito óbvio se estiver ativado (já que blah: string é um erro, mas blah:!string não é, semelhante à maneira como -noimplicitany funciona).

Edit2:
DefinatelyTyped pode então ser atualizado para suportar noninferrednulls, e eles não mudarão o uso das bibliotecas se as pessoas optarem por não aceitar o? e ! característica.

Eu não me importo se não nulo e não indefinido são opt-in, opt-out, com
modificadores de tipo (!?), uma diretiva ou um sinalizador do compilador; Eu vou fazer o que for
leva para obtê-los _ contanto que sejam possíveis de expressar_, o que não é
atualmente o caso.

Na segunda-feira, 22 de dezembro de 2014 às 14h35, Griffork [email protected] escreveu:

Por que não ter:

var string1: string; // isso funciona como o typescript atualmente., não precisa de proteção de tipo antes do uso, null e undefined podem ser atribuídos a ele.
string1.length; // worksvar string2:! string; // isso não funciona porque a string deve ser atribuída a um valor não nulo e não indefinido, não precisa de proteção de tipo antes de usar.var string3:? string; // deve ser protegido por tipo para não nulo, não indefinido antes de usar.var string4:! string | null = null; // Pode ser nulo, mas nunca deve ser indefinido (e deve ser protegido por tipo antes de usar) .var string5:! String | undefined; // isso nunca pode ser nulo, mas pode ser indefinido (e deve ser protegido por tipo antes do uso).

E tem um sinalizador de compilador (que só funciona se -noimplicitany estiver ativado) que
diz -noinferrednulls, que desativa a sintaxe normal para tipos (como
string e int) e você deve fornecer um? ou ! com eles (nulo, indefinido
e qualquer exceção sendo).

Desta forma, não anuláveis ​​são opt-in, e você pode usar o compilador
sinalizador para forçar um projeto a ser explicitamente nulo.
O compilador sinaliza erros na redução de types_, não depois (como
proposta anterior).

Pensamentos?

Responda a este e-mail diretamente ou visualize-o no 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 seria uma opção, mas ruim na minha opinião, e vou explicar por quê:

  • Isso causará muito mais trabalhos nos arquivos de definição, pois agora teremos que verificar e anotar cada tipo para ter a definição correta.
  • Acabaremos escrevendo !string (e às vezes ?string ) em todos os lugares do código, o que tornaria o código muito menos legível.
  • !string em um sistema onde os tipos são anuláveis ​​é um conceito estranho, a única maneira que você não pode realmente descrevê-lo é string minus null minus undefined , pelo contrário ?string é muito simples de descrever em um sistema de tipos onde os tipos são nulos por padrão string | null | undefined .
  • Prevejo muita dor de cabeça (e perda de desempenho) para encontrar um algoritmo de verificação de tipo onde o compilador entende que string | null requer um protetor de tipo, mas string não, você basicamente introduz um conceito onde algum tipo de união deve ser tratado de forma diferente de outro.
  • E, finalmente, a pior parte, perdemos completamente a inferência de tipo var myString = "hello" que myString deveria ser string , ?string ou !string ? honestamente, uma grande dor de cabeça em perspectiva aqui.

Se não tivermos o tipo não nulo como padrão, a melhor proposta que vi aqui é a diretiva 'use non-null' proposta por @metaweta.
Claro que precisa ser especificado de forma adequada, mas pelo menos com apenas uma string use non-null em todo o nosso arquivo, podemos obter um comportamento simples e previsível.

@fdecampredon

  1. Pode ser muito mais trabalhoso em arquivos de definição, mas _você teria que fazer isso de qualquer maneira_ (para garantir que os tipos estão corretos) e desta vez o compilador iria lembrá-lo do que você ainda não editou (se estiver usando -noimplicitnull )
  2. Estou aberto a outras sugestões de anotação. Eu honestamente acredito que o sistema de tipo atual tem seu lugar e não deve ser _substituído_. Eu não acho que uma mudança significativa vale a pena. Em vez disso, acho que deveríamos encontrar uma maneira melhor de descrever o que você procura. (Eu realmente não gosto da ideia de representar nada disso com símbolos, eles não são intuitivos.)
  3. O que é difícil de descrever sobre isso? Já vi discussões em outros lugares no texto datilografado onde esta solicitação (para certos tipos) foi proposta (sem um marcador). Fiz uma pesquisa rápida e não consegui encontrar o problema, irei procurar mais depois.

  4. Se você está se referindo ao que escrevi como !string|null , isso funcionaria no sistema atual se nulo fosse tratado _como_ {} (mas não fosse atribuível a ele).
    Se você está falando sobre string|null que eu não tinha em minha lista, então acho que null deve ser ignorado neste caso. Nulo e indefinido somente fazem sentido em sindicatos onde cada não nulo e não indefinido é precedido por um! e any não é qualquer (pode ser um aviso / erro do compilador).
  5. Boa pergunta, e que só surge se você estiver usando a opção -noimplicitnull, acho que a opção mais segura seria atribuí-la a qualquer opção com maior probabilidade de causar um erro inicial (provavelmente anulável), mas eu recebo a sentindo que há uma ideia melhor na qual não estou pensando. Será que mais alguém tem alguma sugestão de como isso deve ser abordado?

Editar: adicionado ao ponto 2.
Editar: erro de digitação corrigido no ponto 4.

Pode ser muito mais trabalhoso em arquivos de definição, mas você teria que fazer esse trabalho de qualquer maneira (para garantir que os tipos estão corretos) e desta vez o compilador iria lembrá-lo do que você ainda não editou (se estiver usando -noimplicitnull )

Sim e não, verifique a biblioteca que você usa e veja quanto realmente retorna nulo ou indefinido.
é um caso muito raro, só podemos encontrar neste problema muito poucas ocorrências para a lib padrão e, por exemplo, a biblioteca Promise nunca faz isso.
Meu ponto é que em um sistema de tipos onde os tipos não são anuláveis ​​por padrão, a maioria dos arquivos de definição existentes já são válidos .

Estou aberto a outras sugestões de anotação. Eu honestamente acredito que o sistema de tipo atual tem seu lugar e não deve ser substituído. Eu não acho que uma mudança significativa vale a pena. Em vez disso, acho que deveríamos encontrar uma maneira melhor de descrever o que você procura. (Eu realmente não gosto da ideia de representar nada disso com símbolos, eles não são intuitivos.)

Não creio que exista tal caminho, mas espero estar enganado, pois tem um valor inestimável na minha opinião.
Para a parte das últimas alterações, por que você é tão contra a diretiva 'use non-null' ?
Os desenvolvedores que desejam um sistema de tipo anulável não seriam afetados de forma alguma (a menos que já adicionem 'use non-null' estranhamente no início de seu arquivo, mas honestamente isso seria um pouco ... estranho)
E os desenvolvedores que desejam um sistema de tipo não nulo podem apenas usar a nova diretiva.

O que é difícil de descrever sobre isso? Já vi discussões em outros lugares no texto datilografado onde esta solicitação (para certos tipos) foi proposta (sem um marcador). Fiz uma pesquisa rápida e não consegui encontrar o problema, irei procurar mais depois.
Só acho o conceito um pouco 'estranho' e não muito claro, geralmente uso a composição como principal ferramenta de programação, não a _decomposição_, mas por que não.

Se você está se referindo ao que escrevi como! String | null, isso funcionaria no sistema atual se null fosse tratado como {} (mas não pudesse ser atribuído a ele). Se você está falando sobre string | null que eu não tinha em minha lista, então acho que null deve ser ignorado neste caso. Nulo e indefinido somente fazem sentido em sindicatos onde cada não nulo e não indefinido é precedido por um! e any não é qualquer (pode ser um aviso / erro do compilador).
Estou me referindo ao fato de que quando você decompõe os 3 tipos:

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

Os 2 últimos basicamente não têm diferença, mas o compilador deve saber que para string não deve forçar a verificação de proteção de tipo e para ?string deve, essa informação terá que ser propagada em todos os lugares, o algoritmo será muito mais complexo do que realmente é, e tenho certeza de que poderia encontrar casos estranhos com a inferência de tipo.

Boa pergunta, e que só surge se você estiver usando a opção -noimplicitnull, acho que a opção mais segura seria atribuí-la a qualquer opção com maior probabilidade de causar um erro inicial (provavelmente anulável), mas eu recebo a sentindo que há uma ideia melhor na qual não estou pensando. Será que mais alguém tem alguma sugestão de como isso deve ser abordado?

Não faria isso iria basicamente introduzir o mesmo problema que @RyanCavanaugh comentou quando pensei em apenas introduzir um sinalizador que permitiria mudar de nulo como padrão para nulo como padrão.
Nesse caso, a primeira proposição era muito mais simples.

Mais uma vez, por que você é contra a diretiva 'use non-null' Quanto mais penso nisso, mais me parece a solução ideal.

@fdecampredon
Porque o "uso não nulo" como foi proposto atualmente muda a forma como a linguagem é _usada_ e não a forma como a linguagem é _escrita_. O que significa que uma função de um local quando movida para outro local pode funcionar de maneira diferente quando é _usada_. Você obterá erros de tempo de compilação que estão potencialmente entre 1 e 3 arquivos de distância porque algo foi digitado incorretamente.

A diferença entre:
string
e
?string
É esse o ? e ! símbolo está pedindo uma verificação de tipo estrita para esta variável (muito parecido com a sua diretiva "use nonnull", mas em uma base por variável). Explique dessa forma e eu acho que você não terá muitos problemas com as pessoas entendendo isso.

A diferença é claro:
Arquivo 1:

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

Arquivo 2:

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

Os desenvolvedores agora precisam manter um mapa mental de quais arquivos não são nulos e quais não são. Eu honestamente acredito que esta é uma ideia (mesmo que a maior parte do _seu_ código seja 'use não nulo').

Editar: Além disso, quando você começa a digitar uma função e obtém a pequena janela que informa qual é a definição da função, como saber se string nessa definição é anulável ou não anulável?

@Griffork

  • novamente, esse já é o caso com 'use strict' .
  • Pelo menos a ideia é simples. e introduzir um conceito simples no sistema de tipos.
  • Se um desenvolvedor mover uma função em outro arquivo parece um caso bastante extremo para mim, e como o erro que ele receberá será sobre verificação nula, ele será capaz de entender rapidamente o que está acontecendo ...

Novamente, não é perfeito, mas não vejo alternativa melhor.

Só para ficar claro, seus únicos problemas com o que propus (após contra-argumentos) que posso ver são:

  • Não sabemos qual seria o comportamento de var mystring = "string"
  • Você não quer ter que digitar símbolos em todos os lugares.

E minhas preocupações com a diretriz são:

  • Os não nulos não seriam explícitos (mas podem ocorrer no mesmo projeto que os nulos). editar : redação fixa para fazer mais sentido.
  • Será mais difícil para os desenvolvedores controlar se o que estão escrevendo pode ser anulado ou não.
  • As definições de função que você vê ao invocar uma função ( editar : o pop-up fornecido pelo Visual Studio quando você começa a digitar uma função) podem ou não ser anuláveis ​​e você não seria capaz de dizer.
  • Uma vez que uma função é quebrada por 2 ou 3 camadas, você não sabe se sua definição ainda está correta (já que você não pode usar inferência de tipo por meio de arquivos que não têm "usar não nulo").

Honestamente, a implementação de "use strict" não é
E não consigo ver por que a diretiva é melhor do que forçar os desenvolvedores a serem explícitos sobre suas intenções (afinal, esse é o motivo pelo qual o Typescript foi criado, não foi?).

Edit: Esclarecido um ponto.
Editar: coloque a string entre aspas

Honestamente, o resumo da minha preocupação é um pouco pequeno e não reflete o que eu escrevi, sua proposição adiciona mais complexidade ao sistema de tipos, torna o algoritmo de verificação de tipo uma dor de cabeça, é muito difícil de especificar com todo o caso extremo que seria crie especialmente para inferência de tipo e torne o código ultraverboso para nada. Basicamente, não é a ferramenta certa para o trabalho.

Quero que todos os valores não nulos sejam explícitos (uma vez que podem ocorrer no mesmo projeto como valores nulos).

Eu quero o fato de que string é realmente string | null | undefined sem forçar você a verificar isso, explícito.

Será mais difícil para os desenvolvedores controlar se o que estão escrevendo pode ser anulado ou não.

Duvido que haja um único arquivo em um projeto sem 'usar não nulo' se o projeto usar não nulo e rolar no topo do seu arquivo não for tão difícil (pelo menos quando você escreve um arquivo com menos de 500 linhas de código que é a maioria dos casos ...)

As definições de função que você vê ao invocar uma função podem ou não ser anuláveis ​​e você não seria capaz de dizer.

Sim, você vai, se vier de um módulo que tem uma diretiva 'usar não nulo' será digitado de acordo, se você não considerar tudo como anulável ...

Uma vez que uma função é quebrada por 2 ou 3 camadas, você não sabe se sua definição ainda está correta (já que você não pode usar inferência de tipo por meio de arquivos que não têm "usar não nulo").

Eu não entendo seu ponto aqui.

Honestamente, a implementação de "use strict" não é algo que se deva aspirar. Ele foi projetado para JavaScript (não Typescript), e em JavaScript existem poucas alternativas preciosas. Estamos usando o Typescript, então temos a opção de fazer as coisas melhor.
E não consigo ver por que a diretiva é melhor do que forçar os desenvolvedores a serem explícitos sobre suas intenções (afinal, esse é o motivo pelo qual o Typescript foi criado, não foi?).

TypeScript é um superconjunto de javascript, tem sua raiz em JavaScript e o objetivo é permitir que você escreva javascript de forma mais segura, portanto, reutilizar um conceito de JavaScript parece razoável.

E não consigo ver por que a diretiva é melhor do que forçar os desenvolvedores a serem explícitos sobre suas intenções

Porque você simplesmente não será capaz de obter o mesmo resultado, por todas as razões que mencionei, ter um tipo não anulável em um sistema de tipo anulável é apenas uma ideia comercial.

Do meu ponto de vista, existem 3 soluções viáveis ​​aqui:

  • Quebrando as mudanças para 2.0 onde os tipos se tornam não anuláveis
  • O compilador sinaliza a mudança para o tipo não anulável por padrão, como propus algumas dezenas de comentários, mas @RyanCavanaugh tinha um bom ponto sobre isso. (evento se eu honestamente achar que vale a pena)
  • diretiva 'use non-null'

A propósito, a bandeira valeria totalmente a pena para mim.

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

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

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

Se as funções forem a única preocupação, uma exceção pode ser feita nos casos em que uma sobrecarga contém um argumento null explícito - ele sempre teria precedência sobre o nulo implícito (para estar em harmonia com --noImplicitNull ) . Essa também seria a interpretação que faria sentido para o usuário (ou seja, "o que eu digo explicitamente deve substituir o que é dito implicitamente"). Embora eu me pergunte se há outros problemas semelhantes com a sinalização que não podem ser resolvidos dessa forma. E, claro, isso adiciona alguma complexidade hackeada tanto às especificações quanto à implementação: |

  1. string|null|undefined estava explícito em minha proposta usando a bandeira, por isso deixei de fora.
  2. Então, por que ter por arquivo? Estou sugerindo minha sugestão porque _não quebra a compatibilidade com versões anteriores_ que é importante para mim, e provavelmente para muitos outros desenvolvedores. E pode ser forçado a abranger todo o projeto.
  3. Eu uso muitos arquivos que não crio; como eu saberia que aquele arquivo específico tem "use nonnull"? Se eu tiver 100 arquivos feitos por outras pessoas, tenho que memorizar qual desses 100 arquivos é não nulo? (ou, _cada vez_ eu uso uma variável / função de outro arquivo, tenho que abrir esse arquivo e verificá-lo?)
  4. Vamos ver se consigo deixar isso mais claro: exemplo no final do post.
  5. Onde você para? Onde você chama isso de ruim? Uma string ou palavra-chave no início de um arquivo não deve alterar o comportamento do arquivo. "use strict" foi adicionado ao javascript porque

    1. Nenhum grande superconjunto de Javascript (por exemplo, Typescript) existia na época que pudesse fazer o que ele queria.

    2. Foi uma tentativa de acelerar o processamento do JavaScript (que, a meu ver, é a única razão pela qual é desculpável).

      "use strict" foi adotado _não porque fosse a coisa certa a fazer_, mas porque era a única maneira de os navegadores atenderem às demandas dos desenvolvedores. Eu odiaria ver o texto datilografado adicionando 1 (depois 2, depois 3 e 4) outras diretivas que _fundamentalmente mudam a maneira como a linguagem funciona_ como strings que são declaradas em algum escopo arbitrário e afetam alguns outros escopos arbitrários. É um design de linguagem muito ruim. Eu ficaria feliz se "use strict" não existisse no Typescript e, em vez disso, fosse um sinalizador do compilador (e o Typescript o emitisse em todos os arquivos / escopos que precisassem dele).

  6. Javascript não é um "sistema de tipo não anulável", Typescript não é um "sistema de tipo não anulável". Apresentar a capacidade de declarar tipos não anuláveis ​​não significa que todo o sistema seja "não anulável". Não é o objetivo do texto datilografado.
File 1:
"use notnull"
export string foo() {
    return "mygeneratedstring";
}
File 2:
export string foo() {
    return file1.foo()
}
File 3:
"use notnull"
file2.foo(); //???

Você realmente tem a capacidade de perder informações contextuais enquanto usa a mesma sintaxe! Isso não é algo que eu anseio por ter em qualquer idioma que uso.


Parece que estamos discutindo em círculos. Se não estou fazendo sentido para você, sinto muito, mas parece que você está sempre levantando as mesmas questões e estou respondendo a elas repetidamente.

@spion
Nesse exemplo, a string não é um nulo implícito (acredito que assume que o sinalizador --noImplicitNull está ativado)

string | null | undefined estava explícito em minha proposta usando o sinalizador, por isso eu o deixei

O que quero dizer é que no sistema real var myString: string é implicitamente var myString: (string | null | undefined); e que, acima de tudo, o compilador não o força a usar typeguard a menos que todos os outros tipos de união.

Então, por que ter por arquivo? Estou sugerindo minha sugestão porque ela não quebra a compatibilidade com versões anteriores, o que é importante para mim e provavelmente para muitos outros desenvolvedores. E pode ser forçado a abranger todo o projeto.
A diretiva não quebra a compatibilidade com versões anteriores (a menos que você tenha usado um literal de string 'use não nulo' em uma posição de uma diretiva por um motivo muito estranho, o que eu aposto que ninguém o faz), também em algum ponto o TypeScript terá que quebrar a compatibilidade com versões anteriores, é por isso que semver definiu a versão principal, mas isso é outra história.

Eu uso muitos arquivos que não crio; como eu saberia que aquele arquivo específico tem "use nonnull"? Se eu tiver 100 arquivos feitos por outras pessoas, tenho que memorizar qual desses 100 arquivos é não nulo? (ou, toda vez que uso uma variável / função de outro arquivo, tenho que abrir esse arquivo e verificá-lo?)

Se você está em um projeto com diretrizes etc etc, como se todo projeto fosse organizado normalmente, você sabe ... no pior caso, você só precisa rolar um pouco ... não parece tão difícil ...

Onde você para? Onde você chama isso de ruim? Uma string ou palavra-chave no início de um arquivo não deve alterar o comportamento do arquivo. "use strict" foi adicionado ao javascript porque
Nenhum grande superconjunto de Javascript (por exemplo, Typescript) existia na época que pudesse fazer o que ele queria.
Foi uma tentativa de acelerar o processamento do JavaScript (que, a meu ver, é a única razão pela qual é desculpável). "use strict" foi adotado não porque fosse a coisa certa a se fazer, mas porque era a única maneira de os navegadores atenderem às demandas dos desenvolvedores. Eu odiaria ver o texto datilografado adicionando 1 (depois 2, depois 3 e 4) outras diretivas que mudam fundamentalmente a maneira como a linguagem funciona como strings que são declaradas em algum escopo arbitrário e afetam alguns outros escopos arbitrários. É um design de linguagem muito ruim. Eu ficaria feliz se "use strict" não existisse no Typescript e, em vez disso, fosse um sinalizador do compilador (e o Typescript o emitisse em todos os arquivos / escopos que precisassem dele).

'use strict' foi introduzido para alterar o comportamento da linguagem e manter a retro compatibilidade, exatamente o mesmo problema que enfrentamos agora, e irá mais ou menos desaparecer com os módulos es6 (e usar not null pode desaparecer com a próxima versão principal do texto datilografado) .

Javascript não é um "sistema de tipo não anulável", Typescript não é um "sistema de tipo não anulável". Apresentar a capacidade de declarar tipos não anuláveis ​​não significa que todo o sistema seja "não anulável". Não é o objetivo do texto datilografado.

Essas frases não têm nenhum sentido para mim, JavaScript não tem sistema de tipo estático, então como eu disse, o fato de você poder atribuir null a uma variável que estava antes de string pode ser facilmente comparada ao fato de que você pode atribuir 5 a uma variável que era string antes.
A única diferença é que o TypeScript, um verificador de tipo criado para JavaScript, considera null como atribuível a tudo e não 5 .

Para o seu exemplo, sim, perdemos algumas informações entre os arquivos, é uma troca aceitável para mim, mas posso entender sua preocupação.

Parece que estamos discutindo em círculos. Se não estou fazendo sentido para você, sinto muito, mas parece que você está sempre levantando as mesmas questões e estou respondendo a elas repetidamente.

Sim, concordo, duvido que haja algum consenso sobre isso, sinceramente acho que acabarei apenas bifurcando o compilador e adicionando um tipo não nulo para mim, na esperança de que um dia acabe no compilador principal, ou naquele fluxo maduro o suficiente para ser utilizável.

@Griffork , a propósito, acho que a ideia do @spion funciona, a ideia é dizer que sem o sinalizador string sendo um number | null nulo implícito foo(null) será string .

@RyanCavanaugh você acha que poderia funcionar?

@fdecampredon

  1. "use strict" foi introduzido para otimizar o javascript. Ele teve que ser consumido pelos navegadores , portanto, ele teve que ser in-code. E tinha que ser compatível com as versões anteriores, então tinha que ser uma instrução não funcional.
    Os tipos não anuláveis ​​não precisam ser consumidos pelos navegadores, portanto, não precisam ter uma diretiva no código.
  2. Desculpe, estava me referindo a algo que havia esquecido de postar, aqui está:

De acordo com o Facebook : "Em JavaScript, null converte implicitamente em todos os tipos primitivos; também é um habitante válido de qualquer tipo de objeto."
O motivo para eu querer que o não anulável seja explícito (em todas as ações que um desenvolvedor faz) é porque, ao usar o não anulável, o desenvolvedor está reconhecendo que eles estão divergindo da maneira como o javascript funciona, e que um tipo não é anulável não é garantido (pois há muitas coisas que podem fazer com que isso dê errado).
Uma propriedade não anulável é uma propriedade que nunca pode ser excluída.

Eu uso o Typescript há muito tempo. Foi difícil para mim inicialmente convencer meu chefe a mudar de Javascript para Typescript, e foi muito difícil para mim convencê-lo a nos deixar atualizar sempre que havia uma alteração importante (mesmo se não houver, não é fácil). Quando ES: Harmony for compatível com navegadores, provavelmente iremos querer usá-lo. Se uma alteração significativa acontecer no Typescript entre agora e quando o Typescript suportar ES: Harmony (particularmente um tão prevalente quanto não nulo), duvido que eu ganhe esse argumento. Sem mencionar a quantidade de tempo e dinheiro que nos custaria para eu treinar novamente todos os nossos desenvolvedores (eu sou nosso "profissional de Texto Dactilografado", é meu trabalho treinar nossos desenvolvedores de Texto Dactilografado).
Portanto, sou realmente contra a introdução de mudanças significativas.
Eu não me importaria de poder usar não anuláveis ​​sozinho, se houvesse uma maneira de introduzi-los no código futuro sem quebrar o código antigo (esse é o motivo da minha proposta). O ideal é que, eventualmente, todo o nosso código seja convertido, mas isso nos custaria muito menos tempo e dinheiro no treinamento de pessoas e no uso de bibliotecas de terceiros.

Gosto da ideia de usar símbolos (conforme minha sugestão) em vez de uma diretiva por arquivo, porque eles têm a capacidade de se propagar sem perda contextual.

@Griffork Ok, como eu disse, entendo sua preocupação, espero que você entenda a minha.
Agora não tenho a mesma opinião, a tecnologia muda, nada é estável (todo o drama em torno do angular 2 provou isso), e eu prefiro quebrar coisas e ter ferramentas melhores do que ficar com o que tenho, acho que em a longo prazo, me faz ganhar tempo e dinheiro.
Agora, como ambos concluímos antes, duvido que tenhamos um consenso aqui, prefiro a diretiva, você prefere a sua proposta.
De qualquer forma, como eu disse, vou apenas tentar fazer um fork do compilador e adicionar um tipo não nulo, pois acho que não é uma grande mudança no compilador.
Obrigado pela discussão foi interessante

@fdecampredon

Obrigado pela discussão foi

Presumindo que você quis dizer esclarecedor (trabalhos interessantes também: D), também gostei do debate :).
Boa sorte com seu fork, e espero que possamos conseguir uma pessoa TS aqui para pelo menos rejeitar algumas de nossas sugestões (portanto, sabemos o que eles definitivamente não querem implementar).

Então, para elaborar mais sobre minha solução proposta para o problema descrito por @RyanCavanaugh , após adicionar o tipo nulo e o sinalizador --noImplicitNull , o verificador de tipos quando executado sem o sinalizador terá um comportamento modificado:

Sempre que uma sobrecarga de tipo contém |null (como o exemplo fornecido):

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

o typechecker pegará cada sobrecarga e fará novas variantes com (implícito) nulo nessa posição de argumento, adicionado ao final da 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;

Isso resulta com a mesma interpretação da versão estrita --noImplicitNull : quando passado um nulo, a primeira sobrecarga correspondente é o number|null explícito. Efetivamente, torna os nulos explícitos mais fortes do que os implícitos.

(Sim, eu sei que o número de definições implícitas cresce exponencialmente com o número de argumentos que têm sobrecargas de |null - espero que haja uma implementação mais rápida que "permite" nulos em uma segunda passagem se nenhum outro tipo coincidir, mas não acho que vai ser um problema de qualquer maneira, pois espero que esta situação seja rara)

Formulado assim, acho que a mudança será totalmente compatível com as versões anteriores e opt-in.

Aqui estão mais algumas idéias sobre o resto do comportamento:

Atribuir nulos ou deixar valores não inicializados também será permitido para valores de qualquer tipo quando o sinalizador não for passado.

Quando o sinalizador é ativado, deixar os valores não inicializados (não atribuídos) ou nulos seria permitido até o momento em que são passados ​​para uma função ou operador que espera que eles não sejam nulos. Não tenho certeza sobre os membros da classe: eles também

  • precisa ser inicializado até o final do construtor, ou
  • a inicialização pode ser rastreada verificando se os métodos de membro que os inicializam totalmente foram chamados.

As uniões nulas explícitas se comportarão de maneira semelhante a todas as outras uniões em ambos os modos. Eles exigiriam um guarda para restringir o tipo de união.

Acabei de ler _todos_ os comentários acima e isso é muito. É decepcionante que depois de tantos comentários e 5 meses, ainda estejamos debatendo os mesmos pontos: sintaxe e sinalizadores ...

Acho que muitas pessoas têm apresentado argumentos fortes para o valor da verificação de nulos estáticos pelo compilador em grandes bases de código. Não direi mais nada (exceto +1).

Até agora, o principal debate tem sido sobre a sintaxe e o problema de compatibilidade com toneladas de código de TS existente e definições de biblioteca de TS. Posso ver por que a introdução de um tipo nulo e a sintaxe de fluxo ?string parece a mais limpa e natural. Mas isso muda o significado de string e, portanto, é uma alteração enorme _ enorme_ para _todos_ o código existente.

As soluções propostas acima para essa alteração significativa são introduzir sinalizadores de compilador ( @RyanCavanaugh destacou por que essa é uma ideia terrível) ou uma diretiva por arquivo. Esta parece ser uma medida paliativa muito hackeada. Além disso, eu odiaria ler o código no meio de uma revisão de diferenças ou de um arquivo enorme e não ser capaz de saber imediatamente _com certeza_ o significado do código que estou examinando. 'use strict'; foi citado como precedente. Não é porque o TS herdou os erros anteriores do JS que devemos repeti-los.

É por isso que acho que a anotação "não nula" ( string! , !string , ...) é o único caminho a percorrer e tentarei introduzir um novo argumento na discussão: talvez seja não é tão ruim.

Ninguém quer ter estrondos ( ! ) em todo o código. Mas provavelmente não precisamos. TS infere muito sobre digitação.

1. Variáveis ​​locais
Não espero anotar minhas variáveis ​​locais com estrondos. Não me importo se atribuo null a uma variável local, contanto que eu a use de uma maneira segura, o que o TS é perfeitamente capaz de afirmar.

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

Muitas vezes eu uso um inicializador em vez de um tipo.

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

2. Valores de retorno da função
Muitas vezes não especifico o valor de retorno. Assim como TS infere o tipo, ele pode inferir se é anulável ou não.

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

EDITAR: má ideia por causa das variáveis ​​de herança e função, veja o comentário de @Griffork abaixo
Evento se eu der um tipo de retorno, o TS é perfeitamente capaz de adicionar a anotação "não nulo" para mim.

Se você deseja substituir a nulidade inferida (por exemplo, porque uma classe derivada pode retornar nulo, embora seu método não retorne), você especifica o tipo explicitamente:

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

3. Parâmetros de função
Assim como você precisa especificar explicitamente um tipo, é aí que você _tem que_ indicar se não aceita parâmetros nulos:

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

Para evitar erros de compilação em bases de código antigas, precisamos de um novo sinalizador "Dereferência nula é um erro", assim como temos "Nenhum implícito". _Este sinalizador não altera a análise do compilador, apenas o que é relatado como um erro! _
Isso é muita franja para adicionar? É difícil dizer sem fazer estatísticas em bases de código grandes e reais. Não se esqueça de que os parâmetros opcionais são comuns em JS e todos podem ser anulados, portanto, você precisaria dessas anotações se 'não nulo' fosse o padrão.
Essas batidas também são bons lembretes de que null não é aceito para chamadores, de acordo com o que eles sabem sobre string hoje.

4. Campos de classe
Isso é semelhante a 3. Se você precisar ler um campo sem ser capaz de inferir seu conteúdo, deverá marcá-lo como não anulável na declaração.

class C {  x: string!; }

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

Podemos imaginar uma abreviação para inicializador, talvez:

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

Um atalho semelhante provavelmente é desejável para propriedades anuláveis ​​se você disser que não nulo é o padrão com um inicializador.
Novamente, é difícil dizer se a maioria dos campos no código existente são anuláveis ​​ou não, mas essa anotação está muito de acordo com as expectativas que os desenvolvedores têm hoje.

5. Declarações, .d.ts
O grande benefício é que todas as bibliotecas existentes funcionam. Eles aceitam valores nulos e não nulos como entradas e devem retornar um valor possivelmente nulo.
A princípio, você precisará converter explicitamente alguns valores de retorno para "não nulo", mas a situação melhorará à medida que as definições são atualizadas lentamente para indicar quando elas nunca retornam nulo. Anotar os parâmetros é mais seguro, mas não é necessário para compilar com sucesso.

Acho que declarar o estado "não nulo" de um valor onde o compilador não pode inferir pode ser útil (por exemplo, ao manipular código não tipado, ou quando o desenvolvedor pode fazer algumas afirmações sobre o estado global do programa) e uma abreviação pode ser bom:

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

Eu acho que a habilidade de sugerir ao compilador - de uma forma simples - que algo não é nulo é desejável. Isso também é verdadeiro para não nulo por padrão, embora seja mais difícil obter uma sintaxe simples e lógica.

Desculpe, esta foi uma postagem muito longa. Tentei demonstrar a ideia de que mesmo com "null por padrão", não teríamos que anotar todo o nosso código: o TS pode inferir a exatidão da maioria dos códigos sem nossa ajuda. As duas exceções são campos e parâmetros. Acho que não é excessivamente barulhento e é uma boa documentação. Um ponto forte a favor dessa abordagem é que ela é compatível com versões anteriores.

Concordo com a maior parte do que você disse @ jods4, mas tenho algumas perguntas e preocupações:

2) se o compilador pode converter automaticamente anulável em não anulável em tipos de retorno, como você pode sobrescrever isso (por exemplo, para herança ou funções que são substituídas)?

3 e 5) Se o compilador pode converter anulável em não anulável, uma assinatura de função em um arquivo de código agora pode se comportar de maneira diferente para a mesma assinatura de função em um arquivo d.ts.

6) Como o bang funciona / fica ao usar tipos de união?

2) Boa captura. Não acho que funcionaria bem com herança (ou 'delegados'), de fato.
Onde trabalho, nossa experiência com o TS é que quase nunca digitamos explicitamente os valores de retorno das funções. O TS os descobre e eu acho que o TS pode descobrir a parte nula / não nula.

Os únicos casos em que os especificamos explicitamente é quando queremos retornar uma interface ou uma classe base, por exemplo, para extensibilidade (incluindo herança). A anulação parece se encaixar muito bem nessa descrição.

Vamos mudar minha proposta acima: um valor de retorno explicitamente digitado é _não_ inferido não anulável se não for declarado assim. Isso funciona bem para os casos que você sugere. Ele fornece uma maneira de substituir o compilador que infere "não nulo" em um contrato que pode ser nulo em classes filhas.

3 e 5) Com a mudança acima, não é mais o caso, certo? A assinatura de funções no código e nas definições agora se comportam exatamente da mesma maneira.

6) Pergunta interessante!
Este não é realmente ótimo, especialmente se você adicionar genéricos à mistura. A única saída que consigo pensar é que todas as partes devem ser não nulas. Você provavelmente precisará de um literal de tipo nulo novamente por causa dos genéricos, veja meu último exemplo.

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

Para fins de discussão, observe que se você optou por tipos não nulos por padrão, também precisaria inventar uma nova sintaxe: como expressar que um tipo genérico não deve permitir valores anuláveis?
E, inversamente, como você especificaria que mesmo se o tipo genérico T contiver nulo, uma função nunca retornará T, mas nunca nula?

Literais de tipo são ok, mas não muito satisfatórios: var x: { name: string }! , especialmente se ocuparem mais de uma linha :(

Mais exemplos:
Matriz de números não nulos number![]
Matriz de números não nula: number[]!
Nada pode ser nulo: number![]!
Genéricos: Array<number!>[]

2, 3 e 5) Concordo com a alteração da proposta, parece que funcionaria.
6)

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

Presumo que aqui você queira dizer que f pode retornar nulo, não que f possa ser atribuído a nulo.

Para sindicatos, o que você acha da sintaxe a seguir ser outra opção disponível? (parece um pouco bobo, mas pode ser mais sucinto / fácil de seguir em alguns casos).

var x = ! & number | string;

Para significar que nenhum dos dois é anulável.


Para literais de tipo, eu diria que você pode colocar o estrondo no início da expressão literal, bem como no final, pois var x: !{name: string} , será mais fácil de trabalhar na minha opinião do que tê-lo no final.

@ jods4 você leu sobre minha proposta de correção para "nulo explícito tem prioridade sobre nulo implícito"? Isso resolve o problema de semântica variável, eu acho.

@Griffork
Sim, claro, "é anulável" era uma formulação incorreta para "pode ​​retornar nulo".

O que me faz pensar que function f() {} é na verdade uma declaração de f , que pode ser definida mais tarde ... queremos expressar que f nunca será definido como nulo ? Gosta de function f!() {} ? Isso parece ir longe demais, na minha opinião. Acho que o inspetor de tipos deve presumir que esse é sempre o caso. Existem outros casos extremos incomuns que ele não consegue lidar de qualquer maneira.

Em relação à nova opção, concordo que parece um pouco boba e introduz um símbolo adicional na declaração de tipo: & , com um único propósito ... Isso não me parece uma boa ideia. E em vários idiomas & AND vincula-se com maior prioridade do que | OR que não é o que acontece aqui.

Colocar a explosão antes dos tipos é uma alternativa possível. Acho que devemos tentar reescrever todos os exemplos para ver o que pode ficar melhor. Acho que esse atalho de campo que sugeri vai ser um 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.
}

@spion
Sim, eu vi.

Em primeiro lugar, não tenho certeza se isso realmente resolve o problema em questão. Então, com sua nova definição, qual sobrecarga se liga a fn(null) ? O 'gerado' que retorna number ? Isso não é inconsistente com o fato de que a segunda sobrecarga também aceita null mas retorna string ? Ou é um erro do compilador porque você não pode executar a resolução de sobrecargas?

Em segundo lugar, tenho certeza de que podemos encontrar muitos outros problemas. Acho que mudar o significado global do código-fonte com o toque de um botão simplesmente não funciona. Não apenas em .d.ts , mas as pessoas adoram reutilizar código (reutilização de biblioteca ou mesmo copiar e colar). Sua sugestão é que o significado semântico do meu código-fonte depende do sinalizador do compilador e isso parece uma proposição muito falha para mim.

Mas se você puder provar que o código será compilado e executado corretamente em todos os casos, estou totalmente a favor!

@ jods4

Em ambos os casos, a 2ª sobrecarga será usada

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

porque o segundo nulo explícito terá precedência sobre o implícito (o primeiro), independentemente do sinalizador usado. Portanto, o significado não muda com base na bandeira.

Como o tipo nulo não existe agora, eles seriam introduzidos juntos e a alteração seria totalmente compatível com versões anteriores. As definições de tipo antigo continuarão a funcionar normalmente.

@spion
Então, qual era o problema com o function fn(x: null): number; implícito? Se a 2ª sobrecarga sempre tiver precedência, não importa qual sinalizador seja usado, todo esse "conserto" não tem efeito algum?

Outra questão: hoje todas as definições de lib têm comportamento "nulo permitido". Portanto, até que sejam _todos_ atualizados, tenho que usar o sinalizador "implicitamente nulo". Agora, com esse sinalizador ativado, como posso declarar uma função que usa um parâmetro não nulo em meu projeto?

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

A correção certamente surtiu efeito. Isso torna o comportamento com e sem o sinalizador consistente, o que corrige o problema semântico mencionado acima.

Em segundo lugar, você não precisará usar um sinalizador "nulo por padrão" para bibliotecas de terceiros. O mais importante sobre esse recurso é que você não precisará fazer _nada_ para obter os benefícios na grande maioria dos casos.

Vamos dizer que existe uma biblioteca que calcula a contagem de palavras para uma string. Sua declaração é

declare function wordCount(s: string): number;

Digamos que seu código use essa função desta forma:

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

Este programa é aprovado em ambos os compiladores: mesmo se os nulos implícitos forem desabilitados. O código é totalmente compatível com versões anteriores.

Por quê? Porque o compilador, como é hoje, já acredita que os valores não são nulos em todos os lugares onde você tenta usá-los (embora eles possam teoricamente ser nulos).

O novo compilador também presumirá que os valores não são nulos quando você tenta usá-los. Não há razão para acreditar o contrário, uma vez que você não especificou que o valor pode ser nulo, de modo que parte do comportamento permanece o mesmo. A mudança no comportamento só tem efeito ao atribuir null (ou ao deixar as variáveis ​​não inicializadas) e, em seguida, tentar passar esses valores para uma função como a acima. Eles não exigem nenhuma alteração em outras partes do código que usam valores normalmente.

Agora vamos dar uma olhada na implementação de wordCount

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

Opa, esse tipo não conta toda a história. É possível que essa função retorne um valor nulo.

O problema é exatamente esse. É impossível contar toda a história no compilador atual, mesmo que quiséssemos. Claro, dizemos que o valor de retorno é um número, o que afirma implicitamente que pode ser nulo: mas o compilador nunca avisa sobre isso quando tentamos acessar esse valor potencialmente nulo incorretamente. Ele sempre pensa que é um número válido.

Após a mudança noImplicitNull , obteremos o mesmo resultado aqui se usarmos as mesmas definições de tipo. O código será compilado sem reclamações. O compilador ainda vai pensar que wordCount sempre retorna um número. O programa ainda pode falhar se passarmos strings vazias, como antes (o compilador antigo não nos avisará que o número retornado pode ser nulo, e nem o novo, pois confia na definição do tipo). E se quisermos o mesmo comportamento, podemos mantê-lo sem alterar nada em nosso código. (1)

No entanto, agora podemos _ ser capazes_ de fazer melhor: seremos capazes de escrever uma definição de tipo melhorada para wordCount:

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

e obter um aviso do compilador sempre que tentarmos usar wordCount sem verificar um valor de retorno nulo.

A melhor parte é que essa melhoria é totalmente opcional. Podemos manter todas as declarações de tipo como estavam e obter exatamente o mesmo comportamento de agora. As declarações de tipo não irão piorar (2) - só podem ser melhoradas para ser mais precisas nos casos em que consideramos necessário.


(1): já existe uma melhoria aqui, mesmo sem melhorar a definição do tipo. com as novas definições de tipo, você não será capaz de passar nulo acidentalmente para sumWordcounts e obter uma exceção de ponteiro nulo quando a função wordCount tentar .split() aquele nulo.

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

(2): ok, isso é uma mentira. As declarações de tipo ficarão piores para um pequeno subconjunto de funções: aquelas que recebem argumentos anuláveis ​​que não são opcionais.

As coisas ainda estarão bem para argumentos opcionais:

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

mas não para argumentos que não são opcionais e podem ser nulos

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

Eu diria que essas funções são muito raras e as correções necessárias serão mínimas.

@spion

A correção certamente surtiu efeito. Isso torna o comportamento com e sem o sinalizador consistente.

De que forma, você pode dar um exemplo completo? Você também disse:

Em ambos os casos, a 2ª sobrecarga será usada

O que, para mim, implica que a correção não tem efeito?

Este programa é aprovado em ambos os compiladores: mesmo se os nulos implícitos forem desabilitados. O código é totalmente compatível com versões anteriores.

Sim, ele compila sem erros em ambos os casos, mas não para o mesmo resultado. sumWordCounts() será digitado como number! em um caso e number? no segundo. Alterar a semântica do código com uma mudança é altamente _não_ desejável. Conforme demonstrado antes, essa alteração poderia ter efeitos globais, por exemplo, na resolução de sobrecargas.

O novo compilador também presumirá que os valores não são nulos quando você tenta usá-los. Não tem razão para acreditar de outra forma

Não! Este é o ponto principal: quero que o compilador lance um erro para mim quando eu uso um valor potencialmente nulo. Se "presume" que não é nulo, como eu faço quando codifico, esse recurso é inútil!

A mudança no comportamento só tem efeito ao atribuir null (ou ao deixar as variáveis ​​não inicializadas) e, em seguida, tentar passar esses valores para uma função como a acima.

Não tenho certeza se entendi, pois "função como a anterior" na verdade aceita nulos ...

Lendo o final de seu último comentário, tenho a impressão de que não obteremos análise nula estática real até ligarmos a chave, o que você não pode fazer até que _todas_ as definições de sua biblioteca sejam corrigidas. :(

De modo geral, é difícil entender todos os casos e como tudo funciona apenas com palavras. Alguns exemplos práticos seriam muito úteis.

Aqui está um exemplo e como eu gostaria que o compilador se comportasse:

Suponha uma biblioteca com funções yes(): string! que nunca retorna nulo e no(): string? que pode retornar nulo.

A definição .d.ts é:

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

Eu quero ser capaz de criar grandes projetos em TS que se beneficiem de valores nulos verificados estaticamente e usem as definições de biblioteca existentes. Além disso, quero ser capaz de fazer a transição _progressivamente_ para uma situação em que todas as bibliotecas sejam atualizadas com informações de nulidade corretas.

Usando o modificador não nulo proposto ! acima, sou capaz de fazer isso:

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.

Como isso funcionaria com a sua ideia?

Este tópico é uma discussão excessivamente longa (130 comentários!), O que torna muito difícil seguir as ideias, sugestões e problemas apontados por todos.

Eu sugiro que criemos gists externas com nossas propostas, incluindo a sintaxe sugerida, o que é aceito pelo TS, o que é um erro e assim por diante.

@spion, uma vez que sua ideia envolve sinalizadores de compilador, para cada parte do código você deve documentar o comportamento do TS com o sinalizador ativado e desativado.

Aqui está a essência do marcador !T not-null:
https://gist.github.com/jods4/cb31547f972f8c6bbc8b

É basicamente o mesmo que no meu comentário acima, com as seguintes diferenças:

  • Percebi que o aspecto 'não nulo' dos parâmetros de função pode ser inferido com segurança pelo compilador (graças a @spion por esse insight).
  • Incluí comentários do !T vez de T! .
  • Eu adicionei algumas seções, por exemplo, sobre o comportamento do construtor.

Sinta-se à vontade para comentar, bifurcar e criar propostas alternativas ( ?T ).
Vamos tentar seguir em frente.

@ jods4

Em ambos os casos, a 2ª sobrecarga será usada

O que, para mim, implica que a correção não tem efeito?

  1. Isso implica que a resolução da sobrecarga seja alterada para tornar a semântica do idioma consistente para ambos os sinalizadores.

Sim, ele compila sem erros em ambos os casos, mas não para o mesmo resultado. sumWordCounts () será digitado como um número! em um caso e número? no segundo. Alterar a semântica do código com um switch é altamente indesejável. Conforme demonstrado antes, essa alteração poderia ter efeitos globais, por exemplo, na resolução de sobrecargas.

  1. Não há number! em minha proposta. O tipo será simplesmente number em ambos os casos. O que significa que a resolução de sobrecarga continua a funcionar normalmente, exceto com valores nulos, caso em que o novo comportamento para nulos explícitos tendo precedência sobre implícitos normaliza a semântica de uma maneira compatível com versões anteriores.

Não! Este é o ponto principal: quero que o compilador lance um erro para mim quando eu uso um valor potencialmente nulo. Se "presume" que não é nulo, como eu faço quando codifico, esse recurso é inútil!

  1. O que eu estava tentando expressar é que há muito pouca incompatibilidade com versões anteriores do recurso (em termos de declarações de tipo). Se você não o usar, terá exatamente o mesmo comportamento de antes. Se você quiser usá-lo para expressar a possibilidade de que um valor do tipo nulo possa ser retornado, agora você pode.

É como se o compilador usasse o tipo string para valores do tipo object e string antes, permitindo todos os métodos de string em todos os objetos e nunca verificando-os. Agora ele teria um tipo separado para Object , e você pode começar a usar esse tipo para denotar que os métodos de string nem sempre estão disponíveis.

Não tenho certeza se entendi, pois "função como a anterior" na verdade aceita nulos ...

Vamos dar uma olhada nesta função:

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

Com o novo sinalizador do compilador, você não seria capaz de chamá-lo com valores nulos, por exemplo, sumWordCounts(null, null); e essa é a única diferença. A própria função irá compilar porque a definição de wordCounts diz que ela pega uma string e retorna um número.

Lendo o final de seu último comentário, tenho a impressão de que não obteremos análise nula estática real até ligarmos a chave, o que você não pode fazer até que todas as definições de sua biblioteca sejam corrigidas. :(

A grande maioria do código simplesmente não lida com valores nulos ou indefinidos, a não ser verificar se eles são nulos / indefinidos e gerar um erro para evitar a propagação desse valor por toda a base de código para um lugar completamente não relacionado, tornando-o difícil para depurar. Existem algumas funções aqui e ali usando argumentos opcionais, que são reconhecíveis e provavelmente podem ser modelados de acordo com a nova opção. Não é tão frequente que as funções retornem valores nulos como meu exemplo (que usei apenas para fazer uma observação sobre o recurso, não como um caso representativo)

O que estou dizendo é que a grande maioria das definições de tipo permanecerão corretas, e para os poucos casos restantes em que precisamos fazer a correção, poderíamos escolher não corrigi-los se considerarmos o caso nulo como sem importância, ou mude o tipo de acordo, se nos importarmos. O que está completamente de acordo com o objetivo da datilografia de progressivamente "aumentar o dial" de obter garantias mais fortes sempre que precisarmos delas.


Ok, então as definições de tipo existentes são

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

E digamos que seu código foi escrito antes de o recurso ser adicionado também:

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

No novo compilador, o comportamento será exatamente o mesmo do antigo

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

A menos que você tente algo que o compilador sabe que pode ser nulo, como o resultado de x ()

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

Você olha para no() e pode decidir que os casos em que ele retorna nulo são raros, então você não vai modelar isso, ou pode corrigir a definição de tipo.

Agora vamos ver o que acontece com sua proposta: cada linha de código que até mesmo toca em uma biblioteca externa se quebra. Funções que tinham definições de tipo perfeitamente válidas como acima também são interrompidas. Você tem que atualizar cada anotação de argumento em todos os lugares e adicionar ! para fazer o compilador parar de reclamar, ou adicionar verificações de nulos em todos os lugares.

O resultado final é que o sistema de tipos no TypeScript está incorreto no momento. Ele tem uma interpretação dupla de valores nulos:

Quando tentamos atribuir nulo a alguma variável do tipo T , ou passar nulo como um argumento onde um tipo T é necessário, age como se o tipo fosse T|null .

Quando tentamos usar um valor do tipo T , ele age como se o tipo fosse T e não houvesse possibilidade de nulo.

Sua proposta sugere tratar todos os tipos normais T como T|null sempre; o meu sugere tratá-los como apenas T . Ambos mudam a semântica. Ambos fazem o compilador se comportar "corretamente"

Meu argumento é que a variante "apenas T " é muito menos dolorosa do que parece e está em sintonia com a grande maioria do código.

editar: Acabei de perceber que sua proposta pode ser continuar tratando tipos sem "!" ou "?" da mesma forma que antes. Isso é compatível com versões anteriores, sim, mas dá muito trabalho para obter quaisquer benefícios (já que a grande maioria do código simplesmente não lida com valores nulos além de verificação / lançamento.

@spion

1. Isso implica que a resolução de sobrecarga é alterada para tornar a semântica da linguagem consistente para ambos os sinalizadores.

Você está apenas afirmando o mesmo fato, mas ainda não está claro para mim a que caso se trata e de que maneira.
Por isso pedi um exemplo concreto.

1.Não há number! em minha proposta. O tipo será simplesmente number em ambos os casos.

Isso está incorreto, eu sei que a sintaxe é diferente, mas os conceitos subjacentes são os mesmos. Quando digo number! , estou enfatizando um tipo de número não nulo, que em sua proposta seria simplesmente number . E o segundo caso não seria number no seu caso, mas number | null .

A grande maioria do código simplesmente não lida com valores nulos ou indefinidos, a não ser verificar se eles são nulos / indefinidos e gerar um erro para evitar a propagação desse valor por toda a base de código para um lugar completamente não relacionado, tornando-o difícil para depurar. Existem algumas funções aqui e ali usando argumentos opcionais, que são reconhecíveis e provavelmente podem ser modelados de acordo com a nova opção. Não é tão frequente que as funções retornem valores nulos como meu exemplo (que usei apenas para fazer uma observação sobre o recurso, não como um caso representativo)

Acho que descrições como esta fazem _muitas_ suposições e atalhos. É por isso que acho que, para progredir, precisamos passar para exemplos mais concretos com código, sintaxe e explicações de como o sistema funciona. Você diz:

A grande maioria do código simplesmente não lida com valores nulos ou indefinidos, a não ser verificar se eles são nulos / indefinidos e gerar um erro

Muito discutível.

Existem algumas funções aqui e ali usando argumentos opcionais.

Ainda mais discutível. Bibliotecas JS estão cheias de exemplos.

, que são reconhecíveis e provavelmente podem ser modelados de acordo com o novo switch

Faça uma proposta mais concreta porque esse é um atalho enorme. Acho que posso ver onde isso leva para o código JS real, mas como você "reconheceria" um argumento opcional dentro de declare function(x: {}); ou dentro de interface ?

Não é tão frequente que as funções retornem valores nulos como meu exemplo.

Novamente muito discutível. Muitas funções retornam um null ou undefined : find (quando o item não é encontrado), getError (quando não há erro) e assim por diante ... Se você quiser mais exemplos, basta olhar as APIs de navegador padrão, você encontrará _plenty_.

O que estou dizendo é que a grande maioria das definições de tipo permanecerá correta.

Como você pode perceber pelos meus comentários anteriores, não estou convencido disso.

e para os poucos casos restantes em que precisamos fazer a correção, poderíamos escolher não corrigi-los se considerarmos o caso nulo como sem importância, ou alterar o tipo de acordo, se nos importarmos. O que está completamente de acordo com o objetivo da datilografia de progressivamente "aumentar o dial" de obter garantias mais fortes sempre que precisarmos delas.

Neste ponto, esta afirmação não parece trivial para mim. Você pode dar exemplos concretos de como isso funciona? Especialmente a parte _progressivamente_.

Agora vamos ver o que acontece com sua proposta: cada linha de código que até mesmo toca em uma biblioteca externa se quebra. Funções que tinham definições de tipo perfeitamente válidas como acima também são interrompidas. Você tem que atualizar cada anotação de argumento em todos os lugares e adicionar ! para fazer o compilador parar de reclamar, ou adicionar verificações de nulos em todos os lugares.

Isso é quase completamente incorreto. Por favor, leia a essência que escrevi com atenção. Você notará que, na verdade, poucas anotações não nulas são necessárias. Mas há _uma_ mudança significativa, sim.

Suponha que você pegue uma base de código existente que usa definições de biblioteca e tente compilá-la com minha proposta sem alterar nenhum código:

  • Você obterá muitos benefícios de análise nula, mesmo sem anotações (praticamente da mesma forma que obtém no compilador Flow).
  • Uma única coisa vai quebrar: usar o valor de retorno de uma função de biblioteca que você sabe que não retorna nulo.
declare function f(): string; // existing declaration, but f will never return null.
var x = f();
x.toUpperCase();  // error, possibly null reference.

A solução de longo prazo é atualizar as definições da biblioteca para serem mais precisas: declare function f(): !string;
A correção de curto prazo é adicionar um elenco não nulo: var x = <!>f(); .
E para ajudar grandes projetos com muitas dependências a atualizarem mais facilmente, sugiro adicionar um sinalizador de compilador semelhante a "nenhum implícito": "tratar possíveis referências nulas como aviso". Isso significa que você pode usar o novo compilador e esperar até que as bibliotecas tenham suas definições atualizadas. Nota: ao contrário de sua proposição, este sinalizador não altera a semântica do compilador. Ele apenas altera o que é relatado como um erro.

Como eu disse, não tenho certeza se estamos progredindo neste debate. Eu sugiro que você olhe para minha essência e crie um com suas próprias idéias, exemplos de código e comportamento em ambos os estados de sua bandeira. Como somos todos codificadores, acho que ficará mais claro. Além disso, há muitos casos extremos a serem considerados. Tendo feito isso, tenho toneladas de perguntas a fazer em situações especiais, mas realmente não adianta discutir sobre conceitos e ideias sem uma definição precisa.

Existem muitas discussões sobre diferentes coisas acontecendo nesta edição de mais de 130 comentários.

Eu sugiro que continuemos a discussão _geral_ aqui.

Para discussões de propostas concretas que podem implementar tipos não nulos em TS, sugiro que abramos novos problemas, cada um sobre uma única proposta de design. Criei o # 1800 para discutir a sintaxe !T .

@spion, eu sugiro que você crie um problema para o seu design também.

Muitas pessoas desejam tipos não nulos. É fácil em novas linguagens (por exemplo, Rust), mas é muito difícil retroajustar em linguagens existentes (as pessoas têm pedido referências não nulas em .net há muito tempo - e acho que nunca conseguiremos eles). Olhando para os comentários aqui, mostra que é um problema não trivial.
Flow me convenceu de que isso pode ser feito para TS, vamos tentar fazer acontecer!

@ jods4 Lamento dizer isso, mas sua proposta não tem nada a ver com tipo não nulo, está mais relacionada a algum tipo de análise de fluxo de controle, dificilmente previsível, e que quebra completamente a retrocompatibilidade (na verdade a maior parte do código que é válido 1,4 ts irá falhar sob a regra descrita em sua essência).
Concordo com o fato de que a discussão não vai a lugar nenhum, e que 130 comentários + talvez seja demais. Mas talvez seja porque há 2 grupos de pessoas discutindo aqui:

  • aqueles que pensam que o tipo não nulo deve ser o padrão e querem encontrar uma maneira de fazer isso acontecer (por meio de um sinalizador ou qualquer outro mecanismo)
  • aqueles que não querem tipo não nulo e apenas tentam evitar sua introdução, ou empurrá-los em uma nova sintaxe.

Esses 2 grupos de pessoas há muito que pararam de ouvir os argumentos uns dos outros, e no final o que precisamos é o ponto de vista da equipe do TS, até então, pessoalmente, deixarei de tentar discutir mais sobre esse assunto.

@fdecampredon
Em relação à parte sobre a minha essência, copiei e colei seus argumentos no # 1800 e se você estiver genuinamente interessado, podemos discutir por que é um design ruim lá. Eu realmente não entendo seu ponto de vista, mas não quero começar a discutir aqui, é por isso que criei o # 1800 em primeiro lugar.

Em relação a este tópico, concordo com você que a discussão não vai a lugar nenhum ...

Como você pode perceber pelos meus comentários anteriores, não estou convencido disso.

Bem, eu realmente não sei como explicar de outra forma - já expliquei algumas vezes. Deixe-me tentar mais uma vez.

Agora você não pode expressar isso

declare function err():string;

retorna nulo. É impossível, porque o texto digitado sempre deixará você fazer err().split(' ') . Você não pode dizer "isso pode não ser válido".

Após essa alteração, você seria capaz de, alterando string para ?string ou string|null

Mas se você não fizer isso, _ você não perderá nada_ que você tinha antes da mudança:

Você não tinha verificação de nulos antes da mudança e não tem depois (se você não fizer nada). Argumentei que isso é principalmente compatível com versões anteriores. Resta ser demonstrado em bases de código maiores, é claro.

A diferença é: você não pode forçar a verificação de nulos antes da mudança, mas você _pode_ depois (progressivamente, primeiro nas definições que mais lhe interessam, depois em outros lugares)

@fdecampredon Ainda discuto o assunto porque sinto que há muitos conceitos errôneos sobre o problema e sobre o quão "difícil" e "estrito" será tirar proveito do recurso. Suspeito que muitos dos problemas potenciais são exagerados. Provavelmente deveríamos tentar implementar uma variante básica de --noImplicitNull em um fork - não tenho certeza se vou conseguir encontrar o tempo neste próximo período, mas se eu fizer isso, vou tentar , Soa como um projeto divertido.

@spion , sou totalmente

E @fdecampredon, meu único pedido era que não

Suponho que @spion em seu exemplo atual, você poderia continuar codificando o Typescript da mesma maneira que fazemos agora, se você assumir que uma variável local, se não atribuída a, é convertida em parâmetros anuláveis ​​e função com um? também pode ser nulo. Bem, exceto por aquele exemplo que você teve anteriormente com os parâmetros de função iniciais anuláveis.

@spion
O que entendi do seu último comentário é muito diferente do que entendi antes ...

Então : string é "o seu tipo de string antigo que nunca será verificado quanto à nulidade, como <any> nunca é verificado quanto à exatidão do tipo".

A nova sintaxe : string | null é compatível com versões anteriores porque não existia antes e acessar este tipo sem uma verificação de nulo é um erro.

Isso é interessante e é difícil entender todas as implicações. Terei que pensar nisso por algum tempo.

A primeira questão que vem à mente é como você coloca restrições nos valores de entrada (por exemplo, parâmetros de função):

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

Isso está certo ou é um erro? Se estiver tudo bem, como posso cometer um erro.

_Ainda acho que alguma ideia dentro dessa discussão está perdida, não quer abrir um novo fascículo com essa sua ideia? Podemos discutir isso lá._

@jods isso seria um erro:

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 Não acho que precisamos criar vários problemas em um tópico, então as coisas ficarão mais difíceis de rastrear, pois você terá que ir a 10 propostas diferentes apenas para ver se algo aconteceu / inscrever-se em 10 propostas diferentes . E todos eles teriam que ter links para as outras propostas em suas apresentações para que todos que procuram a não-nulidade não apenas vejam e votem em uma.

@fdecampredon Eu sei que há muitas postagens e muitas coisas não resolvidas, mas isso não significa que devemos parar de tentar encontrar uma solução que todos gostem. Se você não gosta do fato de que ainda estamos felizes em falar sobre isso e nos explicar, então você é mais do que bem-vindo para cancelar a inscrição no tópico.

@Griffork
Mas só depois de ligar a bandeira mágica, certo?
Portanto, com o sinalizador desativado, você tem código desmarcado, exceto novos tipos anuláveis ​​que não existiam antes; então, quando você ativa o sinalizador, todos os tipos são não anuláveis ​​e marcados?

Acho que o resultado final é provavelmente a melhor solução. _Mas está quebrando quase todo o código que existe hoje._ Imagine que eu tenho 300 mil linhas de código ... Tenho que adicionar uma anotação anulável em todos os lugares em que um tipo é realmente anulável antes de poder ativar o sinalizador. Isso é um grande negócio.

Se bem entendi, isso é bom para novos projetos (uma vez que .d.ts são atualizados), mas muito doloroso para o código existente.

@Griffork, meu problema com esse único problema e sua longa lista de comentários é que é muito difícil ter uma discussão focada em uma única proposta. Inscrever-se em 3 ou 4 edições não é grande coisa e você tem um bom contexto em cada uma delas.

A proposta original de @spion não tinha bandeira mágica. Foi uma mudança fundamental no modo como a datilografia funcionava.

Sim, mas alguém que entra nesta discussão deve ler tudo o que veio antes dela. Se você acha que as pessoas não deveriam ler tudo o que veio antes (e, portanto, potencialmente propor as mesmas propostas) _então_ podemos dividir; mas eu não concordo. A questão de estar em um lugar é que, embora estivéssemos falando sobre diferentes detalhes ou diferentes implementações possíveis , estávamos todos falando sobre a mesma coisa .
O mesmo tópico, não tópicos diferentes.
Toda a conversa é centralizada e nenhuma delas é enterrada por não ser respondida.

Você não pode realmente quebrar essa conversa por detalhes de implementação, porque muitas propostas frequentemente tocarão em muitos detalhes diferentes que estão atualmente em discussão.

Separar as propostas umas das outras significa que, se alguém identificar uma falha, terá que ir a vários tópicos diferentes e escrevê-la. As pessoas não estão aprendendo com os erros umas das outras e têm a capacidade (o que farão) de se isolar em uma discussão e repetir os erros descritos em outras discussões.

A questão é: dividir a conversa fará com que as pessoas tenham que repetir muitas postagens por causa dos visualizadores que têm preguiça de ler tudo em todas as outras postagens vinculadas (assumindo que todas as postagens relevantes vinculam todas as outras postagens )

Além disso, @ jods4 é apenas uma alteração significativa quando você atribui nulo a algo.

Imo é uma alteração muito menos significativa do que sua proposta, que teria cada definição que não deveria ter null ser atribuível a ela precisa ser verificada / tocada antes que você possa usá-los e obter os benefícios de tipos não anuláveis.

Aqui está um exemplo de como pode ser o processo de "atualização" para as propostas de @ jods4 e @spion (assumindo que todos os sinalizadores necessários estejam ativados):

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.

Em última análise, os dois estão interrompendo as mudanças. Ambos são igualmente dolorosos para depurar, com erros ocorrendo no uso da variável em vez da declaração da variável. A diferença é que um erro quando null é atribuído a algo (@spion), e os outros erros quando null não é atribuído a algo (@ jods4). Pessoalmente, eu atribuo null a coisas com muito menos frequência do que quando não atribuo null a coisas, então o @spion é menos uma mudança para eu trabalhar.

Outras coisas que gosto:

  • Os tipos de variáveis ​​não mudam em mim dinamicamente. Se eu quisesse, usaria o flow:
var s: string;
s.toUpperCase(); // error
var t = (s || "test").toUpperCase(); // ok. Inferred t: string
yes(t);  // ok
t = no();
yes(t);  // error

Pessoalmente, gostaria de receber um erro se chamasse no (), não uma mudança implícita de tipo.

  • Os anuláveis ​​que contêm a palavra null, em vez de algum símbolo, são mais fáceis de ler e menos de lembrar. Mesmo ! deve ser usado para "não" e não "não nulo". Odeio ter que lembrar o que um símbolo faz, assim como odeio siglas, nunca consigo me lembrar delas e elas diminuem significativamente minha velocidade de trabalho.

@ jods4 Eu sei que a conversa atual é difícil para as pessoas acompanharem, mas dividi-la em questões separadas não vai ajudar, pois teremos que levantar todos esses pontos em todos eles eventualmente porque o desinformado fará as mesmas perguntas que nós fizemos. Isso realmente não vai _ajudar_ a conversa, apenas tornará mais difícil mantê-la atualizada.

@Griffork, honestamente, li tudo, mas duvido que muitas pessoas vão ler. Ele entrou em muitas subdiscussões diferentes e não relacionadas e eu perdi o controle mais de uma vez. Mas não vamos deixar esse fio mais pesado do que já está e deixar por isso mesmo.

É uma alteração significativa toda vez que você tem algo que pode ser anulado (o que implica que ele é atribuído como nulo em algum ponto, caso contrário, não seria realmente anulável). O erro será relatado onde null é atribuído / passado para uma função, mas a correção está na declaração. Isso inclui declarações de funções, declarações de variáveis, declarações de campos em classes ... Muitas coisas precisam ser potencialmente revisadas e modificadas.

@ jods4 as sub-discussões não realizadas deveriam ter sido ramificadas, não as informações centrais reais. Mas é tarde demais para voltar e mudar isso agora.

@spion , acho que tenho uma solução mais simples para o problema @RyanCavanaugh com o sinalizador '-nonImplicitNull', a ideia é bem simples, só não podemos permitir ?type type|null ou type|undefined sem esta bandeira.

@fdecampredon então você precisa de duas versões de cada biblioteca disponível e mantida atualizada.
Também mataria nosso processo em que temos um projeto não estrito para testar o código e executar testes que dependem do código de um projeto mais estrito.

@ jods4

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

Ao usar o sinalizador --noImplicitNull proposto, sim, isso seria um erro.

Não estou confortável em abrir uma proposta oficial ainda, quero pelo menos fazer mais alguns experimentos mentais ou talvez tentar implementar uma versão mais simples da ideia em um fork.

Percebi que a proposta !T também quebra muitos códigos. Não porque a semântica dos tipos existentes mudou, mas porque a nova análise levantará erros em muitos lugares.

Então fechei. Se quebrarmos muitos códigos, prefiro a outra abordagem, parece mais limpa.

Agora estou convencido de que não há como introduzir esse recurso sem quebrar muitos códigos existentes. Isso ainda é bom para todo o código a ser escrito e, possivelmente, o código antigo pode continuar a compilar sem os benefícios das verificações de nulos pelo compilador - que é exatamente como é hoje.

Eu me pergunto qual é a posição da equipe oficial de TS em relação ao aspecto de quebra disso versus seus benefícios.

É muito improvável que faríamos uma pausa tão grande quanto "tornar tudo não anulável por padrão". Se houvesse alguma proposta que _só_ emitisse novos erros em casos que são obviamente bugs, isso poderia estar em cima da mesa, mas propostas como essa são difíceis de encontrar: wink:

Se o escopo do impacto fosse menor - mudanças na resolução de sobrecarga ao passar null como um argumento, por exemplo, onde o comportamento exato não é necessariamente óbvio de imediato, isso poderia ser uma história diferente.

: +1: pessoa sã detectada: @RyanCavanaugh

Que tal nenhuma mudança na maneira existente como as coisas funcionam, mas a introdução de um tipo nulo para sindicatos, que força você a guardar a variável antes de usá-la (ou atribuí-la a uma variável com tipo não nulo)? O comportamento padrão ainda permite que nulos sejam atribuídos a variáveis ​​digitadas normalmente, bem como variáveis ​​digitadas com nulo; no entanto, uma função que pode retornar nulo se marcada corretamente forçará uma conversão.

Dessa forma, não há mudanças significativas, no entanto, com boas práticas, você ainda pode ter um monte de bugs detectados pelo sistema de digitação com digitação inferida e boas práticas de codificação.

Então (mais tarde, talvez?) Você pode adicionar um sinalizador --noImplicitNullCast que evita que null seja atribuído a variáveis ​​que não possuem nulo como parte de sua digitação (isso funciona mais ou menos como a sugestão de @spion com o sinalizador habilitado).

Imagino que isso não seja muito diferente de toda a digitação normal e da adição do sinalizador --noImplicitAny.

@Griffork
A primeira metade do pacote (adicione um tipo null e proíba a desreferenciação sem proteção) não é 100% compatível. Você vai quebrar mais código do que pensa. Considere isto:

Com este novo recurso, muitos defs de lib serão atualizados, incluindo o embutido. Digamos que eles alterem algumas assinaturas para incluir explicitamente nulo como um possível valor de retorno. Alguns exemplos:

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

Existem muitas apis: find retorna undefined se nenhum elemento da matriz corresponde ao predicado, pop retorna undefined se a matriz está vazia, localStorage.getItem ou sessionStorage.getItem ambos retornam null se a chave não foi encontrada.

O código a seguir é legítimo e compilado perfeitamente antes. Agora ele será interrompido com um erro:

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)

Mesma ideia se você buscar algo de localStorage que você sabe que está lá. Há uma abundância de código como esse por aí e agora requer um elenco (ou alguma nova sintaxe) para compilar e dizer ao compilador: suponha que não seja nulo, eu sei o que estou fazendo.

Isso incluirá alguns bugs reais, com certeza. Mas também incluirá muitos falsos positivos e essas são alterações importantes. Em 100K + LOC, isso não é uma atualização do compilador trivial.

Isso provavelmente significa que a única abordagem que funcionaria é: o novo código escrito do zero ou o código antigo migrado aproveita ao máximo a verificação de nulos; o código legado não traz nenhum benefício. Isso sendo conduzido por uma opção do compilador (--enableNullAnalysis) e sem nenhum caminho de migração de "transição" ou "progressiva".

Bem, sim. Mas você sempre pode congelar o lib.d.ts para um projeto ou pegar um antigo.
Uma nova digitação quebrará o código antigo, o que acontecerá de qualquer maneira. Quase toda nova digitação, quando introduzida, requer que o código antigo seja atualizado de uma forma ou de outra para funcionar corretamente (por exemplo, sindicatos, genéricos).
Essa mudança significa que aqueles que desejam ignorar a atualização ou congelar suas definições de biblioteca / código não sofrerão.
Teoricamente, a maior parte do código que usa essas funções deve ser protegida de qualquer maneira (e normalmente estão em grandes bases de código) - porque há bugs ocultos em seu código se você não fizer isso. Portanto, é mais provável que essa alteração detecte erros do que cause alterações de interrupção generalizadas.

Editar
@ jods4 por que você usou um código inseguro como exemplo quando esse é exatamente o tipo de código que pode causar um erro que estamos tentando detectar fazendo essa alteração?

Editar 2
Se você não quebrar _some_ (errado / inseguro) código, então não há nenhum ponto de fazer qualquer alteração a ver com este tema nunca.

Mas, novamente, como este é apenas um problema de sintaxe, tudo ainda seria compilado corretamente, apenas haveria erros em todos os lugares.

_Nova digitação quebrará o código antigo, isso acontecerá de qualquer maneira._

Este não é o caso. Exceto em circunstâncias excepcionais, não adicionaríamos novos recursos que quebrassem o código existente. Os genéricos e as uniões não foram adicionados a lib.d.ts de forma que exija que os consumidores de lib.d.ts atualizem sua base de código para usar a nova versão do compilador. Sim, as pessoas poderiam escolher usar uma versão mais antiga da biblioteca, mas simplesmente não é o caso de que faremos alterações de linguagem que quebram muitos códigos existentes (como é o caso de essencialmente todas as linguagens principais). Haverá uma exceção ocasional para isso (https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes), mas eles serão poucos e distantes entre si, na maioria das vezes se acreditarmos que o código que estamos quebrando só poderia ser um inseto.

Mas você sempre pode congelar o lib.d.ts para um projeto ou pegar um antigo

Congelar lib.d.ts (e qualquer outro .d.ts que eu possa usar, BTW) tem implicações muito fortes. Isso significa que não consigo obter nenhuma atualização nas bibliotecas que uso ou nas novas apis HTML. Isso não é algo para se tomar de ânimo leve.

Teoricamente, a maior parte do código que usa essas funções deve ser protegida de qualquer maneira (e normalmente estão em grandes bases de código) - porque há bugs ocultos em seu código se você não fizer isso.

JS tem uma tradição de retornar undefined (ou às vezes null ) em muitos casos que gerariam um erro em outras linguagens. Um exemplo disso é Array.prototype.pop . Em C #, saltar de uma pilha vazia seria lançado. Portanto, você pode dizer que ele sempre retorna um valor válido. Em Javascript, um array vazio retorna undefined . Se o seu sistema de tipos for rígido quanto a isso, você terá que cuidar dele de alguma forma.

Eu sabia que você iria responder a isso, e é por isso que escrevi exemplos que são códigos legítimos e funcionais. Em grandes bases de código, você encontrará muitos exemplos em que uma api pode retornar nulo em algumas situações, mas você sabe que é seguro em seu caso específico (e portanto ignora todas as verificações de segurança).

Eu estava olhando para a parte da micro tarefa de alguma biblioteca há apenas uma hora. A parte principal basicamente se resume a isso:

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

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

Existem muitos casos como este.

Com relação à sua Edição 2: sim, espero que essa mudança _will_ pegue bugs e aponte código inválido. Estou convencido de que é útil e é por isso que gostaria que acontecesse de alguma forma. Mas a equipe de TS levará em consideração as alterações significativas no código _válido_. Existem trade-offs que precisam ser decididos aqui.

@danquirk Fair, então eu acho que esse recurso nunca poderá ser implementado.
@ jods4 Eu entendo o que você está dizendo, mas anuláveis ​​nunca podem ser implementados sem quebrar esse código.

Triste, devo finalmente encerrar esse problema?

Provavelmente.

Acho que se evitarmos o argumento anulável e não anulável ...

O problema pode ser resolvido (ou mitigado) de forma não intrusiva com um conjunto de novos recursos:

  • Apresente o símbolo null-typeguard ?<type>
    Se um tipo estiver anotado com este símbolo, é um erro acessá-lo diretamente

`` `TypeScript
var foo:? string;

foo.indexOf ('s'); // Error
foo && foo.indexOf ('s'); // OK
`` `

  • Apresente uma bandeira --nouseofunassignedlocalvar

`` `TypeScript
var foo: string;

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

foo = 'bar';

foo.indexOf ('s'); // OK
`` `

Nota histórica interessante : ao tentar encontrar um problema relacionado a isso, me deparei com um problema antigo no codeplex que menciona uma opção de compilador --cflowu em fevereiro de 2013. @RyanCavanaugh , eu me pergunto o que aconteceu com essa bandeira?

  • Apresente o operador de navegação segura nº 16

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

Combinados, esses recursos ajudariam a capturar mais erros em torno do uso de null sem realmente causar um colapso nervoso em todos.

@NoelAbrahams :
Sua primeira proposta é essencialmente igual à última, você está apenas usando um? em vez de | null (leia a postagem de @ jods4 sobre os problemas com a atualização de lib.d.ts e alterações importantes).

Sua segunda proposta tem um problema tratado anteriormente por @RyanCavanaugh (acima):

Sinalizadores que mudam a semântica de um idioma são perigosos. Um problema é que os efeitos são potencialmente muito não locais:
...[recorte]...
O único tipo de coisa segura a fazer é manter a semântica de atribuibilidade a mesma e alterar o que é um erro versus o que não depende de um sinalizador, da mesma forma que noImplicitAny funciona hoje.

Tenho quase certeza de que um sinalizador não diferente foi proposto anteriormente e, em seguida, abandonado devido ao comentário de @RyanCavanaugh .

Sua terceira proposta tem muito pouco a ver com o tópico atual (não é mais sobre digitação e erros em tempo de compilação, mas captura erros em tempo de execução). Digo isso porque este tópico foi criado para ajudar a reduzir a necessidade de fazer verificações indefinidas ou nulas em variáveis ​​que são conhecidas como "seguras" (com uma maneira fácil de rastrear isso), sem adicionar novos nulos ou verificações indefinidas em todos os lugares.

Seria possível implementar uma das propostas sugeridas, e simplesmente não atualizar lib.d.ts para usar os nullables (e as outras libs)? Então, a comunidade pode manter / usar suas próprias versões com anuláveis, se quiser?

Editar:
Especificamente, todas as bibliotecas contêm as informações mais recentes, mas não têm sua digitação atualizada para exigir proteções de tipo.

@RyanCavanaugh Voltarei e discutirei isso se / quando eu tiver um patch que demonstra que não é uma grande mudança (especialmente se os arquivos .d.ts não forem atualizados).

As definições de tipo

De qualquer forma, parece que esse problema é uma causa perdida.

A propósito, houve um compilador modificado pelo MSR que fez isso entre outras coisas - há algum artigo disponível com suas descobertas? Editar: encontrei: http://research.microsoft.com/apps/pubs/?id=224900 mas, infelizmente, não tenho certeza se está relacionado.

@Griffork , sim, tenho certeza de que quase tudo foi discutido de uma forma ou de outra - dada a extensão da discussão. Meu resumo é prático sobre como mitigar os problemas em torno de null, introduzindo uma série de recursos relacionados.

não adicionar novas verificações nulas ou indefinidas em todos os lugares

A captura de variáveis ​​locais não atribuídas mitiga isso e o operador de navegação segura é um recurso ES proposto, então ele pousará em TS em algum ponto.

Eu também acho que a probabilidade de isso chegar ao TS é baixa ... :(

A única solução que posso pensar é tornar a segurança nula um sinalizador de compilador opcional. Por exemplo, introduza a nova sintaxe T | null ou ?T mas não acione _qualquer_ erro a menos que o projeto opte por aceitar. Assim, novos projetos obtêm os benefícios, assim como projetos antigos que optam por tornar seu código compatível com o novo recurso. Bases de código grandes que são grandes demais para serem adaptadas facilmente simplesmente não possuem esse recurso.

Dito isso, mesmo assim, ainda faltam vários problemas para fazer esse recurso voar ...

@NoelAbrahams , desculpe, meu ponto não é que seja uma sugestão ruim, apenas não a resposta que se desejava a partir da instigação dessa discussão.
Com certeza estarei usando esse recurso quando (/ se) ele se tornar nativo, parece muito bom.

@ jods4 que tem o mesmo problema com o comentário @RyanCavanaugh que citei acima (erro de atribuição devido a um problema em outro lugar quando um sinalizador é alterado).

Novamente, a maneira mais fácil é implementar uma das outras propostas (provavelmente @spion ) e não adicionar os novos tipos a .d.ts '.

@Griffork
Não necessariamente, embora este seja um dos "vários problemas" restantes. O recurso deve ser projetado de forma que habilitar o sinalizador _não_ mude a semântica do programa. Deve apenas gerar erros.

Por exemplo, se usarmos o design 'não nulo' !T , não acho que temos esse problema. Mas isso está longe de ser um problema resolvido.

Devo também salientar que, embora T | null seja uma alteração importante, ?T não é.

@ jods4 Eu estava com a impressão de que "mudar a semântica" incluía "mudar a capacidade de atribuição", de qualquer forma minhas preocupações (e possivelmente as de @RyanCavanaugh ) sobre os efeitos não locais ainda permanecem.

@NoelAbrahams how?
T | null requer uma protecção de tipo,? T requer uma protecção de tipo, são a mesma coisa com nomes / símbolos diferentes.
Sim, você pode ter os dois "ligados" com uma bandeira.
Não consigo ver a diferença?

@Griffork , a meu ver ?T significa simplesmente "não acessar sem verificar se há nulo". É possível adicionar essa anotação ao novo código para fazer cumprir a verificação. Estou pensando nisso como uma espécie de operador, em vez de anotação de tipo.

A anotação T|null quebra o código existente porque faz uma declaração sobre se os tipos são anuláveis ​​ou não por padrão.

@jbondc
Interessante ... Eu não analisei muito profundamente sua proposta ainda, mas acho que vou.

No momento, a imutabilidade imposta pelo compilador não é tão interessante em JS quanto em outras linguagens porque seu modelo de threading proíbe dados compartilhados. Mas certamente algo para se manter em mente.

@NoelAbrahams
Todas as propostas de aplicação de nulos são alterações significativas. Mesmo ?T que você descreveu. Mostrei vários exemplos do motivo de alguns comentários acima.

É por isso que acho que a única saída, se quisermos, é torná-la uma opção de compilador e pensar bem o suficiente para que a ativação da opção altere os erros relatados, mas não o comportamento do programa. Não tenho certeza se é viável ou não.

Na verdade, estou meio que bem com isso. Com o TS 1.5, poderei conseguir o que desejo:

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

agora a verificação isVoid é forçada nos valores de retorno para externalUnsafeFunction ou qualquer outra função ou definição de função onde eu adiciono |void ao tipo de retorno.

Ainda não poderei declarar funções que não aceitem null / undefined, mas é possível ser diligente o suficiente para lembrar de inicializar variáveis ​​locais e membros de classe.

Porque @NoelAbrahams já discutimos que mesmo com um tipo nulo, nulo ainda teria que ter permissão para converter implicitamente para qualquer outro tipo, a menos que um sinalizador de compilador futuro alterasse isso.

Também significa que, no futuro, podemos trazer um sinalizador do compilador que nos permite anotar onde e quando uma função pode aceitar nulos.

E, pessoalmente, odeio a ideia de usar símbolos para representar tipos quando podemos usar palavras em seu lugar.

@spion esse é um bom ponto, na verdade. Se isso funcionar no TS atualmente, então sem dúvida todos os arquivos d.ts integrados estão

Na verdade, uma vez que já é algo alcançável, vou propor que comecemos a usar isso esta semana para o meu chefe.

Devo advertir que de forma alguma vemos T|void como uma sintaxe que teríamos medo de quebrar no futuro. É quase um absurdo.

@RyanCavanaugh É tão absurdo quanto void === undefined (um valor atribuível).

Suspirar.

Nosso código é muito grande para começar, dependendo de T|void se ele quebrar em breve e não for substituído. E não vou conseguir convencer os outros programadores a usá-lo se em qualquer semana ele quebrar.

Ok, @spion se você fizer seu patch, me avise e eu irei executá-lo no código base do meu trabalho. Posso pelo menos fornecer estatísticas sobre quantos erros ele causa.

@RyanCavanaugh bobagem, realmente? De que maneira? E o que você sugere para expressar tipos anuláveis ​​que devem ser proibidos para qualquer tipo de consumo antes de uma verificação nula / indefinida?

Eu realmente tenho muitas bibliotecas que se beneficiariam com isso.

@Griffork não será possível remover com segurança void do sindicato antes de 1.5 de qualquer maneira, não sem proteções de tipo definidas pelo usuário, então usar isso só será possível após 1.5 (se for possível)

Bobagem como você escreveria esse código?

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

Claro que não. Então, qual é o significado de adicionar void a uma união de tipos possíveis? Observe esta parte da especificação do idioma que existe há bastante tempo na seção que descreve The Void Type (3.2.4):

_NOTA: Podemos considerar a proibição de declarar variáveis ​​do tipo Void, pois elas não têm uma finalidade útil. No entanto, como Void é permitido como um argumento de tipo para um tipo ou função genérica, não é viável proibir propriedades ou parâmetros Void.

Essa questão:

_E o que você sugere para expressar tipos anuláveis ​​que devem ser proibidos para qualquer tipo de consumo antes de uma verificação nula / indefinida? _

é sobre o que trata todo o tópico. Ryan está meramente apontando que string|void é um absurdo de acordo com a semântica da linguagem atual e, portanto, não é necessariamente razoável assumir uma dependência da semântica sem sentido.

O código que escrevi acima é um TS 1.5 perfeitamente válido, certo?

Vou dar um exemplo que certamente não é absurdo. Temos uma função de biblioteca de banco de dados (nodejs) que chamamos de get() semelhante ao Single () do Linq que infelizmente retorna Promise<null> vez de lançar um erro (uma promessa rejeitada) quando o item não é encontrado. É usado em toda a nossa base de código em milhares de lugares e não é provável que seja substituído ou desapareça em breve. Quero escrever uma definição de tipo que obriga a mim e aos outros desenvolvedores a usar um protetor de tipo antes de consumir o valor, porque tivemos dezenas de bugs difíceis de rastrear decorrentes de um valor nulo movendo-se muito para a base de código antes de ser consumido incorretamente.

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

Isso parece completamente razoável para mim. Adicionar void à união faz com que todas as operações em val sejam inválidas, como deveriam ser. O typeguard restringe o tipo removendo |void e deixa a parte T .

Talvez a especificação não leve em consideração as implicações dos sindicatos com lacunas?

Não proíba variáveis ​​nulas! Eles funcionam como valores de tipo de unidade e podem ser usados
em expressões (ao contrário de void em C # que requer 2 conjuntos de tudo: um
para ações e funções). Por favor, deixe o vazio em paz, ok?
Em 27 de janeiro de 2015, às 20h54, "Gorgi Kosev" [email protected] escreveu:

O código que escrevi acima é um TS 1.5 perfeitamente válido, certo?

Vou te dar um exemplo que certamente não é absurdo. Nós temos uma
função de biblioteca de banco de dados semelhante ao Single () do Linq que, infelizmente
retorna promessaem vez de lançar um erro (uma promessa rejeitada) quando
o item não foi encontrado. É usado em toda a nossa base de código em milhares de
lugares e não é provável que seja substituído ou desapareça em breve. Eu quero escrever um
definição de tipo que força a mim e aos outros desenvolvedores a usar um protetor de tipo
antes de consumir o valor, porque tivemos dezenas de bugs difíceis de rastrear
decorrente de um valor nulo movendo-se muito para a base de código antes de ser
consumido incorretamente.

interface Legacy {
pegue(...):Promessa
}
função isVoid (val: any): val is void {return val == null; } // também captura indefinido

legacy.get (...). then (val => {
// val.consumeNormally () é um erro
if (! isVoid (val)) {
val.consumeNormally (); // OK
}
else {lidar com caso nulo}
});

Isso parece completamente razoável para mim. Você pode apontar qual parte é
Absurdo?

Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -71767358
.

O que estou dizendo é que T|void é essencialmente sem sentido agora porque já temos a regra de que a união de um tipo e seu subtipo é equivalente a simplesmente o supertipo, por exemplo, Cat|Animal é equivalente a Animal . Tratar void como um substituto para os valores null / undefined não é coerente porque null e undefined estão _já no domínio de T _ para todos os T , o que implica que eles devem ser subtipos de T

Em outras palavras, T|null|undefined , se você pudesse escrever isso, já estaria sujeito a colapsar em T . Tratar void como um encantamento para null|undefined é errado porque se realmente fosse o caso, o compilador já teria recolhido T|void em T .

Eu pessoalmente leria este http://www.typescriptlang.org/Content/TypeScript%20Language%20Specification.pdf

Os únicos valores possíveis para o tipo Void são nulos e indefinidos. O tipo Vazio é um subtipo do tipo Qualquer e um supertipo dos tipos Nulo e Indefinido, mas caso contrário, Vazio não está relacionado a todos os outros tipos.

Como; em T|void , T é especificamente _não_ um supertipo de void porque o único supertipo (de acordo com as especificações) é any .

Editar: não permitir isso é um problema diferente.

Edit2: Releia o que você disse, e faz sentido literalmente (no sentido em que a documentação está sem palavras), mas não faz sentido no que se presume ser a interpretação comum (ou correta) da documentação.

@RyanCavanaugh Vou usar o recurso de qualquer maneira até que ele seja interrompido, já que, por enquanto, é provável que me ajude a encontrar vários bugs.

E não posso deixar de apontar a ironia de dizer que recolher T|null|undefined em T "faz sentido", enquanto a existência de T|void não. (Eu sei, é sobre a especificação, mas ainda assim ...)

@jbondc

Não há tipos estruturais estáticos em ECMAScript, portanto, esse argumento é inválido. No ECMAScript, todos os valores são do tipo any portanto, qualquer coisa pode ser atribuída a qualquer coisa ou passada para qualquer coisa. Esse fato não deve implicar em nada sobre o sistema de tipos estáticos do TypeScript, que existe para melhorar o inexistente em JS

É um pouco triste ter que explicar a ironia, mas aqui vai:

  1. você tem um valor que não suporta métodos e propriedades, null
  2. você faz um sistema de tipos onde este valor pode ser atribuído a variáveis ​​de qualquer tipo, a maioria deles suportando vários métodos e propriedades (por exemplo, números de strings ou objetos complexos var s:string = null )
  3. você faz um tipo void que aceita corretamente esse valor e não permite métodos ou propriedades e, como consequência, T|void não permite métodos e propriedades.

A alegação é que (3) é um absurdo, enquanto (2) não é. Você vê a parte que é irônica aqui? Caso contrário, tentarei outro exemplo:

  1. você tem valores que suportam apenas alguns métodos e propriedades {}
  2. você faz um sistema de tipos onde este valor pode ser atribuído a variáveis ​​de qualquer tipo (por exemplo, para números de strings ou objetos complexos var s:string = {} ) que têm um conjunto muito maior de métodos e propriedades
  3. você tem um tipo {} que aceita corretamente os valores {} e não permite métodos ou propriedades além de alguns incorporados, e T|{} naturalmente não permite métodos ou propriedades além dos os embutidos de {}

Agora, qual deles é um absurdo, (2) ou (3)?

Qual é a única diferença entre os dois exemplos? Alguns métodos integrados.

Finalmente, a interface era apenas uma descrição de definição de tipo. Não é uma interface que está sendo implementada em qualquer lugar. É a interface fornecida pela biblioteca, que nunca retorna nada além de promessas, mas pode retornar uma promessa para nulo. Portanto, a segurança é definitivamente muito real.

@spion era exatamente o que eu estava pensando.

Aqui, acho que isso vai esclarecer um pouco as coisas:
De acordo com as especificações :


O que é vazio?

O tipo Void, referenciado pela palavra-chave void, representa a ausência de um valor e é usado como o tipo de retorno de funções sem valor de retorno.

Portanto, void não é null | undefined.

Os únicos valores possíveis para o tipo Void são nulos e indefinidos

Portanto, null e undefined podem ser atribuídos a void (assim como podem ser atribuídos a praticamente qualquer outra coisa).


Nulo é um tipo.

3.2.5 O Tipo Nulo
O tipo Null corresponde ao tipo primitivo JavaScript com nome semelhante e é o tipo do nulo
literal.
O literal nulo faz referência ao único valor do tipo Nulo. Não é possível fazer referência direta ao próprio tipo Nulo.


Indefinido é um tipo.

3.2.6 O Tipo Indefinido
O tipo Undefined corresponde ao tipo primitivo JavaScript com nome semelhante e é o tipo do literal indefinido.


Vazio é apenas um supertipo dos _tipos_ nulo e indefinido

. O tipo Vazio é um subtipo do tipo Qualquer e um supertipo dos tipos Nulo e Indefinido, mas caso contrário, Vazio não está relacionado a todos os outros tipos.


Portanto, @jbondc de acordo com a especificação da linguagem Typescript, null é um valor, Null é um tipo, undefined é um valor, Undefined é um tipo e Void (minúsculas quando em código) é um tipo que é um supertipo de Null e Indefinido, mas _não converte implicitamente_ a qualquer outro tipo (exceto any ). Na verdade, Void foi escrito especificamente para ter um comportamento diferente dos tipos Null e Undefined:

Vazio:

O tipo Vazio é um subtipo do tipo Qualquer e um supertipo dos tipos Nulo e Indefinido, mas caso contrário, Vazio não está relacionado a todos os outros tipos.

Nulo:

O tipo Nulo é um subtipo de todos os tipos, exceto o tipo Indefinido. Isso significa que null é considerado um valor válido para todos os tipos primitivos, tipos de objetos, tipos de união e parâmetros de tipo, incluindo até mesmo os tipos primitivos Number e Boolean.

Indefinido:

O tipo indefinido é um subtipo de todos os tipos. Isso significa que undefined é considerado um valor válido para todos os tipos primitivos, tipos de objetos, tipos de união e parâmetros de tipo.

Mais claro agora?

O que estamos pedindo é um tipo que force uma guarda para nulos e um tipo que force uma guarda para indefinidos. Não estou pedindo que nulo ou indefinido não seja atribuível a outros tipos (isso é muito complicado), apenas a capacidade de optar por marcação extra. Caramba, eles nem precisam ser chamados de nulos ou indefinidos (os tipos).

@jbondc esta é a ironia: Não importa o que o compilador TS diga, os valores nulos e indefinidos em JS nunca suportarão quaisquer métodos ou propriedades (exceto lançar exceções). Portanto, torná-los atribuíveis a qualquer tipo é um absurdo, quase tão absurdo quanto permitir que o valor {} seja atribuído a strings. O tipo void em TS não contém valores, _mas_ null ou undefined podem ser atribuídos a todos e, portanto, podem ser atribuídos a variáveis ​​do tipo void, então há outro absurdo aí. E essa é a ironia - isso (por meio de guardas de tipo e sindicatos) atualmente se combinam em algo que realmente faz sentido :)

Algo tão complexo quanto o compilador TS foi escrito sem proteções, isso é algo para se pensar.

Existe um aplicativo muito grande escrito em PHP ou JAVA, que não torna a linguagem melhor ou pior.
As pirâmides egípcias foram construídas sem nenhuma máquina moderna. Isso significa que tudo o que inventamos nos últimos 4.500 anos é uma porcaria?

@jbondc Veja meu comentário acima com a lista de problemas no compilador TypeScript causados ​​por valores nulos e indefinidos (menos o primeiro da lista)

@jbondc não deveria resolver os problemas do mundo, deveria ser outra ferramenta à disposição do desenvolvedor.
Teria o mesmo impacto e utilidade que sobrecargas de função, uniões e aliases de tipo.
É outra sintaxe opcional que permite aos desenvolvedores digitar valores com mais precisão.

Um dos grandes problemas mencionados para tornar os tipos não anuláveis ​​é o que fazer com o código TypeScript existente. Há dois problemas aqui: 1) Fazer o código existente funcionar com um compilador mais recente que suporta tipos não anuláveis ​​e 2) Interoperar com código que não foi atualizado - evitando um atoleiro de estilo Python 2/3

No entanto, deveria ser possível fornecer uma ferramenta que executa uma reescrita automatizada do código TS existente para torná-lo compilado e, para este caso limitado, algo que funciona muito melhor do que, digamos, 2to3 funcionava para Python.

Talvez uma migração gradual possa ser parecida com isto:

  1. Introduzir suporte para sintaxe '? T' para indicar um tipo possivelmente nulo. Inicialmente, isso não tem efeito na verificação de tipo, está lá apenas para documentação.
  2. Fornece uma ferramenta que reescreve automaticamente o código existente para usar '? T'. A implementação mais idiota converteria todos os usos de 'T' em '? T'. Uma implementação mais inteligente verificaria se o código que usou o tipo implicitamente assumiu que não era nulo e usaria 'T' nesse caso. Para o código que tenta atribuir um valor do tipo '? T' a um parâmetro ou propriedade esperando 'T', isso poderia envolver o valor em um marcador que afirma (em tempo de compilação) que o valor não é nulo - por exemplo, let foo: T = expect(possiblyNullFoo) ou let foo:T = possiblyNullFoo!
  3. Aplique a ferramenta a todas as definições de tipo fornecidas e aquelas no repositório DefinitelyTyped
  4. Introduzir um aviso do compilador ao tentar atribuir '? T' a um slot que espera um 'T'.
  5. Deixe os avisos assar em estado selvagem e verifique se a transferência é gerenciável e veja como a mudança é recebida
  6. Faça com que a atribuição de '? T' a um slot esperando um 'T' seja um erro, em uma nova versão importante do compilador.

Talvez também valha a pena introduzir algo que desabilite os avisos para módulos específicos para facilitar o uso de código ao qual a ferramenta ainda não foi aplicada.

No comentário anterior, estou assumindo que não haveria modo de escolher a nulidade do 'T' simples para evitar a divisão da linguagem e que as etapas 1 a 3 seriam úteis por conta própria, mesmo que a etapa 6 nunca seja prática.

Meus pensamentos aleatórios:

  1. A parte expect() pode ser complicada, mas precisa ser bem pensada. Não existem apenas atribuições, mas interfaces, genéricos ou tipos de parâmetros ...
  2. Acho que o TS não tem nenhum aviso, apenas erros. Se eu estiver certo, não tenho certeza se eles apresentarão avisos apenas para esse recurso.
  3. Mesmo que muitas pessoas atualizem seu código, as empresas podem ser muito lentas para fazer isso ou até mesmo nunca querer fazer isso no caso de grandes bases de código existentes. Isso torna aquele grande lançamento uma grande mudança revolucionária, algo que a equipe do TS não deseja.

Dito isso, ainda estou pensando que um sinalizador "opt-in" para relatar erros nulos é uma solução aceitável. O problema é que o mesmo código / definições devem ter o mesmo comportamento (sem os erros) quando o sinalizador está desligado, algo que ainda não tive tempo de pensar.

@ jods4 - Para a parte expect() , meu pensamento é que onde quer que você tenha ?T o compilador vê T | undefined então você pode reutilizar as regras para lidar com tipos de união até possível.

Concordo, não tenho certeza de quão realista é um 'dia da bandeira' para o TypeScript. Além de um sinalizador 'opt-in', também pode ser um sinalizador opt-in que eventualmente se torna um sinalizador opt-out. O importante é ter uma direção clara sobre o que é o estilo idiomático.

Outra coisa relevante para esta discussão - https://github.com/rwaldron/tc39-notes/blob/master/es6/2015-01/JSExperimentalDirections.pdf discute investigações pela equipe do Chrome sobre o uso de informações de tipo (especificadas por meio do estilo TypeScript anotações) no compilador para otimizar JS. Há uma questão em aberto sobre a nulidade - eles gostariam de tipos não anuláveis, mas também não têm certeza sobre a viabilidade.

Só vou gritar de volta aqui. Estou surpreso por não termos realmente resolvido este.

Eu digo que há um valor agregado aqui. Aquele que só é aplicável em tempo de compilação.
Em vez de ter que escrever mais código para verificar um valor nulo, ser capaz de declarar um valor como não anulável pode economizar tempo e codificação. Se eu descrever um tipo como 'não nulo', então ele deve ser inicializado e possivelmente declarado como não nulo antes de ser passado para outra função que espera não nulo.

Como eu disse acima, talvez isso pudesse ser resolvido simplesmente por uma implementação de contratos de código.

Por que você não pode simplesmente parar de usar o literal nulo e guardar todos os lugares onde
nulos podem vazar em seu código? Desta forma, você estará a salvo de null
exceção de referência sem ter que fazer verificações de nulos em todos os lugares.
Em 20 de fevereiro de 2015, 13h41, "electricessence" [email protected] escreveu:

Só vou gritar de volta aqui. Estou surpreso por não termos realmente resolvido
Este.

Eu digo que há um valor agregado aqui. Aquele que só é aplicável na compilação
Tempo.
Em vez de ter que escrever mais código para verificar um valor nulo, ser capaz de
declarar um valor como não anulável pode economizar tempo e codificação. Se eu descrever um
digite como 'não anulável' então ele deve ser inicializado e possivelmente declarado
como não nulo antes de ser passado para outra função que espera
não nulo.

Como eu disse acima, talvez isso pudesse simplesmente ser resolvido por um contrato de código
implementação.

Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -75295204
.

As verificações nulas são significativamente mais rápidas do que as verificações indefinidas.
Em 21/02/2015 11h54, "Aleksey Bykov" [email protected] escreveu:

Por que você não pode simplesmente parar de usar o literal nulo e guardar todos os lugares onde
nulos podem vazar em seu código? Desta forma, você estará a salvo de null
exceção de referência sem ter que fazer verificações de nulos em todos os lugares.
Em 20 de fevereiro de 2015, 13h41, "electricessence" [email protected]
escreveu:

Só vou gritar de volta aqui. Estou surpreso por não termos realmente resolvido
Este.

Eu digo que há um valor agregado aqui. Aquele que só é aplicável na compilação
Tempo.
Em vez de ter que escrever mais código para verificar um valor nulo, ser capaz de
declarar um valor como não anulável pode economizar tempo e codificação. Se eu descrever um
digite como 'não anulável', então ele deve ser inicializado e possivelmente
afirmou
como não nulo antes de ser passado para outra função que espera
não nulo.

Como eu disse acima, talvez isso pudesse simplesmente ser resolvido por um contrato de código
implementação.

Responda a este e-mail diretamente ou visualize-o no GitHub
<
https://github.com/Microsoft/TypeScript/issues/185#issuecomment-75295204>
.

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -75346198
.

Meu ponto de que essas verificações são desnecessárias (se a palavra-chave nula for banida
e todos os outros lugares onde ele pode entrar são guardados).

Por quê? 1. O código sem verificações é executado significativamente mais rápido do que o código
com verificações de nulos. 2. Contém menos if's e, portanto, mais legível
e sustentável.
Em 20 de fevereiro de 2015, 19:57, "Griffork" [email protected] escreveu:

As verificações nulas são significativamente mais rápidas do que as verificações indefinidas.
Em 21/02/2015 11h54, "Aleksey Bykov" [email protected] escreveu:

Por que você não pode simplesmente parar de usar o literal nulo e guardar todos os lugares onde
nulos podem vazar em seu código? Desta forma, você estará a salvo de null
exceção de referência sem ter que fazer verificações de nulos em todos os lugares.
Em 20 de fevereiro de 2015, 13h41, "electricessence" [email protected]
escreveu:

Só vou gritar de volta aqui. Estou surpreso por não termos realmente resolvido
Este.

Eu digo que há um valor agregado aqui. Aquele que só é aplicável na compilação
Tempo.
Em vez de ter que escrever mais código para verificar um valor nulo, ser capaz de
para
declarar um valor como não anulável pode economizar tempo e codificação. Se eu
descrever um
digite como 'não anulável', então ele deve ser inicializado e possivelmente
afirmou
como não nulo antes de ser passado para outra função que espera
não nulo.

Como eu disse acima, talvez isso pudesse simplesmente ser resolvido por um contrato de código
implementação.

Responda a este e-mail diretamente ou visualize-o no GitHub
<
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -75295204

.

Responda a este e-mail diretamente ou visualize-o no GitHub
<
https://github.com/Microsoft/TypeScript/issues/185#issuecomment-75346198>
.

Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -75346390
.

Você está perdendo o ponto, usamos nulos para evitar verificações indefinidas.
E uma vez que nosso aplicativo não tem um estado consistente, nós temos, e
frequentemente tem que ter coisas nulas / indefinidas em vez de um objeto para
representar isso.
Em 21/02/2015 12:20, "Aleksey Bykov" [email protected] escreveu:

Meu ponto de que essas verificações são desnecessárias (se a palavra-chave nula for banida
e todos os outros lugares onde ele pode entrar são guardados).

Por quê? 1. O código sem verificações é executado significativamente mais rápido do que o código
com verificações de nulos. 2. Contém menos if's e, portanto, mais legível
e sustentável.
Em 20 de fevereiro de 2015, 19:57, "Griffork" [email protected] escreveu:

As verificações nulas são significativamente mais rápidas do que as verificações indefinidas.
Em 21/02/2015 11h54, "Aleksey Bykov" [email protected]
escreveu:

Por que você não pode simplesmente parar de usar o literal nulo e proteger todos os lugares
Onde
nulos podem vazar em seu código? Desta forma, você estará a salvo de null
exceção de referência sem ter que fazer verificações de nulos em todos os lugares.
Em 20 de fevereiro de 2015, 13h41, "electricessence" [email protected]
escreveu:

Só vou gritar de volta aqui. Estou surpreso que não tenhamos realmente
resolvido
Este.

Eu digo que há um valor agregado aqui. Aquele que só é aplicável em
compilar
Tempo.
Em vez de ter que escrever mais código para verificar um valor nulo, sendo
capaz
para
declarar um valor como não anulável pode economizar tempo e codificação. Se eu
descrever um
digite como 'não anulável', então ele deve ser inicializado e possivelmente
afirmou
como não nulo antes de ser passado para outra função que espera
não nulo.

Como eu disse acima, talvez isso pudesse simplesmente ser resolvido por um código
contratos
implementação.

Responda a este e-mail diretamente ou visualize-o no GitHub
<

https://github.com/Microsoft/TypeScript/issues/185#issuecomment -75295204

.

Responda a este e-mail diretamente ou visualize-o no GitHub
<
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -75346198

.

Responda a este e-mail diretamente ou visualize-o no GitHub
<
https://github.com/Microsoft/TypeScript/issues/185#issuecomment-75346390>
.

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -75347832
.

Você está perdendo meus pontos também. Para manter seu estado consistente e ser capaz de representar valores ausentes, você não precisa usar nulos ou indefinidos. Existem outras maneiras, especialmente agora com suporte a tipos de união, 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);
}

Portanto, apenas codificamos um valor ausente sem usar um valor nulo ou indefinido. Observe também que estamos fazendo isso de maneira 100% explícita . Graças aos guardas do tipo, não há como perder um cheque antes de ler um valor.

Quão legal é isso?

Você acha que uma instância de verificação é mais rápida do que uma verificação nula, para um
aplicativo que tem que fazer centenas desses por segundo?
Deixe-me colocar desta forma: tivemos que criar apelidos para funções porque
é mais rápido não usar '.' acessores.
Em 21/02/2015 12:58, "Aleksey Bykov" [email protected] escreveu:

Você está perdendo meus pontos também. A fim de manter seu estado consistente e
ser capaz de representar valores ausentes, você não precisa usar nulos ou
Indefinido. Existem outras maneiras, especialmente agora com suporte a tipos de união
considerar:

class Nothing {public 'não sou nada': Nothing; }
classe algo {
construtor(
nome público: string,
valor público: número
) {}
}
var nada = novo Nada ();
var whoami = Math.random ()> 0,5? novo Algo ('meh', 66): nada;

if (whoami instanceof Nothing) {
// whoami não é nada
console.log ('Eu sou um assassino nulo');
} else if (whoami instanceof Something) {
// whoami é uma instância de algo
console.log (whoami.name + ':' + whoami.value);
}

Então, codificou um valor ausente sem usar um valor nulo ou indefinido.
Observe também que estamos fazendo isso de maneira _100% explícita_. Obrigado a digitar
guardas, não há como perder uma verificação antes de ler um valor.

Quão legal é isso?

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -75349967
.

@ aleksey-bykov Observe que já abordei esse ponto: a necessidade de modelar definições de tipo para bibliotecas existentes que já usam valores nulos não foi resolvida. Nesse caso, você não pode simplesmente "sair usando" valores nulos.

Além disso, isso nem mesmo toca nos problemas com variáveis ​​não inicializadas que serão undefined . Em Haskell, o problema nem existe porque você não pode deixar um valor "não inicializado".

@Griffork , acho que não, faço testes, os testes dizem que depende do navegador.
http://jsperf.com/nullable-vs-null-vs-undefined-vs-instanceof
O que eu acho, porém, é que você precisa encontrar um equilíbrio entre segurança e desempenho. Em seguida, você precisa medir seu desempenho com cuidado antes de tentar otimizá-lo. Provavelmente, você está consertando algo que não está quebrado e suas verificações de nulos com as quais você está tão preocupado podem constituir menos de 2% do desempenho geral, se assim for, se você dobrar mais rápido, você ganhará apenas 1%.

@spion , considerando todas as coisas, é uma maneira muito mais agradável do que continuar usando nulos em seu código, dada a situação atual e os problemas que os não nulos trazem com eles

Nossa base de código tem mais de 800 arquivos TypeScript e mais de 120000 linhas de código
e nunca precisamos de nulos ou indefinidos quando se trata de modelagem de negócios
entidades de domínio. E embora tivéssemos que usar nulos para manipulações de DOM,
todos esses lugares são cuidadosamente isolados, de modo que os nulos não tenham como vazar
in. Eu não acredito em seu argumento de que nulos podem ser necessários, há
linguagens prontas para produção sem nulos (Haskell) ou com nulos
banido (F #).
Em 22 de fevereiro de 2015, às 9h31, "Jon" [email protected] escreveu:

@ aleksey-bykov https://github.com/aleksey-bykov De uma forma prática
perspectiva, você não pode ignorar null. Aqui estão algumas informações sobre por que você
precisa de um tipo anulável de qualquer maneira:
https://www.google.com/patents/US7627594?ei=P4XoVPCaEIzjsATm4YGoBQ&ved=0CFsQ6AEwCTge

Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -75437927
.

Sim, e a coisa mais importante que você pode entender aqui é que NÃO estamos modelando entidades de domínio de negócios, SOMOS ALTAMENTE sensíveis ao tempo e nossa base de código não é muito menor que a sua.

Para seu propósito, você não precisa de nulos, o que é bom, você pode continuar usando o Typescript sem eles.
Para nosso propósito, precisamos de nulos e não podemos deixar de fazê-lo.

Oh, acabei de ver seu outro comentário, desculpe.
Se você leu um pouco antes neste tópico, notará que eu disse (essencialmente) que sempre usamos nulos e raramente tivemos problemas com eles se infiltrando em outras partes do código.

Se você também leu acima, a proposta não anulável também abrange os indefinidos.

Nulo e indefinido em variáveis ​​para sua velocidade parecem semelhantes, mas assim que eles estão presentes em um objeto (particularmente um com uma cadeia de protótipo) a história é muito diferente, então usamos nulos em seu lugar.

Novamente, provavelmente não vale a pena se preocupar, a menos (como nós), você está fazendo 10.000 verificações em <10ms.

(E sim, na verdade mudamos devido ao rastreamento do desempenho de nosso aplicativo e à descoberta de um gargalo)

Eu gostaria de estimular alguma discussão sobre isso. Ainda ontem, no vídeo Build

Basicamente, o analisador é uma biblioteca que pode fazer verificações de sintaxe adicionais, linhas de tag como erros / avisos e são interpretadas pelo serviço de linguagem. Isso permitiria efetivamente impor verificações nulas com a biblioteca externa.

Na verdade, vejo que os analisadores no TypeScript também seriam ótimos para outras coisas. Existem bibliotecas realmente estranhas por aí, mais do que em C #.

Para qualquer um que esteja acompanhando este problema: Estou mantendo uma biblioteca chamada monapt que permite declarar a nulidade de uma variável. Inclui definições de Typescript, então tudo é verificado em tempo de compilação.

Feedback seja bem-vindo!

Acabei de ler todo o tópico.

Eu acho que a proposta deve usar non-voidable e voidable termos, porque void na especificação TS é um tipo para o null e undefined . (Presumo que a proposta também cobre um valor indefinido: sorria:)

Também concordo que o acesso indefinido ou exceção nula é a raiz de todos os males da programação, a adição desse recurso economizará horas incontáveis ​​para as pessoas.

Por favor, adicione isso! E seria melhor se isso se tornasse um recurso que eu pudesse passar um sinalizador para torná-lo o padrão. Isso economizaria muito trabalho de depuração para muitas pessoas.

Embora eu possa concordar que ADTs podem ser usados ​​no lugar de tipos anuláveis; como podemos ter certeza de que o ADT que passamos não é nulo em si? Qualquer tipo de TS pode ser nulo, incluindo ADTs, certo? Eu vejo isso como uma grande falha em pensar sobre os ADTs como uma solução para o problema dos tipos não anuláveis ​​(não anuláveis). Os ADTs funcionam tão bem quanto em linguagens que permitem (ou proíbem) tipos não anuláveis.

Outra questão importante são as definições da biblioteca. Posso ter uma biblioteca que espera uma string como argumento, mas o compilador TS pode digitar check passando null para a função! Isso não está certo ...

Eu vejo isso como uma grande falha em pensar sobre os ADTs como uma solução para o problema ...

  1. obter ADTs disponíveis através de um padrão de design (devido à falta de
    suporte do TypeScript)
  2. proíba a palavra-chave null
  3. proteja todos os lugares onde nulos podem vazar para o seu código de fora
  4. aproveite o problema de null resolvido para sempre

Em nosso projeto, conseguimos realizar as etapas 1, 2 e 3. Adivinhe o que estamos fazendo
agora.

@ aleksey-bykov

Como você se sai (2) com undefined ? Pode surgir em uma variedade de situações que não envolvem a palavra-chave undefined : membros da classe não inicializados, campos opcionais, declarações de retorno ausentes em ramos condicionais ...

Compare typescript com flowtype . Pelo menos o fluxo lida com a maioria dos casos.

Além disso, se você estiver disposto a dizer "Esqueça as expressões idiomáticas comuns na língua. Não me importo, vou me isolar do resto do mundo", então provavelmente é melhor usar o purescript .

Pode surgir em várias situações que não envolvem a palavra-chave indefinida ...

  • membros da classe não inicializados - classes proibidas
  • campos opcionais - campos / parâmetros opcionais proibidos
  • declarações de retorno ausentes - uma regra tslint personalizada que garante que todos os caminhos de execução retornem

melhor usar o purescript

  • eu gostaria, mas o código que ele gera preocupa o mínimo com o desempenho

@aleksey-bykov, é engraçado que você mencione desempenho, porque substituir tipos anuláveis ​​por ADTs impõe sobrecarga significativa de CPU e memória em comparação com apenas a aplicação de verificações nulas (como tipo de fluxo). O mesmo vale para não usar classes (pode ser proibitivamente caro se você instanciar muitos objetos com muitos métodos neles)

Você mencionou desistir de campos opcionais, mas eles são usados ​​por muitas bibliotecas JS. O que você fez para lidar com essas bibliotecas?

Você também mencionou não usar classes. Suponho que você também evite bibliotecas que dependem (ou irão depender) de classes (como, por exemplo, React).

Em qualquer caso, não vejo razão para desistir de tantos recursos quando uma solução perfeitamente razoável (que na verdade se ajusta à linguagem não digitada subjacente) é mais provável de ser implementada.

é engraçado que você pense que o único propósito dos ADTs é representar um valor ausente que teria sido codificado por nulo

eu prefiro dizer que ter o problema "nulo" desaparecido (e as classes também) é um subproduto de todas as coisas boas que os ADTs nos trazem

atendendo à sua curiosidade, para peças críticas de desempenho, como loops apertados e grandes estruturas de dados, usamos a chamada Nullable<a> abstração, fazendo com que o TypeScript pense que lida com um valor agrupado, mas na verdade (em tempo de execução) tal valor é justo uma primitiva que pode receber nulos, há um conjunto especial de operações em Nullable que impede que nulos vazem

deixe-me saber se você quiser saber os detalhes

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 que usar uma estrutura de tipo recursiva? 'a nullable': a deve ser suficiente, não?

Suponho que você também tenha callFunctionWithNullableResult<T,U>(f: (T) => U, arg:T):Nullable<U> (incluindo todas as outras sobrecargas para funções com aridade diferente) para lidar com todas as funções externas que retornam nulos?

E se a palavra-chave nula é proibida por um linter, como você obtém um " Nothing " armazenado em um anulável? você tem um caso especial de toNullable(null) ?

Eu sei o que são tipos de dados algébricos. Você pode me dar uma instância (diferente de verificações de exaustividade e clunkiness) onde ADTs (ou melhor, tipos de soma) + correspondência de padrão podem fazer algo que as uniões de texto + protetores de tipo definidos pelo usuário não podem fazer?

Por que usar uma estrutura de tipo recursiva? 'a anulável': a deve ser suficiente, não?

bom, nesse exemplo é o suficiente, mas fazer assim sobrecarrega a finalidade desse campo, que atende a 2 casos distintos:

Eu pessoalmente odeio sobrecarregado, o que significa que é por isso que eu escolho 2 pseudocampos separados, onde cada um resolve um problema separado

Suponho que você também tenha callFunctionWithNullableResult(f: T -> U, arg: T) => Anulável

não, como eu disse, alteramos os arquivos de definição (* .d.ts) substituindo tudo o que temos evidência pode ser nulo no código externo por um Nullable "wrapper", veja o exemplo no comentário na minha mensagem anterior

como você obtém um "Nothing" armazenado em um anulável

não armazenamos nulos em anuláveis, anuláveis ​​vêm do servidor (sobre o qual temos controle limitado) ou de APIs externas, assim que estiver em nosso código, é empacotado em Nullable, mas nosso código não é capaz de produzir um anulável à vontade, só podemos transformar um dado anulável em outro anulável, mas não para criar um do nada

as uniões de datilografia + protetores de tipo definidos pelo usuário não podem fazer

ha! Eu não sou um fã de tipos sindicais e posso falar por horas sobre o que os torna uma má ideia

de volta à sua pergunta, dê-me um equivalente reutilizável de Either a b usando uniões e protetores de tipo
(uma dica: https://github.com/Microsoft/TypeScript/issues/2264)

Os tipos em seu exemplo não são mais nominais apenas porque são recursivos - eles permanecem "pseudo-nominais".

Para responder à sua pergunta: você não implementa um Either genérico, precisamente porque os tipos não são nominais. Você simplesmente usa

type MyType = A | B

e, em seguida, use protetores de tipo para A e B ... Como um bônus, isso funciona com todas as bibliotecas JS existentes também: por exemplo, a forma idiomática de verificar Thenable é se há é um método then anexado ao objeto. Um tipo de guarda funcionará para isso? Certamente.

Ha! Essa é uma ótima captura sua. Bem, algo deve ter mudado nas versões recentes do compilador. Aqui está o nominal à prova de balas:

enum GottaBeNonimalBrand {}
interface GottaBeNonimal {
     branded: GottaBeNonimalBrand;
}
function toGottaBeNominal() : GottaBeNominal {
   return <any> {};
}

Espere um segundo! Você trapaceou ! O bom e velho jeito ainda funciona como um encanto.

Para responder à sua pergunta: você não implementa um Either genérico, precisamente porque os tipos não são nominais. Você simplesmente usa ...

isso é bom o que você diz, mas como é Either ? que Either autêntico é GENÉRICO e existem cerca de 30 funções de conveniência padrão em torno dele, agora você está dizendo que eu preciso ir resolvido (nominal) e ter até 30 x número_de_combinações_de_qualquer_2_tipos_in_my_app dessas funções para todos os pares possíveis que eu posso encontro?

Esperançosamente, este tópico ainda é monitorado por pessoas da equipe TS. Eu quero tentar reiniciá-lo!
Parece-me que os tipos não nulos ficaram presos quando se tornou óbvio que era impossível introduzir isso de uma forma ininterrupta. A equipe C # está pensando em tipos de referência não anuláveis ​​e, é claro, enfrenta os mesmos problemas, principalmente compatibilidade. Gosto das ideias que eles estão explorando no momento e me pergunto se seriam aplicáveis ​​ao TS.

C # vnext brainstorm (brevemente)

Basicamente, em C # você tem tipos de valor não nulos, como int . Então, em C # 2.0, obtivemos os tipos de valor anuláveis, como int? . As referências sempre foram anuláveis, mas eles estão pensando em mudar isso e aplicar a mesma sintaxe: string nunca é nula, string? pode ser.

Obviamente, o grande problema é que isso muda o significado de string e quebra o código. Onde não havia problema em atribuir / retornar null a um string , agora é um erro. A equipe C # está pensando em "desabilitar" esses erros, a menos que você aceite. Pontuar em string? é sempre um erro, porque a sintaxe é nova e não era permitida antes (portanto, não é uma alteração importante).
O outro problema é com outras bibliotecas, etc. Para resolver isso, eles querem fazer o sinalizador opt-in por arquivo ou assembly. Portanto, o código externo que opta pelo sistema de tipo mais rígido obtém os benefícios, e o código e as bibliotecas legadas antigas continuam a funcionar.

Como isso poderia se transpor para o TS?

  1. Apresentamos um sinalizador de aceitação de tipos não nulos. Pode ser global (passado para o compilador) ou por arquivo. Deve ser sempre por arquivo por .d.ts .
  2. Os tipos básicos não podem ser anulados, por exemplo, number . Existe um novo tipo null .
  3. number? é simplesmente um atalho para number | null .
  4. O recurso introduz novos erros para tipos anuláveis, como (x: string?) => x.length sem um protetor de tipo.
  5. O recurso introduz novos erros para tipos não anuláveis, como assign let x: string = null;
  6. Ao manipular um tipo não nulo que é declarado fora do escopo opt-in, os erros relatados em 5. são ignorados. _Isso é um pouco diferente de dizer que string ainda é considerado string? ._

Que bom é isso?

Quando escrevo um novo código, posso aceitar e obter verificações nulas estáticas completas em meu código e em definições de biblioteca atualizadas. Posso continuar a usar as definições da biblioteca legada sem erros.

Quando eu uso o código antigo, posso optar por novos arquivos. Posso declarar string? qualquer maneira e obter erros ao acessar membros sem uma verificação de nulos adequada. Também recebo benefícios e verificações rigorosas para bibliotecas cujas definições foram atualizadas. Uma vez que .d.ts opta por entrar, passar null para uma função definida como length(x: string): number torna-se um erro.
_ (Nota: Passar string? é um erro, embora passar string do código de cancelamento não seja.) _

Contanto que eu não aceite, não há alterações significativas.

O que você acha?

Obviamente, há muito a ser considerado sobre como o recurso funcionaria na prática. Mas esse esquema de aceitação é algo que a equipe de TS pode até considerar?

é incrível como as pessoas querem um cavalo mais rápido em vez de um automóvel.

Não precisa ser condescendente. Eu li seus comentários sobre o ADT e não acho que eles sejam o que eu preciso. Podemos discutir isso se você quiser, mas em uma nova edição sobre "suporte ADT no TS". Você pode escrever aqui por que eles são ótimos, por que precisamos deles e como eles aliviam a necessidade de tipos não anuláveis. Esse problema é sobre tipos não anuláveis ​​e você está realmente sequestrando o tópico.

o que você está falando é chamado de propagação constante e, se implementado, não deve ser limitado apenas por 2 constantes aleatórias que por acaso são null e undefined . O mesmo pode ser feito com strings, números e tudo o mais e desta forma faz sentido, caso contrário, seria apenas para nutrir alguns velhos hábitos que algumas pessoas não estão dispostas a

@ aleksey-bykov e, claro, para obter melhores restrições de tipo, você implementa tipos de tipos superiores e, para obter instâncias derivadas, implementa typeclasses e assim por diante. Como isso se encaixa com o objetivo de design "alinhar-se com ES6 +" do TypeScript?

E ainda é tudo inútil para resolver o problema original deste problema. É fácil implementar Maybe (and Either) em TS usando classes genéricas, ainda hoje (sem tipos de união). Será tão útil quanto adicionar Optional ao Java, que é: apenas. Porque alguém irá apenas atribuir null algum lugar ou precisa de propriedades opcionais, e a própria linguagem não reclamará, mas as coisas explodirão em tempo de execução.

Não está claro para mim por que você insiste em abusar da linguagem para fazê-la dobrar de uma forma que nunca foi projetada. Se você deseja uma linguagem funcional com tipos de dados algébricos e sem valores nulos, basta usar PureScript. É uma linguagem excelente e bem projetada com recursos que se encaixam de forma coerente, testados em batalha ao longo dos anos em Haskell sem as verrugas acumuladas de Haskell (hierarquia de typeclass baseada em princípios, registros reais com polimorfismo de linha ...). Parece-me muito mais fácil começar com uma linguagem que adere aos seus princípios e precisa, em seguida, ajustar para o desempenho, em vez de começar com algo que não (e nunca foi feito para) torcer, girar e restringir para chegar a trabalhe do seu jeito.

Qual é o seu problema de desempenho com PureScript? São os fechos produzidos por currying generalizado?

@ jods4 significa que todos os arquivos .d.ts existentes no momento precisarão ser atualizados com um sinalizador de fora do trabalho ou que a configuração de todo o projeto não afeta .d.ts (e um .d.ts sem uma bandeira é sempre considerado o método 'antigo'.
Quão compreensível é isso (vai causar problemas para as pessoas que trabalham em vários projetos ou confusão ao ler arquivos .d.ts)?
Qual será a aparência da interface entre o código novo e o antigo (biblioteca). Estará repleto de (em alguns casos) verificações e conversões desnecessárias (provavelmente não é uma coisa ruim, mas pode desligar os iniciantes da linguagem)?
Principalmente (agora) gosto da ideia de tipos não anuláveis ​​e da sugestão de @ jods4 e ! sugestão é a única maneira de ver os tipos não anuláveis ​​funcionando (ignorando adts).

Pode ser perdido neste tópico, mas que problema isso resolve? Isso é algo que me fez alusão. Os argumentos que vi recentemente neste tópico são "outras línguas têm". Também estou tendo dificuldade em ver o que isso resolve de útil que não requer algum tipo de maquinação no TypeScript. Embora eu possa entender que os valores de null e undefined podem ser indesejáveis, tendo a ver isso como uma lógica de ordem superior que desejo colocar em meu código, em vez de esperar que a linguagem subjacente o controle .

O outro ponto menor, é que todos estão tratando undefined como uma constante ou uma palavra-chave. Tecnicamente, também não. No contexto global, existe uma variável / propriedade undefined que é do tipo primitivo undefined . null é um literal (que também é do tipo primitivo null , embora seja um bug de longa data implementado para retornar typeof "object" ). No ECMAScript, essas são duas coisas bastante diferentes.

@kitsonk
O problema é que todos os tipos podem ser nulos e todos os tipos podem ser indefinidos, mas nem todas as operações que funcionam em um tipo específico não funcionam em valores nulos ou indefinidos (por exemplo, divisão), causando erros.
O que eles querem é a capacidade de especificar um tipo como "é um número e nunca é nulo ou indefinido" para que possam garantir que, ao usar uma função que tenha a possibilidade de introduzir programadores nulos ou indefinidos, saibam se proteger contra valores inválidos.
Existem dois argumentos principais para poder especificar um valor como "é este tipo, mas não pode ser nulo ou indefinido":
A) é óbvio para todos os programadores que usam o código que esse valor nunca deve ser nulo ou indefinido.
B) o compilador ajuda a capturar protetores de tipo ausentes.

há uma variável / propriedade indefinida que é do tipo primitivo indefinido. nulléum literal (que também é do tipo primitivo null

@kitsonk este é um grande argumento para este problema; null não é number , não é mais Person que Car . É seu próprio tipo e essa questão é sobre permitir que o texto digitado faça essa distinção.

@kitsonk Acho que um dos erros mais comuns são erros de referência nula / acesso indefinido. Ter a capacidade de especificar uma propriedade ou variável para não ser nula / indefinida tornará o código do software mais confiável. E também permite que ferramentas como o LS detectem bugs como este https://github.com/Microsoft/TypeScript/issues/3692.

Obrigado pelo esclarecimento. Talvez eu esteja perdendo algo aqui de novo, tendo revisto isso ... Visto que já existe um argumento de "opcionalidade" no TypeScript, por que o seguinte não funcionaria como uma proposta?

interface Mixed {
    optional?: string;
    notOptional: string;
    nonNullable!: string;
}

function mixed(notOptional: string, notNullable!: string, optional?: string): void {}

Embora haja maior funcionalidade em algumas das soluções propostas, suspeito que a proteção contra eles se torne um desafio, pois isso (embora não conheça bem o funcionamento interno do TypeScript) parece uma extensão do que já está sendo verificado.

Visto que já existe um argumento de "opcionalidade" no TypeScript, por que o seguinte não funcionaria como uma proposta?

Só para esclarecer, os opcionais agora no TS são opcionais apenas durante a inicialização. Null e undefined ainda podem ser atribuídos a todos os tipos.

Estes são alguns casos não intuitivos de opcionais

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

Ou apenas para mantê-lo simples optional em TS significa não inicializável. Mas esta proposta diz que indefinido e nulo não podem ser atribuídos a um tipo não anulável (não nulo, não indefinido).

O ? é colocado pelo nome da propriedade / argumento porque se refere à _existência_ desse nome ou não. @tinganho explicou bem. Estendendo seu exemplo:

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

O valor não nulo ! não está relacionado à _existência_ de um nome. Está relacionado ao tipo, por isso deve ser colocado por tipo. O tipo não é anulável.

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 Existente .d.ts funcionaria bem e pode ser atualizado sem problemas para suportar definições mais restritas.
Por padrão, .d.ts não aceita. Se um autor de biblioteca atualiza sua biblioteca com definições não nulas fiéis, ele indica isso definindo um sinalizador no topo do arquivo.
Em ambos os casos, tudo funciona sem qualquer pensamento / configuração do consumidor.

@kitsonk O problema que isso resolve é reduzir bugs porque as pessoas presumem que algo não é nulo quando deveria ser, ou passam nulos para funções que não o suportam. Se você trabalha em grandes projetos com muitas pessoas, pode consumir funções que não escreveu. Ou você pode consumir bibliotecas de terceiros que não conhece bem. Quando você faz: let x = array.sum(x => x.value) , você pode assumir que x nunca é nulo? Talvez seja 0 se a matriz estiver vazia? Como você sabe? É permitido que sua coleção tenha x.value === null ? Isso é suportado pela função sum() ou será um erro?
Com esse recurso, esses casos são verificados estaticamente e a passagem de um valor possivelmente nulo para uma função que não o suporta ou o consumo de um valor possivelmente nulo sem uma verificação são relatados como erros.
Basicamente, em um programa completamente digitado, não pode haver mais "NullReferenceException" (menos os casos extremos relacionados a undefined , infelizmente).

A discussão sobre os parâmetros opcionais não está relacionada. Permitir nulo ou não está relacionado ao sistema de tipo, não às declarações de variável / parâmetro e se integra perfeitamente com proteções de tipo, tipos de união e interseção, etc.

tipo _void_ não tem nenhum valor, mas os valores null e undefined podem ser atribuídos a ele

aqui está um bom resumo: https://github.com/Microsoft/TypeScript/issues/185#issuecomment -71942237

@jbondc Acho que verifiquei as especificações recentemente. void é um termo genérico para null e undefined . Não sei sobre void 0 , mas acho que void também o tem sob seu guarda-chuva.

Infers any . mas null e undefined ainda podem ser atribuídos a todos os tipos.

void 0 sempre retorna undefined também http://stackoverflow.com/a/7452352/449132.

@jbondc se a questão for sobre um sistema de tipo potentiel null-aware, como o que descrevi acima,

function returnsNull() {
  return null;
}

deve inferir o tipo null .

Existem muitos casos extremos em torno de undefined e, para ser honesto, (ainda?) Não tenho certeza sobre qual é a melhor maneira de lidar com isso. Mas antes de gastar muito tempo pensando nisso, gostaria de saber se a equipe de TS aceitaria uma estratégia de aceitação.

A última vez que participei dessa discussão, ela terminou com: "não faremos alterações significativas e não queremos alterar o significado do código-fonte com base em opções / sinalizadores de ambiente". A ideia que descrevo acima não é uma alteração significativa e é relativamente boa no que diz respeito à interpretação da fonte, embora não seja 100%. É por essa área cinzenta que pergunto o que pensa a equipe do TS.

@ jods4 Eu preferiria o anulável inferido any , que manteria a sintaxe existente. E uma estratégia de aceitação seria preferível. [1]

Eu preferiria uma sintaxe explícita para tipos não anuláveis. Para objetos, isso quebraria muitas coisas de uma vez. E também, os argumentos opcionais devem sempre ser anuláveis ​​por razões óbvias (e deve ser impossível de alterar). Os argumentos padrão devem reter sua assinatura atual externamente, mas dentro da própria função, o argumento pode se tornar não anulável.

Eu acho isso absurdo, entretanto, uma vez que números anuláveis ​​quebram muitos dos mecanismos de otimização podem fazer, e não é um caso de uso comum fora dos argumentos opcionais / padrão. Pode ser um pouco mais viável / não separável tornar os números que não são argumentos opcionais não anuláveis ​​por padrão por meio de um sinalizador do compilador. Isso também exigiria uma sintaxe para marcar explicitamente os tipos anuláveis, mas já estou vendo isso como um resultado provável desta discussão.

[1] Apresentar um sinalizador para torná-lo o padrão não funcionaria bem em primeiro lugar por motivos práticos. Basta olhar os problemas que as pessoas enfrentaram com --noImplicitAny , que resultou em alguns riscos de migração, muitos problemas práticos no teste de existência de propriedade levando a --suppressImplicitAnyIndexErrors e várias definições DefinitelyTyped quebradas.

@impinball

Eu preferiria o anulável inferido any , que manteria a sintaxe existente. E uma estratégia de aceitação seria preferível.

Eu editei minha resposta aqui. Pensando mais sobre isso, simplesmente não vejo nenhum caso útil em que você possa inferir implicitamente apenas o tipo null . Como: () => null ou let x = null ou function y() { return null } .

Portanto, concordo que continuar a inferir any é provavelmente uma coisa boa.

  1. É compatível com versões anteriores.
  2. Todos esses casos são erros em noImplicitAny qualquer maneira.
  3. Se você tiver um caso estranho em que deseja fazer isso e for útil para você, pode declarar explicitamente o tipo: (): null => null ou let x: null = null ou function y(): null { return null } .

Eu preferiria uma sintaxe explícita para tipos não anuláveis. Para objetos, isso quebraria muitas coisas de uma vez.

Dizer que string realmente significa string! | null pode ser outra opção. Deve-se observar que, se você ler todas as discussões acima, isso foi inicialmente proposto porque havia esperança de que seria mais compatível com o código existente (sem alterar o significado atual de string ). Mas, na verdade, a conclusão era que, mesmo assim, grandes quantidades de código seriam quebradas de qualquer maneira.

Dado que adotar um sistema de tipo não anulável não será uma opção trivial para bases de código existentes, eu escolheria a opção string? vez de string! porque parece mais limpa. Especialmente se você olhar o código-fonte do compilador TS, onde praticamente todos os tipos internos seriam nomeados incorretamente :(

Pode ser um pouco mais viável / não separável tornar os números que não são argumentos opcionais não anuláveis ​​por padrão por meio de um sinalizador do compilador. Isso também exigiria uma sintaxe para marcar explicitamente os tipos anuláveis, mas já estou vendo isso como um resultado provável desta discussão.

Acho que as coisas estão ficando confusas neste ponto. Para mim, este parece ser um bom argumento para usar string? qualquer lugar. Eu não gostaria de ver misturando number? e string! , isso ficaria muito confuso muito rapidamente.

[1] Apresentar um sinalizador para torná-lo o padrão não funcionaria bem em primeiro lugar por motivos práticos.

Sim, não funcionaria bem para uma base de código existente sem alterações. Mas:

  1. Funcionará muito bem para novas bases de código. Podemos construir um futuro melhor com isso.
  2. As bases de código existentes colherão _alguns_ benefícios e verificações adicionais, mesmo que nunca aceitem.
  3. As bases de código existentes podem ser aceitas em uma base por arquivo, o que permite que o novo código seja mais estrito e pode permitir a transição progressiva de código mais antigo, se desejado.

@ jods4

Acho que as coisas estão ficando confusas neste ponto. Para mim, isso parece um bom argumento para usar string? em toda parte. Eu não gostaria de ver misturando números? e string !, isso ficaria muito confuso muito rapidamente.

Eu não quis dizer isso nesse sentido. Eu quis dizer que há menos caso de uso para números anuláveis ​​do que para strings anuláveis. Eu não sou tão apegado a essa sugestão, de qualquer maneira (eu não me importaria de qualquer maneira).

As bases de código existentes podem ser aceitas em uma base por arquivo, o que permite que o novo código seja mais estrito e pode permitir a transição progressiva de código mais antigo, se desejado.

Com que meios? Não acredito que atualmente você possa configurar o compilador arquivo por arquivo. Estou aberto a ser provado que estou errado aqui.

@impinball

Com que meios? Não acredito que atualmente você possa configurar o compilador arquivo por arquivo. Estou aberto a ser provado que estou errado aqui.

Talvez eu esteja errado. Nunca os usei, mas quando vejo casos de teste, vejo muitos comentários parecidos com // <strong i="9">@module</strong> amd . Sempre achei que era uma maneira de especificar opções de dentro de um arquivo ts. Eu nunca tentei fazer isso, então talvez eu esteja completamente errado! Veja por exemplo aqui:
https://github.com/Microsoft/TypeScript/blob/master/tests/cases/conformance/externalModules/amdImportAsPrimaryExpression.ts

Se esta não for uma maneira de especificar opções por arquivo, podemos precisar de algo novo. É necessário especificar esta opção por arquivo, _pelo menos_ para .d.ts arquivos. Um comentário formatado especial na parte superior do arquivo pode servir, afinal, já temos suporte para /// <amd-dependency /> e cia.

EDIT : estou errado. Explorar o código-fonte me mostrou que essas coisas são chamadas de marcadores e pré-analisadas pelo executor FourSlash. Portanto, é apenas para testes, não está dentro do compilador. Algo novo precisaria ser descoberto para isso.

Existem também limitações práticas em alguns casos - não é o mais prático para alguns argumentos como os recursos do ES6, pois exigiria uma nova análise completa do arquivo. E a verificação de tipo do IIUC é feita na mesma passagem que a criação do AST em primeiro lugar.

Se você olhar o código do analisador, os comentários /// <amd-dependency /> e /// <amd-module /> são lidos antes de qualquer outra coisa ser analisada em um arquivo. Adicionar algo como /// <non-null-types /> seria viável. OK, isso é péssimo nome e não sou a favor da proliferação de opções "mágicas" arbitrárias, mas isso é apenas uma ideia.

@ jods4 Acho @I 'm pinball está dizendo que IDEs não vai apoiá-lo sem grandes alterações no modo como o destaque de sintaxe obras.

Haverá alguma forma de tipo não anulável no 2.0?
Afinal, o texto digitado traz muito menos coisas para o javascript se não puder garantir o tipo. O que podemos obter depois de superar todos os problemas e reescrever introduzidos pelo texto digitado?
Apenas um IntelliSense melhor? Já que você ainda precisa verificar o tipo manualmente ou por código em cada exportação de função para outros módulos.

Boas notícias a todos, o TypeScript 1.6 tem poder expressivo suficiente para modelar e rastrear com segurança um valor ausente (que de outra forma são codificados pelos valores null e undefined ). Essencialmente, o seguinte padrão fornece tipos não anuláveis:

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

Com isso dito, o pedido deve ser encerrado porque não existe mais nenhum problema. Caso encerrado, turma encerrada.

@ aleksey-bykov

Com isso dito, o pedido deve ser encerrado porque não existe mais nenhum problema.

Você não pode estar falando sério, pode? Dissemos várias vezes que um padrão para tipo algébrico anulável não era o motivo desse problema.

_Não_ vou envolver cada var x: number do meu código em um Nullable<number> ou ainda melhor NonNullable<x> . Isso é muita sobrecarga. Ainda assim, eu quero saber se fazer x *= 2 é 100% seguro, em qualquer lugar que isso aconteça no meu código.

Seu código não resolve o problema de usar uma biblioteca de terceiros. _Não_ vou chamar sanitize em _todo_ biblioteca de terceiros, ou mesmo API DOM integrada, que eu chamo. Além disso, não quero adicionar isNothing(safe) _todo o lugar_. O que eu quero é poder chamar let squares = [1,2,3].map(x => x*x) e ter 100% de certeza no momento da compilação de que squares.length está seguro. Com _qualquer_ API que eu uso.

Da mesma forma, quero documentação para bibliotecas JS de terceiros que uma função aceita null como entrada ou não. Eu quero saber em tempo de compilação se $(element).css(null) está OK ou é um erro.

Quero trabalhar em ambientes de equipe, onde não posso garantir que todos usem padrões complexos como os seus de forma consistente. Seu tipo Nulllable não faz absolutamente nada para evitar que um desenvolvedor faça let x: number = null; x.toString() (ou algo menos estúpido, mas com o mesmo efeito).

E assim por diante. Este tíquete está _far_ de fechado e o problema ainda está 100% lá.

Você não pode estar falando sério, pode?

Eu estou falando sério

Eu não vou embrulhar cada um ...

Por que não? Com ! sintaxe ou qualquer outra coisa que vocês estão empurrando, você teria que fazer de qualquer maneira.

ou ainda melhor não anulável

Deve ser anulável o que estamos modelando são tipos anuláveis que podem ter um valor não-nulo ou nulo e é explicitamente declarado. Ao contrário dos tipos convencionais, que podem SEMPRE ter um valor nulo e ser chamados de number o que implica que você deve verificar se há nulo antes de usar.

Isso é muita sobrecarga.

Onde?

Seu código não resolve o problema de usar uma biblioteca de terceiros.

É verdade.

Não vou chamar o sanitize em todas as bibliotecas de terceiros, ou mesmo na API DOM integrada, que eu chamo.

Você não precisa. Tudo que você precisa fazer é corrigir o arquivo de definição dessa biblioteca de terceiros, substituindo tudo que pode ser nulo por Nullable<*>

Da mesma forma, quero documentação para bibliotecas JS de terceiros em que uma função aceita nulo como entrada ou não.

Mesma coisa. Defina esse método como aceitando Nullable<string> vez de string simples.

Seu tipo Nulllable não faz absolutamente nada para evitar que um desenvolvedor faça let x: number = null; x.toString ()

De fato, não. Você precisa banir a palavra-chave null usando o linter.

Venha, minha solução está 95% funcionando e 100% prática e está disponível HOJE sem ter que tomar decisões difíceis e colocar todos na mesma página. É a questão do que você está procurando: uma solução que funcione que não se parece com o que você esperava, mas que funcione, ou conseguir exatamente do jeito que você quer com todas as cerejas e jack backs em cima

Em 1.4 e 1.5, o tipo void não permite nenhum membro de Object, incluindo como .toString (), então type Nothing = void; deve ser suficiente em vez de precisar do módulo (a menos que isso seja alterado novamente em 1.6). http://bit.ly/1OC5h8d

@ aleksey-bykov isso não é verdade. Você ainda precisa ter cuidado para limpar todos os valores que são possivelmente nulos. Você não receberá um aviso se esquecer de fazer isso. É uma melhoria, com certeza (menos lugares para ter cuidado).

Além disso, para ilustrar como os hacks realmente não levarão você muito longe, tente

if (isNothing(something)) {
  console.log(something.toString())
}

Eu não vou embrulhar cada um ...

Por que não? Com ! sintaxe ou qualquer outra coisa que vocês estão empurrando, você teria que fazer de qualquer maneira.

OK, eu _posso_ fazer isso se todo o resto for resolvido. E se tudo for resolvido, talvez o TS possa até considerar um pouco de açúcar de linguagem para isso.

ou ainda melhor não anulável

Deve ser anulável o que estamos modelando são tipos anuláveis ​​que podem ter um valor não anulável ou nulo e é declarado explicitamente. Ao contrário dos tipos convencionais que podem SEMPRE ter um valor ou nulo e ser chamados de número, o que significa que você deve verificar se há nulo antes de usar.

Não é que eu apenas queira me livrar das exceções nulas. Eu quero ser capaz de expressar tipos não anuláveis ​​também, com segurança estática.
Digamos que você tenha um método que sempre retorna um valor. Como toString() sempre retorna uma string não nula.
Quais são suas opções lá?
let x: string = a.toString();
Isso não é bom porque não há validação estática de que x.length é seguro. Neste caso, é, mas como você disse, o string embutido pode muito bem ser null então esse é o _status quo_.
let x = sanitize(a.toString());
OK, agora não posso usá-lo sem uma verificação de nulo, portanto, o código _é_ seguro. Mas eu não quero adicionar if (isNothing(x)) todos os lugares que eu uso x em meu código! É feio e ineficiente, pois eu bem poderia saber que x não é nulo em tempo de compilação. Fazer (<string>x).length é mais eficiente, mas ainda é muito feio ter que fazer isso em qualquer lugar que você queira usar x .

O que eu quero fazer é:

let x = a.toString(); // documented, non-null type string (string! if you want to)
x.length; // statically OK

Você não pode conseguir isso sem o suporte de idioma adequado, porque todos os tipos em JS (e TS 1.6) são sempre anuláveis.

Eu gostaria de dizer que programar com tipos não opcionais (anuláveis) é uma prática muito boa, tanto quanto possível. Portanto, o que estou descrevendo aqui é um cenário essencial, não uma exceção.

Isso é muita sobrecarga.

Onde?

Veja minha resposta anterior.

Seu código não resolve o problema de usar uma biblioteca de terceiros.

É verdade.

Apenas metade do problema está resolvido. Assumindo que as definições da biblioteca foram atualizadas conforme você propôs: cada entrada anulável ou valor retornado é substituído por Nullable<X> vez de X.

Ainda posso passar null para uma função que não aceita null parâmetros. OK, este fato agora está _documentado_ (o que já podemos fazer com comentários JSDoc ou qualquer outro), mas eu quero isso _forçado_ em tempo de compilação.
Exemplo: declare find<T>(list: T[], predicate: (T) => bool) . Ambos os parâmetros não devem ser nulos (não usei Nullable ), mas posso fazer find(null, null) . Outro erro possível é que a declaração diz que devo retornar um bool não nulo do predicado, mas posso fazer: find([], () => null) .

Seu tipo Nulllable não faz absolutamente nada para evitar que um desenvolvedor faça let x: number = null; x.toString ()

De fato, não. Você precisa banir a palavra-chave null usando o linter.

Fazer isso e fornecer uma variável global nothing não o ajudará com os casos que listei no ponto anterior, não é?

Do meu ponto de vista, isso não é 95%. Acho que a sintaxe piorou muito para muitas coisas comuns e ainda não tenho toda a segurança estática que quero quando falo sobre tipos não anuláveis.

Você ainda precisa ter cuidado para limpar todos os valores que são possivelmente nulos.

Exatamente o mesmo tipo de trabalho de separação que você teria que fazer de qualquer maneira com tipos hipotéticos não anuláveis ​​de que vocês estão falando aqui. Você teria que revisar todos os seus arquivos de definições e colocar ! onde apropriado. Como isso é diferente?

Você não receberá um aviso se esquecer de fazer isso.

Mesma coisa. Mesma coisa.

ilustrar como os hacks realmente não levarão você muito longe, apenas tente

Embora você esteja certo da maneira que eu defini Nada que tenha toString de Object , mas .. se pegarmos a ideia de @Arnavion (de usar void para Nothing ) tudo clica de repente

Ver

image

Do meu ponto de vista, isso não é 95%. Acho que a sintaxe piorou muito para muitas coisas comuns e ainda não tenho toda a segurança estática que quero quando falo sobre tipos não anuláveis.

cara, você acabou de me convencer, esse padrão não é para você e nunca será, por favor, nunca use em seu código, continue pedindo pelos tipos reais não anuláveis, boa sorte, desculpe por incomodá-lo com tal absurdo

@ aleksey-bykov
Eu vejo o que você está tentando fazer, embora me pareça que você ainda não resolveu todos os casos e parece estar inventando soluções ao longo do caminho quando alguém aponta um buraco (como a substituição de null por void no último exemplo).

No final _talvez_ você fará com que funcione do jeito que eu quero. Você usará uma sintaxe complexa para tipos anuláveis, você terá banido com um linter qualquer fonte de null no programa que pode eventualmente tornar os tipos anuláveis ​​não nulos. Ao longo do caminho, você terá que convencer todos a atualizarem suas bibliotecas usando sua convenção não padrão, caso contrário, não adianta (veja bem, isso é verdade para _todas_ as soluções para o problema nulo, não apenas para o seu).

No final, você pode conseguir distorcer o sistema de tipos para evitar o tipo null embutido e ter os cheques que queremos anexados ao seu tipo Nothing .

Nesse ponto, você não acha que a linguagem deveria apenas apoiá-lo? A linguagem pode implementar tudo da mesma maneira que você deseja (internamente). Mas, além disso, você terá uma sintaxe agradável, casos extremos resolvidos, sem necessidade de linters externos e, certamente, uma melhor adoção por definições de terceiros. Isso não faz mais sentido?

Nesse ponto, você não acha que a linguagem deveria apenas apoiá-lo?

Vamos conversar sobre o que eu acho. Acho que seria bom acordar em um mundo onde o TypeScript tem tipos não anuláveis ​​amanhã. Na verdade, esse é o pensamento de que vou para a cama todas as noites. Mas isso nunca acontece no dia seguinte e fico frustrado. Então, vou trabalhar e tenho o mesmo problema com valores nulos repetidamente, até recentemente, quando decidi procurar um padrão que pudesse tornar minha vida mais fácil. Parece que encontrei. Eu ainda gostaria de ter valores não nulos no TypeScript? Claro que faço. Posso viver sem eles e frustração? Parece que posso.

Eu ainda gostaria de ter valores não nulos no TypeScript? Claro que faço.

Então, por que você deseja encerrar este problema? :risonho:

Com isso dito, o pedido deve ser encerrado porque não existe mais nenhum problema. Caso encerrado, turma encerrada.

Fico feliz que você tenha encontrado uma solução para suas necessidades, mas como você, ainda espero que um dia o TS receba o suporte adequado. E eu acredito que isso ainda pode acontecer (não em breve, veja bem). A equipe C # está atualmente fazendo os mesmos brainstorms e talvez isso possa ajudar a avançar. Se o C # 7 conseguir obter tipos não nulos (o que ainda não é certo), não há razão para que o TS não faça o mesmo.

@ aleksey-bykov

Então, 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;

e agora você também pode usá-lo sem quaisquer wrappers de função - definições de tipo adequadas são suficientes.

Essa é basicamente ser desencorajado de depender dela .

Você teria que revisar todos os seus arquivos de definições e colocar! onde apropriado. Como isso é diferente?

A principal diferença ao usar recursos de linguagem oficial padrão em vez de hacks é que a comunidade (DefinitelyTyped) está lá para ajudar a escrever, testar e compartilhar os arquivos de definição, em vez de todo aquele trabalho extra sendo empilhado em cima de seus projetos (bem, na verdade, :sorriso:).

Estou defendendo " --noImplicitNull ". Isso tornaria todos os tipos padronizados como não anuláveis, fornecendo imediatamente feedback sobre problemas em potencial em qualquer código existente. Se houver "!", Ele deve se comportar como o Swift para fazer o verificador de tipos ignorar a possibilidade de nulos (conversão insegura para não anulável) - embora isso esteja em conflito com o operador ES7 + "envio eventual" nas promessas, então pode não ser o melhor idéia.

Se isso parece muito radical em termos de quebra de compatibilidade com versões anteriores, é realmente minha culpa. Eu realmente deveria tentar encontrar o tempo e tentar em uma bifurcação para provar que não é realmente tão radical quanto parece. De fato, adicionar "!" é mais radical: seriam necessárias mais alterações para tirar proveito disso, pois a maioria dos valores não são realmente nulos; --noImplicitNull é mais gradual, pois você descobrirá mais erros corrigindo as definições de tipo que incorretamente reivindicam valores não nulos e sem essas correções, você obterá o mesmo comportamento, exceto para atribuição nula, valores não inicializados e verificações esquecidas para campos de objetos opcionais .

Adicionando minha voz à discussão, gosto de como Kotlin resolveu o problema, ou seja,

  • próprias variáveis ​​têm tipos não anuláveis ​​por padrão, um '?' muda isso
  • variáveis ​​externas são anuláveis, um '!!' substitui
  • uma verificação nula é um tipo de elenco
  • código externo pode ser analisado por uma ferramenta que gera anotações

Comparar:
1
2

  • se a|void for banido, pode-se mudar para a|Nothing

A principal diferença ao usar recursos de linguagem oficial padrão em vez de hacks é que a comunidade (DefinitelyTyped) está lá para ajudar a escrever, testar e compartilhar os arquivos de definição

  • não consigo ver por que usar recursos 100% legítimos é um hack
  • não superestime as habilidades da comunidade, muitas (se não a maioria) definições lá são de baixa qualidade e mesmo aquelas como, digamos, jquery muitas vezes não podem ser usadas como escritas sem algumas alterações
  • novamente, um padrão nunca será tão bom quanto um recurso completo, espero que todos entendam, sem dúvida
  • um padrão pode resolver seus problemas hoje, enquanto um recurso pode ou não estar próximo
  • se uma situação pode ser resolvida de forma eficiente (até 95%) com o emprego de AB e C, por que alguém precisaria de D para fazer o mesmo? desejando um pouco de açúcar? por que não nos concentramos em algo que não pode ser resolvido por um padrão?

de qualquer forma, existem solucionadores de problemas e perfeccionistas, pois foi mostrado que o problema tem solução

(Se você não quiser depender do tipo de vazio, nada também pode ser class Nothing { private toString: any; /* other Object.prototype members */ } que terá o mesmo efeito. Consulte # 1108)

_Observação: nada aqui é sobre a introdução de bicicletas na sintaxe. Por favor não pegue
Como tal._

Vou ser simples: aqui está o problema com a solução TS 1.6 proposta
aqui: pode funcionar de forma viável, mas não se aplica à biblioteca padrão ou
a maioria dos arquivos de definição. Também não se aplica a operadores matemáticos. Se você quiser
para reclamar sobre o tipo redundante / boilerplate de tempo de execução, tente fazer
coleções funcionam em C ++ - isso empalidece em comparação, especialmente se você quiser
iteração lenta ou coleções que não são matrizes (ou pior ainda, tente
combinando os dois).

O problema com a solução atualmente proposta por @aleksey-bykov é que
não é aplicado no núcleo da linguagem. Por exemplo, você não pode fazer
Object.defineProperty(null, "name", desc) - você obterá um TypeError de
o objeto de destino null . Outra coisa: você não pode atribuir nenhuma propriedade a um
null object, como em var o = null; o.foo = 'foo'; . Você obterá um
ReferenceError IIRC. Esta solução proposta não pode levar em conta esses casos.
É por isso que precisamos de suporte de idioma para isso.


_Agora, um pouco de bicicleta leve ..._

E quanto à sintaxe, gosto da concisão da "? String" e
sintaxe "! string", mas quanto ao resultado final, não me importo, contanto que esteja
não escrevendo ThisIsANullableType<T, U, SomeRidiculousAndUnnecessaryExtraGeneric<V, W>> .

Na quarta-feira, 29 de julho de 2015, 16:10, Aleksey Bykov [email protected] escreveu:

  • se um | vazio for banido, pode-se mudar para um | Nada

    A principal diferença ao usar os recursos padrão do idioma oficial
    em vez de hacks é que a comunidade (DefinitelyTyped) está lá para ajudar
    escrever, testar e compartilhar os arquivos de definição

    não consigo ver por que usar recursos 100% legítimos é um hack

    não superestime as habilidades da comunidade, muitas (se não a maioria)
    definições que existem são de baixa qualidade e até mesmo aquelas como, digamos, jquery bastante

    muitas vezes não pode ser usado como escrito sem algumas alterações

    novamente, um padrão nunca será tão bom quanto um recurso completo, espero

    todo mundo entende, sem dúvida

    um padrão pode resolver seus problemas hoje, enquanto um recurso pode ou pode

    não esteja perto

    se uma situação pode ser resolvida de forma eficiente (até 95%) com o emprego de AB
    e C, por que alguém precisaria de D para fazer o mesmo? desejando um pouco de açúcar? porque iria
    nos concentramos em algo que não pode ser resolvido por um padrão?

de qualquer forma, existem solucionadores de problemas e perfeccionistas, como foi mostrado
problema é resolvível

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -126082053
.

E poderíamos pelo menos ter uma ideia de como seria a estrutura do galpão de bicicletas antes de ficarmos obcecados com a cor para pintá-la? A maior parte do que vi até agora após os primeiros 10 comentários foi "Ei, queremos construir um galpão de bicicletas! De que cor devemos pintá-lo?" Nada sobre como garantir que o design seja estruturalmente sólido. (Veja # 3192 e a primeira metade de # 1206 para alguns outros exemplos disso - a maior parte do ruído diminuiu quando eu finalmente fiz uma proposta séria com uma sintaxe criada logicamente e semântica totalmente especificada.)

Lembre-se: isso provavelmente resultará em uma grande refatoração e reescrita parcial das definições de tipo de biblioteca padrão. Isso também resultará em uma maioria das definições de tipo no DefinitelyTyped precisando ser reescritas para a primeira versão do TS que suporta isso. Portanto, lembre-se de que isso com certeza vai quebrar. (Uma solução poderia ser sempre emitir por padrão, mesmo quando ocorrerem erros relacionados a nulos, mas fornecer um sinalizador a la --noImplicitAny para alterar esse comportamento.)

Reconheço que estou um pouco sobrecarregado aqui. Mesmo assim, pesquisei em scholar.google por "javascript anulável" e "javascript nulidade":
Compreendendo o TypeScript
tem uma boa tabela de tipos em TypeScript (módulos genéricos).

Tipos dependentes para JavaScript
parece ser importante. código fonte desapareceu

Confie, mas verifique: Digitação de duas fases para dinâmicalínguas
Tipos de refinamento para linguagens de script
oferece uma solução baseada em texto datilografado.
("A abreviação t? Significa t + null."; Parece # 186)

@afrische Muito disso já está sendo usado praticamente em verificadores de tipo JS. O fluxo usa a maioria das expressões idiomáticas em "Confiar, mas verificar", por exemplo. Há também o Infernu , um verificador de tipo WIP JS que se baseia amplamente na inferência de tipo 1 de Hindley-Milner para inferir seus tipos. Mas eu estou divagando ...

[1] Haskell, OCaml, etc. usam uma versão modificada também para seus sistemas de tipos.

No momento, estou brincando com um fork do TS que assume que os tipos não são anuláveis ​​por padrão e que torna o tipo Null (existente) referenciável com null , por exemplo, string | null . Também adicionei a sintaxe para string? que é apenas um atalho para o mesmo tipo de união.

Quando você pensa profundamente sobre isso, é a mesma coisa que @ aleksey-bykov, mas onde null é o tipo Nothing , embutido.

E isso nem é complicado de fazer porque o sistema de tipos já é muito bom em lidar com tudo isso!

Onde as coisas ficam complicadas é que queremos um caminho de transição suave. Precisamos de alguma compatibilidade com versões anteriores, _pelo menos_ com os arquivos de definição existentes (a compatibilidade com o projeto em si poderia ser um sinalizador opcional, embora fosse melhor se o significado de um trecho de código - digamos na internet - não existisse ' t dependem de sinalizadores de compilador global).

A ideia de @afrische de usar string? em seu próprio projeto, mas usar string! em .d.ts pode ser uma nova abordagem. Embora eu não goste da dualidade arbitrária que isso cria. Por que string anulável e alguns arquivos e não anulável em outros? Parece estranho.

Se preferir não ter string anulável em alguns arquivos e não em outros.
Eu gosto da ideia do @impinball de ter um sinalizador de exclusão no compilador e introduzi-lo como uma alteração significativa (suspiro: grande alteração em relação aos meus argumentos anteriores). Presumindo que a nova sintaxe não causará erros ao usar o sinalizador de exclusão, é claro.

Este tópico tem mais de um ano e é difícil descobrir quais são os designs e preocupações relevantes com quase 300 comentários e apenas alguns da própria equipe de TS.

Estou planejando migrar uma grande base de código para Flow, Closure Compiler ou TypeScript, mas a falta de segurança nula no TypeScript é um verdadeiro obstáculo.

Sem ter lido todo o tópico, não consigo descobrir o que há de errado em adicionar ! e ? especificadores de nulidade e, ao mesmo tempo, manter o comportamento existente para tipos sem eles, então aqui está isso como uma proposta :

Proposta

declare var foo:string // nullability unspecified
declare var foo:?string // nullable
declare var foo:!string // non-nullable

Com ?string um superconjunto de !string e string um superconjunto e um subconjunto de ambos, ou seja, a relação entre string e ambos ?string e !string é o mesmo que a relação entre any e todos os outros tipos, ou seja, um tipo simples sem ! ou ? é como any em relação à nulidade.

As seguintes regras se aplicam:

| Tipo | Contém | Fornece | Pode atribuir null ? | Não pode assumir null ? |
| --- | --- | --- | --- | --- |
| T | T , !T , ?T | T , ?T , !T | sim | sim |
| ?T | T , !T , ?T | T , ?T | sim | não (tipo de proteção necessária) |
| !T | T , !T | T , ?T , !T | não | sim |

Isso fornece segurança nula sem quebrar o código existente e permite que as bases de código existentes introduzam segurança nula incrementalmente, assim como any permite que as bases de código existentes introduzam a segurança de tipo incrementalmente.

Exemplo

Aqui está um código com um erro de referência nula:

function test(foo:Foo) { foo.method(); }
test(null);

Este código ainda é aprovado. null ainda pode ser atribuído a Foo e Foo ainda pode ser considerado não nulo.

function test(foo:!Foo) { foo.method(); }
test(null);

Agora o test(null) está errado, pois null não pode ser atribuído a !Foo .

function test(foo:?Foo) { foo.method(); }
test(null);

Agora o foo.bar() está errado, uma vez que uma chamada de método em ?Foo não é permitida (ou seja, o código deve verificar null ).

Obrigada! E eu realmente gosto dessa ideia. Embora, para fins de compatibilidade,
poderíamos fazer ?T e T apenas apelidos, como Array<T> e T[] ? Isto
simplificaria as coisas, e a versão não decorada ainda é tecnicamente
anulável.

Na sexta-feira, 28 de agosto de 2015, 07:14 Jesse Schalken [email protected] escreveu:

Este tópico tem mais de um ano e é difícil descobrir o que todos
projetos e preocupações relevantes estão com quase 300 comentários e apenas alguns
da própria equipe TS.

Estou planejando migrar uma grande base de código para Flow, Closure Compiler ou
TypeScript, mas a falta de segurança de tipo no TypeScript é um verdadeiro obstáculo.

Sem ter lido todo o tópico, não consigo descobrir o que há de errado
com a adição de ambos! e ? especificadores de nulidade, mantendo o
comportamento existente para tipos sem eles, então aqui está uma proposta:
Proposta

declare var foo: string // nullability unspecifieddeclare var foo:? string // nullable, declare var foo:! string // não nullable

Com? String um superconjunto de! String, e string um superconjunto e subconjunto
de ambos, ou seja, a relação entre string e? string e! string
é o mesmo que a relação entre qualquer e todos os outros tipos, ou seja, um
tipo nu sem! ou ? é como qualquer no que diz respeito à nulidade.

As seguintes regras se aplicam:
Tipo Contém Fornece Pode atribuir nulo? Pode assumir que não é nulo? TT,! T,? TT,
? T,! T sim sim? TT,! T,? TT,? T sim não (tipo de proteção obrigatório)! TT,! TT,
? T,! T não sim

Isso fornece segurança nula sem quebrar o código existente e permite
bases de código existentes para introduzir segurança nula incrementalmente, assim como qualquer
permite que as bases de código existentes introduzam a segurança de tipo de forma incremental.
Exemplo

Aqui está um código com um erro de referência nula:

teste de função (foo: Foo) {foo.method (); } teste (nulo);

Este código ainda é aprovado. null ainda pode ser atribuído a Foo e Foo ainda pode
ser considerado não nulo.

teste de função (foo:! Foo) {foo.method (); } teste (nulo);

Agora, o teste (nulo) está errado, pois nulo não pode ser atribuído a! Foo.

teste de função (foo:? Foo) {foo.method (); } teste (nulo);

Agora o foo.bar () está errado, uma vez que uma chamada de método em? Foo não é permitida
(ou seja, o código deve verificar se há nulos).

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -135743011
.

@impinball

Obrigada! E eu realmente gosto dessa ideia. Embora, para fins de compatibilidade, poderíamos fazer ?T e T apenas apelidos, como Array<T> e T[] ? Isso simplificaria as coisas, e a versão não decorada ainda é tecnicamente anulável.

A _Ideia inteira_ é que T , ?T e !T são três coisas distintas, e que _is_ para fins de compatibilidade (compatibilidade com o código TS existente). Não sei explicar melhor, desculpe. Quer dizer, fiz uma mesinha e tudo.

OK. Bom ponto. Eu interpretei mal a tabela nessa parte, e eu esqueci
o caso de troca de arquivos de definição existentes, causando problemas com
outros aplicativos e bibliotecas.

Na sexta-feira, 28 de agosto de 2015, 11h43, Jesse Schalken [email protected] escreveu:

@impinball https://github.com/impinball

Obrigada! E eu realmente gosto dessa ideia. Embora, para fins de compatibilidade,
poderíamos fazer? T e T apenas aliases, como Arraye T[]? Seria
simplificar as coisas, e a versão não decorada ainda é tecnicamente anulável.

A _ idéia inteira_ é que T,? T e! T são três coisas distintas,
e isso _é_ para fins de compatibilidade (compatibilidade com TS existente
código). Não sei explicar melhor, desculpe. Quer dizer, eu fiz um
mesinha e tudo.

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -135810311
.

desculpe por ser um espertinho, mas isso não faz sentido

?string um superconjunto de !string e string um superconjunto e um subconjunto de ambos

da teoria dos conjuntos sabemos que um conjunto A é um subconjunto e um superconjunto de um conjunto B é quando e somente quando A = B

em caso afirmativo, quando você diz "de ambos", só pode significar tudo-da-? string = tudo-da-string e tudo-da-! string = tudo-da-string

então, em última análise, tudo-de-? string = tudo-de-! string

de onde todos saem, o ônibus não vai a lugar nenhum, o ônibus está fora de serviço

@aleksey-bykov Provavelmente um caso de fraseado pobre. Acho que ele quis dizer que string é mais parecido com ?string | !string , considerando as restrições mais permissivas.

Tudo isso é semelhante em escopo às anotações de tipo em tempo de compilação cada vez mais comuns @Nullable e @NonNull / @NotNull Java e como funcionam com tipos não anotados.

E eu definitivamente seria a favor de um novo sinalizador para tipos considerados implicitamente como não anuláveis, particularmente primitivos.

Um sinalizador "não anulável por padrão" seria bom, mas também quebraria um grande número de definições DefinitelyTyped. Isso inclui as definições de Angular e Node, e exigiria muito trabalho tedioso para corrigir. Também pode exigir um backport para que os novos tipos ainda não sejam analisados ​​como erros de sintaxe, mas a nulidade não seja verificada. Tal backport seria a única maneira prática de mitigar a quebra com tal sinalizador, especialmente porque as definições são atualizadas para tipos anuláveis. (As pessoas ainda usam o TypeScript 1.4 no desenvolvimento, especialmente em projetos maiores.)

@impinball

As pessoas ainda usam o TypeScript 1.4 no desenvolvimento, especialmente em projetos maiores.

Eu acho que a história de compatibilidade para esse recurso já é difícil o suficiente sem tentar fazer novas definições compatíveis com compiladores antigos (tipos não nulos FWIW são quase triviais para adicionar ao compilador TS atual).

Se as pessoas permanecerem no antigo TS, devem usar definições antigas. (Eu sei que não é prático).
Quero dizer, as coisas vão quebrar de qualquer maneira. Em breve o TS 1.6 adicionará tipos de interseção e definições que o usam também não serão compatíveis.

Aliás, estou surpreso que as pessoas continuem no 1.4, há muito o que amar no 1.5.

@ aleksey-bykov

desculpe por ser um espertinho, mas isso não faz sentido

Presumo que você esteja se chamando de "espertinho" porque sabe perfeitamente bem, pelo resto do post, o que "superconjunto e subconjunto de ambos" pretende significar. Claro que não faz sentido se aplicado a conjuntos reais.

any pode ser atribuído a qualquer tipo (se comporta como um superconjunto de todos os tipos) e tratado como qualquer tipo (se comporta como um subconjunto de todos os tipos). any significa "cancelar a verificação de tipo para este valor".

string pode ser atribuído de !string ou ?string (se comporta como um superconjunto deles) e pode ser tratado como !string e ?string (ele se comporta como um subconjunto deles). string significa "cancelar a verificação de nulidade para este valor", ou seja, o comportamento atual do TS.

@impinball

E eu definitivamente seria a favor de um novo sinalizador para tipos considerados implicitamente como não anuláveis, particularmente primitivos.

@RyanCavanaugh disse explicitamente:

Sinalizadores que mudam a semântica de um idioma são perigosos. [...] É importante que alguém olhando para um trecho de código possa "acompanhar" o sistema de tipos e entender as inferências que estão sendo feitas. Se começarmos a ter um monte de sinalizadores que mudam as regras da linguagem, isso se torna impossível.

O único tipo de coisa segura a fazer é manter a semântica de atribuibilidade a mesma e alterar o que é um erro versus o que não depende de um sinalizador, da mesma forma que noImplicitAny funciona hoje.

É por isso que minha proposta mantém o comportamento existente para tipos sem um especificador de nulidade ( ! ou ? ). O sinalizador _only_ seguro a ser adicionado seria aquele que não permite tipos sem um especificador de nulidade, ou seja, ele apenas passa o código e causa um erro, ele não muda seu significado. Presumo que noImplicitAny foi permitido pelo mesmo motivo.

@jesseschalken

Eu quis dizer que, no contexto de variáveis ​​digitadas implicitamente não inicializadas
para null ou undefined em casos como estes:

var a = new Type(); // type: !Type
var b = 2; // type: !number
var c = 'string'; // type: !string
// etc...

Desculpe pela confusão.

Na sexta-feira, 28 de agosto de 2015, 19:54, Jesse Schalken [email protected] escreveu:

@impinball https://github.com/impinball

E eu definitivamente seria a favor de uma nova bandeira para tipos que estão sendo
implicitamente considerado como não anulável, particularmente primitivos.

@RyanCavanaugh https://github.com/RyanCavanaugh disse explicitamente:

Sinalizadores que mudam a semântica de um idioma são perigosos. [...]
É importante que alguém olhando para um trecho de código possa "acompanhar"
com o sistema de tipos e entender as inferências que estão sendo feitas. Se
começamos a ter um monte de sinalizadores que mudam as regras da linguagem,
isso se torna impossível.

O único tipo de coisa segura a fazer é manter a semântica de
atribuibilidade o mesmo e alterar o que é um erro vs o que não é dependente
em uma bandeira, muito parecido com como noImplicitAny funciona hoje.

É por isso que minha proposta mantém o comportamento existente para os tipos que faltam
um especificador de nulidade (! ou?). O sinalizador _ único_ seguro a ser adicionado seria
um que não permite tipos sem um especificador de nulidade, ou seja, apenas
pega a passagem do código e causa um erro, não muda seu significado. eu
presuma que noImplicitAny foi permitido pelo mesmo motivo.

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -135916676
.

@jeffmcaffer o que você está tentando dizer ao alterar o significado original das palavras superconjunto e subconjunto ainda pode ser articulado em termos de conjuntos facilmente: o conjunto de valores de string é uma união de valores de !string e ?string significando qualquer coisa de !string ou / e ?string pertence a string

@impinball Novamente, tal sinalizador mudaria o significado do código existente, o que (pela leitura dos comentários de @RyanCavanaugh ) não é permitido. export var b = 5; agora exportará !number onde anteriormente exportava number .

Sim, em certo sentido. Pra ser um pouco mais técnico, aceita a união, mas
ele fornece a interseção. Basicamente, qualquer tipo pode contar como um
string , e string podem ser passados ​​para qualquer tipo.

Na sexta-feira, 28 de agosto de 2015, 20:20 Aleksey Bykov [email protected] escreveu:

@impinball https://github.com/impinball o que você está tentando dizer por
alterar o significado original das palavras superconjunto e subconjunto ainda pode ser
articulado em termos de conjuntos facilmente: o conjunto de valores da string é um
_união_ de valores de! string e? string significando qualquer coisa de! string
ou / e? string pertence a string

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -135920233
.

Obviamente, ele não deve ser ativado por padrão. E a maioria dos arquivos de definição
não seria afetado. Tecnicamente, qualquer mudança que não seja puramente
aditivo (mesmo adicionar um novo argumento a uma função não está em JavaScript) tem
a capacidade de quebrar aplicações. É semelhante em escopo a
noImplicitAny porque força uma digitação um pouco mais explícita. E eu
não acredito que poderia quebrar muito mais do que isso, especialmente porque o único
como isso pode afetar outros arquivos é por meio de exportações do TypeScript real
Arquivos Fonte. (A outra sinalização quebrou vários arquivos de definição e desativou
uma forma frequente de teste de pato.)

Na sexta-feira, 28 de agosto de 2015, 20:21 Jesse Schalken [email protected] escreveu:

@impinball https://github.com/impinball Novamente, tal sinalização mudaria
o significado do código existente, que (da leitura de @RyanCavanaugh
https://github.com/RyanCavanaugh's comments) não é permitido. exportação var
b = 5; irá agora exportar um! número para onde anteriormente exportava um número.

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -135920315
.

Obviamente, ele não deve ser ativado por padrão.

Ainda está mudando o significado do código. Assim que fizer isso, você não poderá ler algum código TypeScript e dizer "Eu sei o que isso significa", nem poderá escrever uma biblioteca que outra pessoa usará, porque o significado do código depende de quais sinalizadores são usados compilá-lo, e isso é inaceitável. Você literalmente dividiu o idioma em dois.

É por isso que minha proposta mantém o comportamento existente para tipos sem um especificador de nulidade ( ? ou ! ).

@jesseschalken
Se você deseja (e gerencia) preservar com 100% de fidelidade o significado do código existente, deve desistir da ideia de segurança nula no texto digitado. Nenhuma solução será utilizável na prática.

Ter que colocar ? e ! em cada tipo de anotação já é prolixo o suficiente para eu não querer fazê-lo, mas você também teria que desistir de toda inferência de tipo. let x = 3 infere number hoje. Se você não aceitar uma alteração aqui, significa que você deve digitar tudo explicitamente para colher os benefícios da segurança nula. Não é algo que eu esteja disposto a fazer também.

Quando os benefícios superam as desvantagens, algumas concessões podem ser feitas. Como foi apontado por @impinball, a equipe de TS fez exatamente isso com noImplicitAny . É um sinalizador que cria novos erros em seu código quando você o ativa. Assim, copiar e colar código da Internet ou mesmo apenas usar bibliotecas TS pode falhar se o código que você assimilar não tiver sido escrito sob a suposição de noImplicitAny (e aconteceu comigo).

Acho que a segurança nula pode ser introduzida de maneira semelhante. O significado do código é o mesmo e ele é executado com uma semântica exatamente idêntica. Mas sob um novo sinalizador (digamos noImplicitNull ou qualquer outro) você obteria verificações e avisos / erros adicionais de código que não foi escrito com a suposição noImplicitNull .

Acho que a segurança nula pode ser introduzida de maneira semelhante. O significado do código é o mesmo
e funciona com semântica exatamente idêntica. Mas sob uma nova bandeira (diga noImplicitNull ou qualquer outra coisa)
você obteria verificações adicionais e avisos / erros de código que não foi escrito com
a suposição noImplicitNull.

Gosto dessa abordagem e parece uma forma lógica de evoluir a linguagem. Espero que, com o tempo, ele se torne o padrão de fato, da mesma forma que as digitações geralmente são escritas sem Implícito em mente.

No entanto, acho que o importante no que diz respeito à adoção gradual é garantir que o código existente possa ser migrado um módulo por vez e que o novo código escrito com nulos explícitos em mente possa funcionar facilmente com o código existente que usa nulos implícitos.

Então, que tal isso:

  • Com -noImplicitNull, T torna-se um apelido para !T . Caso contrário, a nulidade de T é desconhecida.
  • O sinalizador deve ser substituível por módulo, adicionando uma anotação na parte superior, por exemplo. <strong i="18">@ts</strong>:implicit_null . Isso é semelhante à maneira como o Flow permite a verificação de tipo por módulo.
  • A conversão de uma base de código existente é feita adicionando primeiro a opção do compilador -noImplicitNull e, em seguida, anotando todos os módulos existentes com o sinalizador '
  • Ao importar um módulo, é necessário haver uma política sobre como converter os tipos se os módulos importados e importados tiverem configurações de implicidade diferentes.

Existem diferentes opções para esse último ponto se um módulo com nulos explícitos importar um com nulos implícitos.

  • Uma abordagem extrema seria tratar a nulidade de T tipos como desconhecidos e exigir que o importador converta explicitamente o tipo em ?T ou !T . Isso exigiria muitas anotações na chamada, mas seria um safar.
  • Outra abordagem seria tratar todos os tipos T importados como ?T . Isso também exigiria muitas anotações do chamador.
  • Por último, todos os tipos T importados podem ser tratados como !T . É claro que isso estaria errado em alguns casos, mas pode ser a opção mais pragmática. Semelhante à maneira como uma variável do tipo any pode ser atribuída a um valor do tipo T .

Pensamentos?

@ jods4 A noImplicitAny _não_ muda o significado do código existente, apenas requer que o código seja explícito sobre algo que de outra forma estaria implícito.

| Código | Bandeira | Significado |
| --- | --- | --- |
| interface Foo { blah; } | | interface Foo { blah:any; } |
| interface Foo { blah; } | noImplicitAny | erro, tipo explícito necessário |
| var foo = 'blah' | | var foo:string = 'blah' |
| var foo = 'blah' | noImplicitNull | var foo:!string = 'blah' |

Com noImplicitNull , antes você tinha uma variável na qual nulo podia ser escrito. Agora você tem uma variável na qual null _não_ pode ser escrita. Isso é uma besta totalmente diferente de noImplicitAny .

@RyanCavanaugh já descartou sinalizadores que alteram a semântica do código existente. Se você vai ignorar categoricamente os requisitos expressos da equipe de TS, esse tíquete vai durar mais um ano.

@jesseschalken Desculpe, mas não consigo ver a diferença.
Antes de noImplicitAny você pode ter isto em seu código:
let double = x => x*2;
Ele compila e funciona bem. Mas, uma vez que você ativa noImplicitAny , o compilador lança um erro dizendo que x é implicitamente qualquer. Você deve modificar seu código para fazê-lo funcionar com o novo sinalizador:
let double = (x: any) => x*2 ou melhor ainda let double = (x: number) => x*2 .
Observe que, embora o compilador tenha gerado um erro, ele ainda emitirá um código JS perfeitamente funcional (a menos que você desative a emissão de erros).

A situação com nulos é praticamente a mesma na minha opinião. Vamos supor para discussão que com o novo sinalizador, T não é anulável e T? ou T | null denota a união do tipo T e null .
Antes, você poderia ter:
let foo: string; foo = null; ou mesmo apenas let foo = "X"; foo = null que seria inferido para string da mesma forma.
Ele compila e funciona bem. Agora ative a nova bandeira noImplicitNull . De repente, o TS lança um erro indicando que você não pode atribuir null a algo que não foi declarado explicitamente como tal. Mas, exceto pelo erro de digitação, seu código ainda emite _o mesmo_ código JS correto.
Com a sinalização, você precisa declarar sua intenção explicitamente e modificar o código:
string? foo; foo = null;

Então, qual é a diferença, realmente? O código está sempre emitindo bem e seu comportamento em tempo de execução não mudou em nada. Em ambos os casos, você obtém erros do sistema de digitação e precisa modificar seu código para ser mais explícito em suas declarações de tipo para se livrar deles.

Além disso, em ambos os casos, é possível pegar o código escrito sob o sinalizador estrito e compilá-lo com o sinalizador desativado e ainda funciona da mesma forma e sem erros.

@robertknight Muito próximo do meu pensamento atual.

Para módulos / definições que não optaram pelos tipos não nulos estritos, T deve basicamente significar: desligar todos os tipos de erros nulos neste tipo. Tentar forçá-lo a T? ainda pode criar problemas de compatibilidade.

O problema é que hoje alguns T são realmente não anuláveis ​​e alguns são anuláveis. 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);

Se você alias string a string? no módulo legado, a chamada se torna um erro porque você passa uma variável possivelmente nula para um parâmetro não anulável.

@ jods4 Leia meu comentário novamente. Olhe para a mesa. Não sei como expressar isso com mais clareza.

Seu exemplo foi criado explicitamente para chegar à sua conclusão, colocando a definição de foo e a atribuição de foo lado a lado. Com noImplicitAny , os únicos erros resultantes são especificamente do código que precisa ser alterado (porque ele não mudou seu significado, apenas exigiu que fosse expresso de forma mais explícita). Com noImplicitNull , o código que causou o erro foi the _assignment_ para foo mas o código que precisava ser alterado para corrigi-lo (para ter o significado antigo) era a _definição_ de foo . Isso é extremamente importante, porque o sinalizador _alterou o significado da definição de foo _. A atribuição e a definição podem obviamente estar em lados diferentes dos limites de uma biblioteca, para a qual a bandeira noImplicitNull mudou o _significado_ da interface pública dessa biblioteca!

a bandeira mudou o significado da definição de foo.

Sim, isso é verdade. Ele mudou de "Eu não tenho a menor idéia se a variável pode conter nulo ou não - e eu simplesmente não me importo" para "Esta variável é livre de nulos". Há uma chance de 50/50 de que estava certo e, se não estiver, você deve precisar sua intenção na declaração. No final, o resultado é o mesmo de noImplicitAny : você deve tornar sua intenção mais explícita na declaração.

A atribuição e a definição podem obviamente estar em lados diferentes dos limites de uma biblioteca

Na verdade, normalmente a declaração na biblioteca e o uso em meu código. Agora:

  1. Se a biblioteca optou por nulos estritos, ela deve declarar seus tipos corretamente. Se a biblioteca diz que tem tipos nulos estritos e x não é anulável, então estou tentando atribuir um nulo _é realmente um erro que deve ser relatado_.
  2. Se a biblioteca não optou por nulos estritos, o compilador não deve gerar nenhum erro para seu uso.

Este sinalizador (assim como noImplicitAny ) não pode ser ativado sem ajustar seu código.

Eu entendo seu ponto, mas eu diria que nós não _alteramos_ o código de significado; em vez disso, _expressamos o que significa que não é captado_ pelo sistema de tipos hoje.

Isso não é apenas bom porque detecta erros no código atual, mas eu diria que, sem essa etapa, nunca haverá tipos não nulos utilizáveis ​​no TS.

Boas notícias para tipos não anuláveis! Parece que a equipe do TS está bem com a introdução de mudanças importantes nas atualizações do TS!
Se você vir isso:
http://blogs.msdn.com/b/typescript/archive/2015/09/02/announcing-typescript-1-6-beta-react-jsx-better-error-checking-and-more.aspx
Eles apresentam uma alteração significativa (sintaxe opcional mutuamente exclusiva) com um novo tipo de arquivo e uma alteração significativa _sem_ o novo tipo de arquivo (afeta a todos).
Esse é um precedente com o qual podemos argumentar os tipos não anuláveis ​​(por exemplo, uma extensão .sts ou Typescript estrita e .sdts correspondentes).

Agora só precisamos descobrir se queremos que o compilador tente verificar se há tipos indefinidos ou apenas tipos nulos (e qual sintaxe) e temos uma proposta sólida.

@jbondc Leitura muito interessante. Fico feliz em ver que minha intuição sobre a migração para NNBD (não anulável por padrão) sendo mais fácil do que a migração para não anulável opcional foi confirmada por estudos (uma ordem de magnitude menos mudanças para migrar, e no caso de Dart, 1- 2 anotações por 1000 linhas de código exigiam alterações de nulidade, não mais do que 10, mesmo em código com muitos nulos)

Não tenho certeza se a complexidade do documento realmente reflete a complexidade dos tipos não anuláveis. Por exemplo, na seção de genéricos, eles discutem 3 tipos de parâmetros de tipo formal e, em seguida, mostram que você realmente não precisa deles. No TS, null seria simplesmente o tipo do registro completamente vazio (sem propriedades, sem métodos), enquanto {} seria o tipo raiz não nulo e os genéricos não anuláveis ​​são simplesmente G<T extends {}> - não há necessidade de discutir vários tipos de parâmetros de tipo formal.

Além disso, parece que eles propõem muito açúcar não essencial, como var !x

O levantamento das línguas existentes que lidaram com o mesmo problema é a verdadeira joia.

Lendo o documento, percebi que Optional / Maybe tipos são mais poderosos do que tipos anuláveis, especialmente em um contexto genérico - principalmente por causa da capacidade de codificar Just(Nothing) . Por exemplo, se tivermos uma interface de mapa genérica que contém valores do tipo T e suporta get que pode ou não retornar um valor dependendo da presença de uma chave:

interface Map<T> {
  get(s:string):Maybe<T>
}

não há nada que impeça T de ser do tipo Maybe<U> ; o código funcionará perfeitamente bem e retornará Just(Nothing) se uma chave estiver presente, mas contiver um Nothing , e simplesmente retornará Nothing se a chave não estiver presente.

Em contraste, se usarmos tipos anuláveis

interface Map<T> {
  get(s:string):T?
}

então é impossível distinguir entre uma chave ausente e um valor nulo quando T é anulável.

De qualquer forma, a capacidade de diferenciar valores anuláveis ​​de não anuláveis ​​e modelar os métodos e propriedades disponíveis de acordo é um pré-requisito para qualquer tipo de segurança de tipo.

@jbondc Este é um achado muito interessante. Eles obviamente trabalharam e estudaram muito sobre isso.

Acho reconfortante que os estudos mostram que 80% das declarações são realmente não nulas ou que há apenas 20 anotações de nulidade por KLOC (p. 21). Conforme observado no documento, este é um forte argumento para não nulo por padrão, o que também foi o meu sentimento.

Outro argumento a favor do não nulo é que ele cria um sistema de tipo mais limpo: null é seu próprio tipo, T é não nulo e T? é um sinônimo de T | null . Como o TS já tem tipo de união, tudo está bom, limpo e funciona bem.

Vendo a lista de linguagens recentes que resolveram esse problema, eu realmente acho que uma nova linguagem de programação moderna deve lidar com esse problema de longa data e reduzir bugs nulos nas bases de código. Este é um problema muito comum para algo que deveria ser modelado no sistema de tipos. Eu ainda espero que TS consiga algum dia.

Achei a ideia do operador T! intrigante e possivelmente útil. Eu estava pensando em um sistema onde T é um tipo não nulo, T? é T | null . Mas me incomodou que você não pudesse realmente criar uma API genérica que garantisse um resultado não nulo mesmo em face de uma entrada nula. Não tenho bons casos de uso, mas em teoria não poderia modelar isso fielmente: function defined(x) { return x || false; } .
Usando o operador de reversão não nulo, pode-se fazer: function defined<T>(x: T): T! | boolean . Significa que se defined retornar um T é garantido que não é nulo, mesmo se a restrição T genérica fosse anulável, digamos string? . E não acho que seja difícil modelar em TS: dado T! , se T é um tipo de união que inclui o tipo null , retorna o tipo resultante da remoção de null do sindicato.

@spion

Lendo o documento, percebi que os tipos Opcional / Talvez são mais poderosos do que os tipos anuláveis

Você pode aninhar Maybe estruturas, você não pode aninhar null , de fato.

Esta é uma discussão interessante no contexto da definição de novas apis, mas ao mapear apis existentes há pouca escolha. Tornar o mapa da linguagem nulos para Maybe não tirará proveito desse benefício, a menos que a função seja totalmente reescrita.

Maybe codifica duas informações distintas: se existe um valor e qual é o valor. Pegando seu exemplo Map e olhando para C #, isso é óbvio, de Dictionary<T,K> :
bool TryGet(K key, out T value) .
Observe que se C # tivesse tuplas (talvez C # 7), isso seria basicamente o mesmo que:
(bool hasKey, T value) TryGet(K key)
Que é basicamente um Maybe e permite armazenar null .

Observe que JS tem sua própria maneira de lidar com esse problema e cria uma série de novos problemas interessantes: undefined . Um JS Map típico retornaria undefined se a chave não fosse encontrada, seu valor caso contrário, incluindo null .

Proposta relacionada para C # 7 - https://github.com/dotnet/roslyn/issues/5032

Vocês percebem que o problema não é resolvido a menos que você modele indefinido da mesma maneira?
Caso contrário, todos os seus problemas nulos serão apenas substituídos por problemas indefinidos (que são mais prevalentes de qualquer maneira).

@Griffork

todos os seus problemas nulos serão apenas substituídos por problemas indefinidos

Não, por que eles fariam?
Meus problemas nulos irão embora e meus problemas indefinidos permanecerão.

É verdade que undefined ainda é um problema. Mas isso depende muito do seu estilo de codificação. Eu codifico quase sem undefined exceto quando o navegador os força a mim, o que significa que 90% do meu código seria mais seguro com verificações nulas.

Eu codifico quase sem indefinido, exceto quando o navegador os força a mim

Eu teria pensado que o JavaScript força undefined em um a cada turno.

  • Variáveis ​​não inicializadas. let x; alert(x);
  • Argumentos de função omitidos. let foo = (a?) => alert(a); foo();
  • Acessando elementos de matriz inexistentes. let x = []; alert(x[0]);
  • Acessando propriedades de objetos inexistentes. let x = {}; alert(x['foo']);

Nulo, por outro lado, ocorre em menos e mais situações previsíveis:

  • Acesso DOM. alert(document.getElementById('nonExistent'));
  • Respostas de serviços da web de terceiros (uma vez que JSON.stringify tira undefined ). { name: "Joe", address: null }
  • Regex.

Por esse motivo, proibimos o uso de null , converter todos os null recebidos pela transferência em undefined e sempre usar a verificação de igualdade estrita para undefined . Isso funcionou bem para nós na prática.

Conseqüentemente, concordo que o problema de undefined é o mais prevalente.

@NoelAbrahams Estilo de codificação, eu te digo :)

Variáveis ​​não inicializadas

Eu sempre inicializo variáveis ​​e tenho noImplicitAny ativado, então let x seria um erro de qualquer maneira. O mais próximo que eu usaria em meu projeto é let x: any = null , embora esse seja um código que eu não escreveria com frequência.

Parâmetros de função opcionais

Eu uso valores de parâmetro padrão para parâmetros opcionais, me parece que faz mais sentido (seu código _will_ lerá e usará o parâmetro de alguma forma, não é?). Então, para mim: function f(name?: string = null, options?: any = {}) .
Acessar o valor do parâmetro undefined bruto seria um caso _excepcional_ para mim.

Acessando elementos de matriz inexistentes

Isso é algo que eu me esforço para não fazer no meu código. Eu verifico o comprimento de meus arrays para não sair dos limites e não uso arrays esparsos (ou tento preencher slots vazios com valores padrão como null , 0 , ...).
Novamente, você pode pensar em um caso especial em que eu faria isso, mas isso seria uma _exceção_, não a regra.

Acessando propriedades de objetos inexistentes.

Praticamente a mesma coisa que para matrizes. Meus objetos são digitados, se um valor não estiver disponível eu o defino como null , não undefined . Novamente, você pode encontrar casos extremos (como fazer uma análise de dicionário), mas eles são _casos extremos_ e não representam meu estilo de codificação.

Em todos os casos _excepcionais_ em que recebo undefined volta, eu imediatamente ajo no resultado de undefined e não o propago mais ou "trabalho" com ele. Exemplo típico do mundo real em um compilador TS fictício com verificações 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 esse motivo, proibimos o uso de nulo, convertemos todos os nulos recebidos pela conexão em indefinido e sempre usamos a verificação de igualdade estrita para indefinido. Isso funcionou bem para nós na prática.

A partir disso, entendo que você usa um estilo de codificação muito diferente do meu. Se você usar basicamente undefined todos os lugares, então sim, você se beneficiará de tipos nulos verificados estaticamente muito menos do que eu
seria.

Sim, a coisa não é 100% estanque por causa de undefined e não acredito que se possa criar uma linguagem razoavelmente utilizável que seja 100% correta a esse respeito. JS apresenta undefined em muitos lugares.

Mas, como espero que você possa perceber pelas minhas respostas acima, com estilo de codificação apropriado, há _muito_ para se beneficiar de verificações nulas. Pelo menos minha opinião é que em minha base de código ajudaria a encontrar e prevenir muitos bugs estúpidos e aumentaria a produtividade em meu ambiente de equipe.

@ jods4 , foi interessante ler sua abordagem.

Acho que a objeção que tenho a essa abordagem é que parece haver uma série de regras que precisam ser respeitadas - é como o comunismo versus o mercado livre: sorria:

A equipe TS internamente tem uma regra semelhante à nossa para seu próprio estilo.

@NoelAbrahams "Usar undefined " é tão regra quanto "Usar null ".

Em qualquer caso, consistência é a chave e eu não gostaria de um projeto em que não tenha certeza se as coisas deveriam ser null ou undefined (ou uma string vazia ou zero). Especialmente porque o TS atualmente não ajuda com este problema ...

Eu sei que o TS tem uma regra para favorecer undefined vez de null , estou curioso se esta é uma escolha arbitrária "por uma questão de consistência" ou se há mais argumentos por trás da escolha.

Por que gosto de usar null vez de undefined :

  1. Ele funciona de maneira familiar para nossos desenvolvedores, muitos vêm de linguagens OO estáticas, como C #.
  2. Variáveis ​​não inicializadas são geralmente consideradas como um cheiro de código em muitas linguagens, não sabemos por que deveria ser diferente em JS. Deixe sua intenção clara.
  3. Embora JS seja uma linguagem dinâmica, o desempenho é melhor com tipos estáticos que não mudam. É mais eficiente definir uma propriedade como null que delete it.
  4. Ele suporta a diferença clara entre null que é definido, mas significa a ausência de valor, e undefined , que é ... indefinido. Um lugar onde a diferença entre os dois é óbvia: parâmetros opcionais. Não passar um parâmetro resulta em undefined . Como você _passa_ o valor vazio se usar undefined para isso em sua base de código? Usando null não há problema aqui.
  5. Ele apresenta um caminho limpo para a verificação de nulos, conforme discutido neste tópico, o que não é realmente prático com undefined . Embora talvez eu esteja sonhando acordado com este.
  6. Você tem que fazer uma escolha para consistência, IMHO null é tão bom quanto undefined .

Acho que a razão para preferir undefined a null é por causa de
argumentos padrão e consistência com retorno de obj.nonexistentProp
undefined .

Fora isso, eu não entendo o fato de que o ciclismo é considerado nulo
o suficiente para exigir que a variável seja anulável.

Na terça - feira, 8 de setembro de 2015, 06:48 jods

@NoelAbrahams https://github.com/NoelAbrahams "Use undefined" é como
muito mais uma regra como "Use null".

Em qualquer caso, consistência é a chave e eu não gostaria de um projeto onde estou
não tenho certeza se as coisas devem ser nulas ou indefinidas (ou um vazio
string ou zero). Especialmente porque o TS atualmente não ajuda com isso
emitir...

Eu sei que TS tem uma regra para favorecer undefined em vez de null, estou curioso para saber se este
é uma escolha arbitrária "por uma questão de consistência" ou se houver mais
argumentos por trás da escolha.

Por que _I_ gosto de usar nulo em vez de indefinido:

  1. Funciona de maneira familiar para nossos desenvolvedores, muitos vêm de OO estático
    linguagens como C #.
  2. Variáveis ​​não inicializadas são geralmente consideradas como um cheiro de código em
    muitos idiomas, não sei por que deveria ser diferente em JS. Faça seu
    intenção clara.
  3. Embora JS seja uma linguagem dinâmica, o desempenho é melhor com
    tipos estáticos que não mudam. É mais eficiente definir uma propriedade para
    null do que excluí-lo.
  4. Ele suporta a diferença clara entre nulo, que é definido, mas
    significa a ausência de valor e indefinido, que é ... indefinido.
    Um lugar onde a diferença entre os dois é óbvia: opcional
    parâmetros. Não passar um parâmetro resulta em indefinido. Como você
    _pass_ o valor vazio se você usar undefined para isso em seu código
    base? Usando null não há problema aqui.
  5. Ele estabelece um caminho limpo para a verificação de nulos, conforme discutido neste
    thread, o que não é muito prático com undefined. Embora talvez
    Estou sonhando acordado com este.
  6. Você tem que fazer uma escolha de consistência, IMHO null é tão bom quanto
    Indefinido.

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -138514437
.

@impinball
Podemos parar de usar "bicicletas" em todas as discussões do github? Podemos dizer com segurança que usar undefined ou null é uma preferência da equipe; mas a questão de tentar incluir undefined nas verificações de nulos (ou não) e como isso funcionaria não é trivial. Então eu não entendo como isso é motociclismo em primeiro lugar?

Eu construí um fork do TS 1.5 com tipos não anuláveis ​​e foi surpreendentemente fácil. Mas eu acho que há duas questões difíceis que precisam de um consenso para ter tipos não anuláveis ​​no compilador TS oficial, ambas foram discutidas longamente acima, sem uma conclusão clara:

  1. O que fazemos com undefined ? (minha opinião: ainda está em toda parte e não verificado)
  2. Como lidamos com a compatibilidade com o código existente, em definições específicas? (minha opinião: sinalizador de aceitação, pelo menos por arquivo de definição. Ativar o sinalizador está "quebrando" porque talvez seja necessário adicionar anotações nulas.)

Minha opinião pessoal é que nulo e indefinido devem ser tratados
equivalentemente para efeitos de nulidade. Ambos são usados ​​para esse caso de uso,
representando a ausência de um valor. Uma é que o valor nunca existiu,
e a outra é que o valor existiu e não existe mais. Ambos
deve contar para nulidade. A maioria das funções JS retorna indefinido, mas muitos
O DOM e as funções de biblioteca retornam nulo. Ambos atendem ao mesmo caso de uso. Por isso,
eles devem ser tratados de forma equivalente.

A referência de bicicletas era sobre os argumentos de estilo de código sobre os quais
deve ser usado para representar a ausência de um valor. Alguns estão discutindo por
apenas nulo, alguns estão defendendo apenas indefinido e alguns estão defendendo um
mistura. Esta proposta não deve se limitar a apenas uma delas.

Na terça - feira, 8 de setembro de 2015, 13:28 jods

@impinball https://github.com/impinball
Podemos parar de usar "bicicletas" em todas as discussões do github? Podemos com segurança
diga que usar undefined ou null é uma preferência da equipe; mas o problema
se deve tentar incluir undefined nas verificações de nulos (ou não) e como
funcionaria, não é trivial. Então, eu não entendo como isso é andar de bicicleta no
primeiro lugar?

Eu construí um fork do TS 1.5 com tipos não anuláveis ​​e foi
surpreendentemente fácil. Mas acho que há duas questões difíceis que
precisa de um consenso para ter tipos não anuláveis ​​no compilador TS oficial,
ambos foram discutidos longamente acima, sem uma conclusão clara:

  1. O que fazemos com undefined? (minha opinião: ainda está em toda parte
    e desmarcado)
  2. Como lidamos com a compatibilidade com o código existente, em particular
    definições? (minha opinião: sinalizador opt-in, pelo menos por arquivo de definição.
    Ligar o sinalizador está "quebrando" porque você pode ter que adicionar null
    anotações.)

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -138641395
.

@ jods4 Entendo do seu ponto 2 que seu fork também infere o tipo não anulável por padrão, em vez de exigir uma anotação de tipo não anulável explícita?

Você pode vinculá-lo, por favor? Eu gostaria de experimentar.

@impinball
Eu adoraria ver (alguma) segurança contra undefined também, mas é bastante difundida.

Em particular, podemos definir uma matriz de tipos não anuláveis?
Dado que um acesso de array fora dos limites (ou esparso) retorna undefined , podemos convenientemente definir e usar arrays?
Eu acho que exigir que todos os arrays sejam anuláveis ​​é muito trabalhoso na prática.

Devemos diferenciar os tipos null e undefined ? Isso não é difícil: T | null , T | undefined , T | null | undefined e pode fornecer uma resposta fácil para a pergunta acima. Mas e quanto à sintaxe abreviada: o que T? significa? Nulo e indefinido? Precisamos de duas abreviações diferentes?

@Arnavion
Tipos nulos e indefinidos já existem no TS.
Minha opinião foi:

  1. Tornar todos os tipos não anuláveis ​​(incluindo tipos inferidos);
  2. Dê ao tipo nulo um nome ( null ) que você pode usar nas declarações de tipo;
  3. Remova o alargamento de null para any ;
  4. Introduzir a abreviação de sintaxe T? que é igual a T | null ;
  5. Remova as conversões implícitas de null para qualquer outro tipo.

Sem acesso às minhas fontes, acho que é o ponto principal. O tipo Null existente e o maravilhoso sistema de tipo TS (especialmente os tipos de união e protetores de tipo) fazem o resto.

Ainda não inscrevi meu trabalho no github, então não posso compartilhar um link por enquanto. Eu queria codificar a chave de compatibilidade primeiro, mas estou muito ocupado com outras coisas :(
A opção de compatibilidade é muito mais envolvida <_ <. Mas é importante porque agora o compilador TS compila a si mesmo com muitos erros e muitos testes existentes falham.
Mas parece realmente funcionar bem em um código totalmente novo.

Deixe-me resumir o que vi de alguns comentaristas até agora, na esperança de poder ilustrar como essa conversa está acontecendo em círculos:
Problema: Certos valores de 'caso especial' estão encontrando seu estado em um código que não foi projetado para lidar com eles, porque eles são tratados de forma diferente de todos os outros tipos na linguagem (ou seja, nulo e indefinido).
Eu: por que você simplesmente não estabelece padrões de programação para que esses problemas não aconteçam?
Outro: porque seria bom se a intenção pudesse ser refletida na digitação, porque então não seria documentado de forma diferente por cada equipe que trabalha no script de tipo e menos suposições falsas sobre bibliotecas de terceiros acontecerão.
Everone: Como vamos lidar com esse problema?
Outro: vamos apenas tornar os nulos mais rígidos e usar padrões para lidar com os indefinidos!

Não vejo como isso pode ser considerado uma solução quando undefined é muito mais um problema do que null!

Disclaimer: isso não reflete as atitudes de todos aqui, é só que já surgiu o suficiente para que eu quisesse destacar isso.
A única maneira de isso ser aceito é se for uma solução para um problema, não um pouco de solução para a parte menor de um problema.

Droga, telefone!
*todo o mundo

* surgiu o suficiente para que eu quisesse destacá-lo.

Além disso, o último parágrafo deve ser lido como "a única maneira desta proposta"

@ jods4 Eu diria que dependeria de certas construções. Em alguns casos, você pode garantir a não anulação nesses casos, como os seguintes:

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

Nesse caso, o compilador deve ter uma tonelada de lógica para garantir que a matriz seja verificada nos limites:

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];
}

E, neste caso, não há como garantir que o compilador possa verificar se o acesso para obter entry está dentro dos limites sem realmente avaliar partes do código:

declare const list: T![]

const end = round(max, list.length);

for (let i = 0; i < end; i++) {
  const entry: T![] = list[i];
}

Existem alguns fáceis e óbvios, mas existem alguns mais difíceis.

@impinball De fato, APIs modernas como map , forEach ou for..of estão ok porque eles ignoram elementos que nunca foram inicializados ou excluídos. (Eles incluem elementos que foram definidos como undefined , mas nosso hipotético TS seguro para nulos proibiria isso.)

Mas o acesso de array clássico é um cenário importante e gostaria de ver uma boa solução para isso. Fazer uma análise complexa como você sugeriu claramente não é possível, exceto em casos triviais (ainda casos importantes, uma vez que são comuns). Mas note que mesmo se você pudesse provar que i < array.length , isso não prova que o elemento foi inicializado.

Considere o seguinte exemplo, o que você acha que o TS deve fazer?

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

Também há o problema com Object.defineProperty.

let array = new Array(5)
Object.defineProperty(array, "length", 2, {
  get() { return 10 },
})

Na quarta-feira, 9 de setembro de 2015, 17:49 jods [email protected] escreveu:

@impinball https://github.com/impinball Na verdade, API moderna como mapa,
forEach ou for..of estão ok porque eles ignoram elementos que nunca foram
inicializado ou excluído. (Eles incluem elementos que foram definidos para
indefinido, mas nosso hipotético TS seguro para nulos proibiria isso.)

Mas o acesso a array clássico é um cenário importante e eu gostaria de
veja uma boa solução para isso. Fazer uma análise complexa como você sugeriu é
claramente não é possível, exceto em casos triviais (ainda casos importantes, uma vez que
eles são comuns). Mas note que mesmo se você pudesse provar que eu <
array.length não prova que o elemento foi inicializado.

Considere o seguinte exemplo, o que você acha que o TS deve fazer?

deixe array: T! [] = []; // um array vazio de não nulo, não indefinido T // blá, blá
matriz [4] = novo T (); // Bom para array [4], mas significa que array [0..3] são indefinidos, está certo? // blah blahlet i = 2; // Observe que poderíamos ter um array de limites guardif (i <array. comprimento) {
deixe t = array [i]; // O tipo inferido seria T !, mas na verdade é indefinido :(
}

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -139055786
.

Tudo que você precisa fazer é:

  1. Torne os tipos Nulo e Indefinido referenciáveis.
  2. Evita que valores nulos e indefinidos possam ser atribuídos a qualquer coisa.
  3. Use um tipo de união com Nulo e / ou Indefinido onde a nulidade está implícita.

Na verdade, é uma mudança ousada e parece mais adequada para um idioma
extensão.
Em 10 de setembro de 2015, às 9h13, "Isiah Meadows" [email protected] escreveu:

Também há o problema com Object.defineProperty.

let array = new Array(5)
Object.defineProperty(array, "length", 2, {
get() { return 10 },
})

Na quarta-feira, 9 de setembro de 2015, 17:49 jods [email protected] escreveu:

@impinball https://github.com/impinball Na verdade, API moderna como
mapa,
forEach ou for..of estão ok porque eles ignoram elementos que nunca foram
inicializado ou excluído. (Eles incluem elementos que foram definidos para
indefinido, mas nosso hipotético TS seguro para nulos proibiria isso.)

Mas o acesso a array clássico é um cenário importante e eu gostaria de
veja uma boa solução para isso. Fazer uma análise complexa como você sugeriu é
claramente não é possível, exceto em casos triviais (ainda casos importantes, uma vez que
eles são comuns). Mas note que mesmo se você pudesse provar que eu <
array.length não prova que o elemento foi inicializado.

Considere o seguinte exemplo, o que você acha que o TS deve fazer?

deixe array: T! [] = []; // um array vazio de não nulo, não indefinido T //
blá blá
matriz [4] = novo T (); // Ótimo para array [4], mas significa que array [0..3] é
indefinido, está certo? // blá, blá, i = 2; // Observe que poderíamos ter um
array bounds guardif (i <array.length) {
deixe t = array [i]; // O tipo inferido seria T !, mas isso é na verdade
Indefinido :(
}

-
Responda a este e-mail diretamente ou visualize-o no GitHub
<
https://github.com/Microsoft/TypeScript/issues/185#issuecomment-139055786>
.

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -139230568
.

@impinball
Eu me sinto bem em relação ao seu exemplo. Usar defineProperty dessa maneira é sair da caixa de segurança do TS e entrar no domínio JS dinâmico, não acha? Acho que nunca chamei defineProperty diretamente no código TS.

@ aleksey-bykov

parece mais adequado para uma extensão de idioma.

Outro problema com essa proposta é que, a menos que seja amplamente aceita, ela terá seu valor menor.
Eventualmente, precisamos de definições de TS atualizadas e eu não acho que isso acontecerá se for uma extensão ou bifurcação de TS raramente usada e incompatível.

Eu sei que arrays digitados têm uma garantia, porque IIRC eles lançam um
ReferenceError no carregamento e armazenamento fora dos limites da matriz. Matrizes regulares e
objetos de argumentos retornam indefinidos quando o índice está fora dos limites. No meu
opinião, essa é uma falha de linguagem JS, mas consertá-la definitivamente
quebrar a web.

A única maneira de "consertar" isso é no ES6, por meio de um construtor que retorna um proxy,
e seu protótipo de instância e autoprotótipo definido para o Array original
construtor. Algo assim:

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

(observação: não foi testado, foi digitado em um telefone ...)

Em Qui, 10 de setembro de 2015, 10:09 jods [email protected] escreveu:

@impinball https://github.com/impinball
Eu me sinto bem em relação ao seu exemplo. Usar defineProperty dessa maneira é
pisando fora da caixa de segurança de TS e no reino JS dinâmico, não
você pensa? Acho que nunca chamei defineProperty diretamente no código TS.

@ aleksey-bykov https://github.com/aleksey-bykov

parece mais adequado para uma extensão de idioma.

Outro problema com esta proposta é que, a menos que seja amplamente aceita,
tem menos valor.
Eventualmente, precisamos de definições de TS atualizadas e eu não acho que
acontecer se for uma extensão ou bifurcação de TS raramente usada, incompatível.

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -139245706
.

@impinball Não tenho certeza se há algo para "consertar" em primeiro lugar ...
Essas são as semânticas de JS e TS devem acomodá-las de alguma forma.

E se tivéssemos apenas uma marcação (ligeiramente diferente) para declarar um array esparso vs um array não esparso, e fazer o não esparso inicializar automaticamente (talvez os programadores possam fornecer um valor ou equação para a inicialização). Dessa forma, podemos forçar os arrays esparsos a serem do tipo T|undefined (que mudaria para o tipo T usando for ... of e outras operações 'seguras') e deixar os tipos de array não esparsos sozinhos.

//not-sparse
var array = [arrlength] => index*3;
var array = <number[]>[3];
//sparse
var array = [];

Obviamente, esta não é a sintaxe final.
O segundo exemplo inicializaria cada valor com o padrão do compilador para esse tipo.
Isso significa que, para matrizes não esparsas, você precisa digitá-los, caso contrário, suspeito que você teria que convertê-los em algo depois que foram totalmente inicializados.
Além disso, devemos precisar de um tipo não esparso para matrizes para que os programadores possam converter suas matrizes em não esparsas.

@Griffork
Não sei ... fica confuso.

Dessa forma, podemos forçar matrizes esparsas a serem do tipo T|undefined (que mudaria para o tipo T usando for ... of e outras operações 'seguras')

Por causa de uma peculiaridade em JS, ele não funciona assim. Suponha let arr: (T|undefined)[] .
Portanto, estou livre para fazer: arr[0] = undefined .
Se eu fizer isso, usar essas funções "seguras" _will_ retornará undefined para o primeiro slot. Portanto, em arr.forEach(x => ...) você não pode dizer que x: T . Ainda deve ser x: T|undefined .

O segundo exemplo inicializaria cada valor com o padrão do compilador para esse tipo.

Isso não é muito semelhante ao TS em espírito. Talvez eu esteja errado, mas me parece que a filosofia do TS é que os tipos são apenas uma camada adicional no topo do JS e não afetam o codegen. Isso tem implicações de desempenho por causa da correção de tipo parcial, das quais não gosto muito.

O TS obviamente não pode proteger você de tudo em JS e há várias funções / construções que você pode chamar de um TS válido, que tem efeitos dinâmicos no tipo de tempo de execução de seus objetos e interrompe a análise de tipo de TS estático.

Seria ruim se isso fosse um buraco no sistema de tipos? Quer dizer, este código realmente não é comum: let x: number[] = []; x[3] = 0; e se esse é o tipo de coisa que você deseja fazer, então talvez você deva declarar seu array let x: number?[] .

Não é perfeito, mas acho que é bom o suficiente para a maioria dos usos no mundo real. Se você é um purista que deseja um sistema de tipo de som, então certamente deveria procurar outro idioma, porque o sistema de tipo TS não é som de qualquer maneira. O que você acha?

É por isso que eu disse que você também precisa da capacidade de lançar em uma matriz não esparsa
tipo, para que você mesmo possa inicializar um array sem o desempenho
impacto.
Eu me contentaria com apenas uma diferenciação entre (o que deveria ser) esparso
matrizes e matrizes não esparsas por tipo.

Para quem não sabe por que isso é importante, é o mesmo motivo que você
iria querer uma diferença entre T e T|null .

Em 9h11, sexta-feira, 11 de setembro de 2015, jods [email protected] escreveu:

@Griffork https://github.com/Griffork
Não sei ... fica confuso.

Dessa forma, podemos forçar matrizes esparsas a serem do tipo T | indefinido (o que seria
mude para o tipo T usando para ... de e outras operações 'seguras')

Por causa de uma peculiaridade em JS, ele não funciona assim. Assuma let arr:
(T | indefinido) [].
Portanto, estou livre para fazer: arr [0] = undefined.
Se eu fizer isso, usar essas funções "seguras" _will_ retornará indefinido
para o primeiro slot. Portanto, em arr.forEach (x => ...) você não pode dizer que x: T.
Ele ainda precisa ser x: T | indefinido.

O segundo exemplo inicializaria cada valor para o padrão do compilador
para esse tipo.

Isso não é muito semelhante ao TS em espírito. Talvez eu esteja errado, mas me parece
que a filosofia do TS é que os tipos são apenas uma camada adicional em cima do JS
e eles não afetam o codegen. Isso tem implicações de desempenho por causa de
correção de tipo parcial de que não gosto muito.

O TS obviamente não pode proteger você de tudo em JS e há
várias funções / construções que você pode chamar de um TS válido, que tem
efeitos dinâmicos no tipo de tempo de execução de seus objetos e interromper o TS estático
análise de tipo.

Seria ruim se isso fosse um buraco no sistema de tipos? Quero dizer, este código
realmente não é comum: deixe x: número [] = []; x [3] = 0; e se for o
tipo de coisas que você deseja fazer, então talvez você deva declarar seu array let
x: número? [].

Não é perfeito, mas acho que é bom o suficiente para a maioria dos usos no mundo real.
Se você é um purista que deseja um sistema de tipo de som, então você certamente deve
olhe para outro idioma, porque o sistema de tipo TS não é som de qualquer maneira. O que
Você acha que?

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -139408240
.

@ jods4 O que eu quis dizer com "consertar" era "consertar" o que é IMHO uma falha de design da linguagem JS. Não em TypeScript, mas no próprio _JavaScript_.

@jods , estou reclamando de JS, não de TS. Admito que está fora do assunto.

Em quinta-feira, 10 de setembro de 2015, 19:19, Griffork [email protected] escreveu:

É por isso que eu disse que você também precisa da capacidade de lançar em uma matriz não esparsa
tipo, para que você mesmo possa inicializar um array sem o desempenho
impacto.
Eu me contentaria com apenas uma diferenciação entre (o que deveria ser) esparso
matrizes e matrizes não esparsas por tipo.

Para quem não sabe por que isso é importante, é o mesmo motivo que você
iria querer uma diferença entre T e T|null .

Em 9h11, sexta-feira, 11 de setembro de 2015, jods [email protected] escreveu:

@Griffork https://github.com/Griffork
Não sei ... fica confuso.

Dessa forma, podemos forçar matrizes esparsas a serem do tipo T | indefinido (o que seria
mude para o tipo T usando para ... de e outras operações 'seguras')

Por causa de uma peculiaridade em JS, ele não funciona assim. Assuma let arr:
(T | indefinido) [].
Portanto, estou livre para fazer: arr [0] = undefined.
Se eu fizer isso, usar essas funções "seguras" _will_ retornará indefinido
para o primeiro slot. Portanto, em arr.forEach (x => ...) você não pode dizer que x: T.
Ele ainda precisa ser x: T | indefinido.

O segundo exemplo inicializaria cada valor para o padrão do compilador
para esse tipo.

Isso não é muito semelhante ao TS em espírito. Talvez eu esteja errado, mas me parece
que a filosofia TS é que os tipos são apenas uma camada adicional sobre
JS
e eles não afetam o codegen. Isso tem implicações de desempenho por causa de
correção de tipo parcial de que não gosto muito.

O TS obviamente não pode proteger você de tudo em JS e há
várias funções / construções que você pode chamar de um TS válido, que
ter
efeitos dinâmicos no tipo de tempo de execução de seus objetos e interromper o TS estático
análise de tipo.

Seria ruim se isso fosse um buraco no sistema de tipos? Quero dizer, este código
realmente não é comum: deixe x: número [] = []; x [3] = 0; e se for o
tipo de coisas que você deseja fazer, então talvez você deva declarar sua matriz
deixei
x: número? [].

Não é perfeito, mas acho que é bom o suficiente para a maioria dos usos no mundo real.
Se você é um purista que deseja um sistema de tipo de som, você deve
certamente
olhe para outro idioma, porque o sistema de tipo TS não é som de qualquer maneira.
O que
Você acha que?

-
Responda a este e-mail diretamente ou visualize-o no GitHub
<
https://github.com/Microsoft/TypeScript/issues/185#issuecomment-139408240>
.

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -139409349
.

E quanto à minha declaração com comprimentos de Array, poderíamos operar na suposição de que todos os acessos de array estão dentro dos limites e que o acesso fora dos limites é indefinido, a menos que seja explicitamente especificado na interface. Isso é muito parecido com o que C / C ++ faz, e permitiria uma melhor digitação e, potencialmente, uma carga inteira de otimizações do compilador, se alguém decidir escrever um compilador de terceiros que usa a especificação da linguagem, mas não está tão preocupado com combinando o emit.

Eu sei que suportar a correspondência de comportamento indefinido de C / C ++ parece muito estúpido na superfície, mas acho que, neste caso, pode valer a pena. É raro ver algo que realmente é feito _melhor_ ao fazer um acesso fora dos limites. 99,99% dos usos que vi para isso são apenas cheiros de código extremamente pungentes, quase sempre feitos por pessoas que quase não têm familiaridade com JavaScript.

(A maioria dessas pessoas, em minha experiência, nunca ouviu falar de CoffeeScript, muito menos de TypeScript. Muitos deles nem mesmo sabem da nova versão de JS que acabou de ser finalizada e padronizada, ES2015.)

Existe uma resolução emergente para isso?

A menos que tenha um tipo não anulável, ainda parece útil para o TypeScript falhar se alguém estiver tentando acessar uma propriedade em uma variável que é _assuredly_ null.

var o = null;
console.log(o.x);

... deve falhar.

Garantir, por meio do sistema de tipos, que todo o acesso ao array seja verificado por limites, parece derivar para o reino dos tipos dependentes. Embora os tipos dependentes sejam bem legais, isso parece ser um recurso muito maior do que os tipos não anuláveis.

Parece que há três opções, assumindo que a verificação de limites não é aplicada em matrizes em tempo de compilação:

  1. A indexação de matriz (e qualquer acesso de elemento de matriz arbitrário por índice) é considerada para retornar um tipo anulável, mesmo em matrizes de tipos não anuláveis. Essencialmente, o "método" [] tem uma assinatura de tipo T? . Se você sabe que está apenas fazendo indexação com limites verificados, pode converter T? em T! em seu código de aplicativo.
  2. A indexação de matriz retorna exatamente o mesmo tipo (com a mesma nulidade) que o parâmetro de tipo genérico da matriz, e é assumido que todo o acesso à matriz é verificado por limites pelo aplicativo. O acesso fora dos limites retornará undefined e não será detectado pelo verificador de tipo.
  3. A opção nuclear: todas as matrizes são codificadas no idioma como sendo anuláveis ​​e as tentativas de usar matrizes não anuláveis ​​falham nas verificações de tipo.

Todos eles se aplicam ao acesso baseado em índice de propriedades em objetos, também, por exemplo, object['property'] onde object é do tipo { [ key: string ]: T! } .

Pessoalmente, prefiro a primeira opção, em que a indexação em uma matriz ou objeto retorna um tipo anulável. Mas mesmo a segunda opção parece melhor do que tudo ser anulável, que é o estado atual das coisas. A opção nuclear é grosseira, mas, honestamente, ainda melhor do que tudo sendo anulável.

Há uma segunda questão de saber se os tipos devem ser, por padrão, não anuláveis ​​ou, por padrão, anuláveis. Parece que em ambos os casos seria útil ter sintaxe para tipos explicitamente anuláveis ​​e explicitamente não anuláveis ​​para lidar com genéricos; por exemplo, imagine um método get em uma classe de contêiner (por exemplo, um Mapa) que recebeu algum valor e possivelmente retornou um tipo, _mesmo se o contêiner contivesse apenas valores não anuláveis_:

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

Da mesma forma, podemos (este é um argumento menos forte) querer garantir que nossa classe de contêiner aceite apenas chaves não nulas em inserções, mesmo ao usar um tipo de chave anulável:

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

Portanto, independentemente de o padrão ser alterado, parece que seria útil ter uma sintaxe explícita para os tipos anuláveis ​​e não anuláveis. A menos que eu esteja faltando alguma coisa?

No ponto em que há sintaxe para ambos, o padrão parece que pode ser um sinalizador do compilador semelhante a --noImplicitAny . Pessoalmente, eu votaria para que o padrão permanecesse o mesmo até o lançamento 2.0, mas qualquer um dos dois parece bom, contanto que haja uma saída de emergência (pelo menos temporariamente).

Eu preferiria a segunda opção, pois mesmo que ela tornasse o acesso fora dos limites um comportamento indefinido (no reino da tipagem TS), acho que é um bom compromisso para isso. Pode aumentar muito a velocidade e é mais simples de lidar. Se você realmente espera que um acesso fora dos limites seja possível, você deve usar um tipo anulável explicitamente ou converter o resultado para um tipo anulável (o que sempre é possível). E, geralmente, se a matriz não for anulável, qualquer acesso fora dos limites é quase sempre um bug, que deve explodir violentamente em algum ponto (é uma falha de JS).

Exige muito que o programador seja explícito no que está esperando. É menos seguro para tipos, mas, neste caso, acho que a segurança para tipos pode atrapalhar.

Aqui está uma comparação com cada opção, usando uma função de soma como exemplo (os primitivos são os mais problemáticos):

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

Outro exemplo: uma função 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
}

Outra pergunta: qual é o tipo de entry em cada um deles? !string , ?string ou 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) { /* ... */  }

A opção três foi uma sugestão um tanto irônica: stick_out_tongue:

Re: sua última pergunta:

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

Em alguns casos - quando você deseja retornar um tipo não anulável e está obtendo-o de uma matriz, por exemplo - você terá que fazer um elenco extra com a opção um, assumindo que você garantiu em outro lugar que não há valores indefinidos na matriz (o requisito desta garantia não muda, independentemente de qual abordagem for tomada, apenas a necessidade de digitar as string! ). Pessoalmente, ainda prefiro porque é mais explícito (você deve especificar quando está assumindo um comportamento possivelmente perigoso, ao contrário de acontecer implicitamente) e mais consistente com o funcionamento da maioria das classes de contêiner: por exemplo, get um mapa get retorna claramente tipos anuláveis ​​(ela retorna o objeto se ele existe na chave, ou nulo se não), e se Map.prototype.get retorna um anulável então object['property'] provavelmente deve fazer o mesmo, uma vez que eles oferecem garantias semelhantes sobre a nulidade e são usados ​​de forma semelhante. O que deixa os arrays como os ímpares, onde erros de referência nula podem voltar e onde o acesso aleatório pode ser não anulável pelo sistema de tipos.

Definitivamente, existem outras abordagens; por exemplo, atualmente o Flow usa a opção dois e, da última vez que verifiquei, o SoundScript tornou os arrays esparsos explicitamente ilegais em suas especificações (bem, o modo forte / "SaneScript" os torna ilegais e o SoundScript é um superconjunto das novas regras), que até certo ponto contorna o problema, embora eles ainda precisem descobrir como lidar com as alterações manuais de comprimento e com a alocação inicial. Eu suspeito que eles chegarão mais perto da opção um na troca de conveniência vs. segurança - isto é, será menos conveniente de escrever, mas mais seguro - devido à sua ênfase na solidez do sistema de tipo, mas provavelmente parecerá um pouco diferente do que qualquer uma dessas abordagens devido à proibição de matrizes esparsas.

Acho que o aspecto do desempenho é extremamente teórico neste ponto, já que o AFAIK TypeScript continuará emitindo o mesmo JS, independentemente dos casts para qualquer escolha aqui, e as VMs subjacentes continuarão verificando os arrays sob o capô independentemente. Portanto, não sou muito influenciado por esse argumento. A questão para mim é principalmente em torno de conveniência versus segurança; para mim, a vitória da segurança aqui parece compensar a troca de conveniência. Claro, qualquer um é uma melhoria em relação a ter todos os tipos anuláveis.

Eu concordo que a parte do desempenho é principalmente teórica, mas ainda assim
como a conveniência de assumir. A maioria das matrizes são densas e nulidade por
padrão não faz sentido para matrizes booleanas e numéricas. Se não for
pretende ser uma matriz densa, deve ser marcada como explicitamente anulável para
a intenção é clara.


TypeScript realmente precisa de uma maneira de afirmar as coisas, uma vez que as afirmações são frequentemente
usado para auxiliar na verificação de tipo estático em outras linguagens. Eu vi no
O código V8 baseia uma macro UNREACHABLE(); que permite suposições para ser um
um pouco mais seguro, travando o programa se o invariante for violado. C ++
tem static_assert para asserções estáticas para ajudar na verificação de tipo.

Na terça-feira, 20 de outubro de 2015 às 4:01, Matt Baker [email protected]
escreveu:

A opção três foi uma sugestão irônica [imagem:
: stick_out_tongue:]

Re: sua última pergunta:

declarar const regularStrings: string []; declarar const nullableStrings: string? []; declarar const nonNullableStrings: string! []; // falha na verificação de tipo na opção três
for (entrada const de regularStrings) {
// opção 1: a entrada é do tipo string?
// opção 2: depende da nulidade padrão
}
for (entrada const de nullableStrings) {
// opção 1 e 2: a entrada é do tipo string?
}
for (entrada const de nonNullableStrings) {
// opção 1: a entrada é do tipo string?
// opção 2: a entrada é do tipo string!
}

Em alguns casos - quando você deseja retornar um tipo não anulável e você
obtê-lo de uma matriz, por exemplo - você terá que fazer um elenco extra
com a opção um assumindo que você em outro lugar garantiu que não há indefinidos
valores na matriz (o requisito desta garantia não muda
independentemente da abordagem escolhida, apenas a necessidade de digitar como string!).
Pessoalmente, ainda prefiro porque é mais explícito (você tem que
especifique quando você está assumindo um comportamento possivelmente perigoso, ao contrário dele
acontecendo implicitamente) e mais consistente com a forma como a maioria das classes de contêiner
trabalho: por exemplo, a função get de um Map retorna claramente tipos anuláveis
(retorna o objeto se existir na chave, ou null se não existir),
e se Map.prototype.get retornar um valor anulável, então o objeto ['propriedade']
provavelmente deve fazer o mesmo, uma vez que eles oferecem garantias semelhantes sobre
anulabilidade e são usados ​​de forma semelhante. O que deixa as matrizes estranhas de fora
onde erros de referência nula podem voltar, e onde o acesso aleatório é
permitido ser não anulável pelo sistema de tipo.

Definitivamente, existem outras abordagens; por exemplo, atualmente o Flow usa
opção dois http://flowtype.org/docs/nullable-types.html e último I
O SoundScript verificado tornou matrizes esparsas explicitamente ilegais em suas especificações
https://github.com/rwaldron/tc39-notes/blob/master/es6/2015-01/JSExperimentalDirections.pdf
(bem, o modo forte / "SaneScript" os torna ilegais, e SoundScript é um
superconjunto das novas regras), que até certo ponto contorna o problema
embora eles ainda precisem descobrir como lidar com o comprimento manual
mudanças e com alocação inicial.

Acho que o aspecto do desempenho é extremamente teórico neste ponto,
uma vez que AFAIK TypeScript continuará emitindo o mesmo JS independentemente de
casts para qualquer escolha aqui e as VMs subjacentes continuarão
matrizes de verificação de limites sob o capô independentemente. Então eu não sou muito influenciado por
esse argumento. A questão para mim é principalmente em torno de conveniência vs.
segurança; para mim, a vitória da segurança aqui parece compensar a troca de conveniência. De
claro, qualquer um é uma melhoria em relação a ter todos os tipos anuláveis.

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -149468527
.

Isiah Meadows

Devemos simplesmente começar a chamá-los de tipos non-void ? Em qualquer caso, acho que definir explicitamente non-void com T! ou !T é um erro. É difícil de ler como humano e também difícil de lidar com todos os casos do compilador TypeScript.

O principal problema que vejo com os tipos non-void por padrão é que se trata de uma alteração significativa. Bem, e se apenas adicionarmos mais análise estática, semelhante ao Flow, que não muda o comportamento de forma alguma, mas detecta mais bugs? Assim, podemos detectar a maioria dos bugs dessa classe agora, mas não altere a sintaxe e, no futuro, será muito mais fácil introduzir um sinalizador de compilador ou comportamento padrão que seja menos uma alteração significativa.

`` `.ts
// função compila alegremente
função len (x: string): número {
return x.length;
}

len ("funciona"); // 5
len (nulo); // erro, nenhum comprimento de propriedade de nulo

``` .ts
function len(x: string): number {
    if (x === null) {
        return -1;
    }
    return x.length;
}

len("works"); // 5
len(null); // null

O que realmente estaria acontecendo aqui é modelar os dados de entrada como non-void , mas adicionar void implicitamente quando for manipulado na função. Da mesma forma, o tipo de retorno é non-void menos que possa retornar explicitamente null ou undefined

Também podemos adicionar o tipo ?T ou T? , que força a verificação nula (e / ou indefinida) antes do uso. Pessoalmente, gosto de T? , mas há precedentes para usar ?T com Flow.

`` `.ts
função len (x:? string): número {
return x.length; // erro: nenhuma propriedade de comprimento no tipo? string, você deve usar um protetor 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

Sob o capô, o que realmente está acontecendo aqui é que o TypeScript está inferindo se um tipo pode ou não ser nulo ao ver se ele lida com nulo e recebe um valor possivelmente nulo.

A única parte complicada aqui é como fazer a interface com os arquivos de definição. Eu acho que isso pode ser resolvido tendo como padrão assumir que um arquivo de definição declarando function(t: T) faz uma verificação nula / void , muito parecido com o segundo exemplo. Isso significa que essas funções poderão assumir valores nulos sem que o compilador gere um erro.

Isso agora permite duas coisas:

  1. Adoção gradual da sintaxe de tipo ?T , para a qual os parâmetros opcionais já teriam sido trocados.
  2. No futuro, um sinalizador do compilador --noImplicitVoid poderia ser adicionado, o que trataria os arquivos de declaração da mesma forma que os arquivos de código compilados. Isso seria "quebrar", mas se feito no futuro, a maioria das bibliotecas adotará a prática recomendada de usar ?T quando o tipo pode ser void e T quando não pode. Também seria opcional, de forma que apenas aqueles que optassem por usá-lo seriam afetados. Isso também pode exigir que a sintaxe ?T seja usada no caso de um objeto ser nulo.

Acho que essa é uma abordagem realista, pois oferece segurança muito maior quando a fonte está disponível no TypeScript, encontrando esses problemas complicados, ao mesmo tempo que permite uma integração fácil, bastante intuitiva e compatível com as versões anteriores com arquivos de definição.

O prefixo ? variante também é usado nas anotações do Compilador de Fechamento, IIRC.

Na terça-feira, 17 de novembro de 2015, 13:37, Tom Jacques [email protected] escreveu:

Devemos apenas começar a chamá-los de tipos não-nulos? Em qualquer caso, eu acho
definindo explicitamente não vazio com T! ou! T é um erro. É difícil
para ler como um ser humano, e também difícil de lidar com todos os casos de
o compilador TypeScript.

O principal problema que vejo com os tipos não vazios por padrão é que é um
quebra de mudança. Bem, e se apenas adicionarmos mais algumas análises estáticas,
semelhante ao Flow, que não muda o comportamento de forma alguma, mas irá capturar
mais bugs? Então podemos detectar muitos dos bugs desta classe agora, mas
não mude a sintaxe e, no futuro, será muito mais fácil
introduzir um sinalizador de compilador ou comportamento padrão que é menos do que uma quebra
mudança.

// função compila perfeitamente função len (x: string): número {
return x.length;
}

len ("funciona"); // 5
len (nulo); // erro, nenhum comprimento de propriedade de nulo

função len (x: string): número {
if (x === null) {
return -1;
}
return x.length;
}

len ("funciona"); // 5
len (nulo); // nulo

O que realmente estaria acontecendo aqui é modelar os dados de entrada como não vazios,
mas adicionando vazio implicitamente quando é tratado na função. Similarmente,
o tipo de retorno não é nulo, a menos que possa retornar explicitamente nulo ou
Indefinido

Também podemos adicionar o? T ou T? tipo, o que força o nulo (e / ou
indefinido) verifique antes de usar. Pessoalmente, gosto do T ?, mas há precedentes
para usar? T com Flow.

função len (x:? string): número {
return x.length; // erro: nenhuma propriedade de comprimento no tipo? string, você deve usar um protetor de tipo
}

Mais um exemplo - que tal usar os resultados da função?

função len (x: string): número {
return x.length;
}
identidade da função (f: string): string {
return f;
}
função desconhecida (): string {
if (Math.random ()> 0,5) {
return null;
}
retornar "talvez";
}

len ("funciona"); // 5
len (nulo); // erro, nenhum comprimento de propriedade de nulo

identidade ("obras"); // "funciona": string
identidade (nula); // null: void
desconhecido(); // ?corda

len (identidade ("funciona")); // 5
len (identidade (nula)); // erro, nenhum comprimento de propriedade de nulo
len (desconhecido ()); // erro: nenhuma propriedade de comprimento no tipo? string, você deve usar um protetor de tipo

Nos bastidores, o que realmente está acontecendo aqui é que o TypeScript é
inferir se um tipo pode ou não ser nulo, vendo se ele lida com
nulo e recebe um valor possivelmente nulo.

A única parte complicada aqui é como fazer a interface com os arquivos de definição. eu
acho que isso pode ser resolvido tendo como padrão assumir que um
arquivo de definição que declara uma função (t: T) faz verificação de nulo / vazio, muito
como o segundo exemplo. Isso significa que essas funções serão capazes de realizar
valores nulos sem que o compilador gere um erro.

Isso agora permite duas coisas:

  1. Adoção gradual da sintaxe de tipo? T, da qual é opcional
    os parâmetros já estariam trocados.
  2. No futuro, um sinalizador do compilador --noImplicitVoid pode ser adicionado
    que trataria os arquivos de declaração da mesma forma que os arquivos de código compilados. Isto
    seria "quebrar", mas se feito no futuro, a maioria dos
    as bibliotecas adotarão a melhor prática de usar? T quando o tipo puder
    ser vazio e T quando não puder. Também seria opt-in, então apenas aqueles
    quem escolher usá-lo seria afetado.

Acho que é uma abordagem realista, pois oferece uma segurança muito melhor
quando a fonte está disponível no TypeScript, encontrar esses problemas complicados,
ao mesmo tempo que permite fácil, bastante intuitivo e compatível com versões anteriores
integração com arquivos de definição.

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -157463734
.

Bom ponto. Também há muitas semelhanças com as declarações de parâmetros opcionais existentes:

`` `.ts
interface withOptionalProperty {
o ?: string
}
interface withVoidableProperty {
o:? string
}

função withOptionalParam (o ?: string) {}
função comVoidableParam (o:? string) {}
`` `

Na verdade, eles usam prefixo . Eles usam ? para tipos anuláveis ​​explícitos e ! para tipos não anuláveis, com anulável sendo o padrão.

A distinção anulável vs anulável faz muito sentido. : +1:

Eu sinto que isso está andando em círculos.

Todos vocês leram acima por que uma definição anulável / não anulável não vai resolver o problema subjacente?

@Griffork Eu li todos os comentários. Admito que o que eu disse é um pouco uma reformulação / combinação do que outros disseram, mas acho que é o caminho mais realista a seguir. Não sei o que você vê como o problema subjacente, mas para mim o problema subjacente é que null e undefined fazem parte de todos os tipos, e o compilador atualmente não garante a segurança ao tentar para usar um argumento do tipo T . Conforme meu exemplo:

`` `.ts
função len (x: string): número {
return x.length;
}

len ("funciona");
// Sem erro - 5

len (nulo);
// O compilador permite isso, mas deve ocorrer um erro aqui com algo como
// erro: nenhuma propriedade 'comprimento' de nulo

len (indefinido);
// O compilador permite isso, mas deve ocorrer um erro aqui com algo como
// erro: nenhuma propriedade 'comprimento' de indefinido
`` `

Essa é minha opinião sobre o problema fundamental com a _linguagem_ - a falta de segurança. Uma grande parte disso pode ser corrigido examinando o fluxo de argumentos em funções usando análise estática e de tipo, e dando um erro quando algo pode ser feito de forma insegura. Não é necessário haver nenhum tipo ?T quando o código estiver disponível, porque essa análise pode ser realizada (embora nem sempre seja perfeitamente preciso o tempo todo). A razão para adicionar um tipo ?T é porque ele força a verificação de segurança e torna a intenção do programador muito clara.

O problema com a _implementação_ é a compatibilidade com versões anteriores. Se a equipe de TS liberasse amanhã uma alteração que agora um tipo T não é nulo por padrão, quebraria o código existente que atualmente aceita e manipula entradas nulas com essas assinaturas de tipo. Os membros da equipe de TS declararam nesta mesma edição que não estão dispostos a fazer uma mudança tão grande. Eles estão dispostos a quebrar algumas coisas, se o efeito for pequeno o suficiente e o benefício for grande o suficiente, mas isso teria um impacto muito grande.

Minha proposta está em duas partes separadas, uma das quais eu acho que seria um ótimo complemento para a linguagem que não altera nenhuma das sintaxes / semânticas existentes, exceto para encontrar bugs reais e verdadeiros, e a outra é uma maneira possível de reduzir o impacto do interromper a mudança para obter garantias mais fortes de tipos não vazios no futuro. Ainda é uma mudança significativa, mas de um tamanho e natureza esperançosamente mais aceitáveis.

Parte um:

  • Adicione análise para identificar quando os tipos nulo / indefinido / vazio podem ser passados ​​para funções que não os tratam (funciona apenas quando o código TS está presente, não em arquivos de definição).
  • Adicione ?T type que força a verificação nula antes de usar o argumento. Na verdade, isso é apenas uma adição sintática em torno de um tipo de opção de nível de linguagem.
  • Esses dois recursos podem ser implementados de forma independente, pois cada um tem seu próprio mérito individual

Parte dois:

  • Esperar. Mais tarde na mesma linha, depois que a Parte Um é apresentada e a comunidade e os usuários de TS tiveram tempo para usar esses recursos, o padrão será usar T quando o tipo não for nulo e ?T quando o tipo pode ser nulo. Isso não é garantido, mas acho que essa seria uma prática recomendada óbvia.
  • Como um recurso separado, adicione uma opção do compilador --noImplicitVoid que requer que os tipos sejam ?T se eles puderem ser nulos. Este é apenas o compilador que reforça a prática recomendada já existente. Se houver um arquivo de definição que não siga as melhores práticas, ele estará incorreto, mas é por isso que é opcional.
  • Se você realmente quiser ser estrito, o sinalizador pode aceitar argumentos que especificam a quais diretórios / arquivos ele deve ser aplicado. Então, você pode aplicar a alteração apenas ao seu código e excluir node_modules .

Acho que esta é a opção mais realista porque a Parte Um pode ser feita mesmo sem a segunda parte. Ainda é um bom recurso que atenuará amplamente esse problema. Claro que não é perfeito, mas aceitarei o bom o suficiente se isso significar que pode acontecer. Ele também deixa a opção na mesa para verdadeiros tipos não nulos no futuro. Um problema agora é que quanto mais tempo esse problema persiste, mais uma alteração importante é consertá-lo, porque há mais código sendo escrito em TS. Com a Parte Um, na pior das hipóteses deve desacelerar drasticamente e, na melhor das hipóteses, reduzir o impacto ao longo do tempo.

@tejacques Eu, por exemplo, estou totalmente de acordo com isso.

@tejacques - FWIW, concordo totalmente com sua avaliação e proposta. Esperamos que a equipe do TS concorde :)

Na verdade, duas coisas:

Primeiro, não tenho certeza se a análise semelhante a Flow mencionada na Parte Um é necessária. Embora seja muito legal e útil, eu certamente não gostaria que ele contivesse tipos voidable / ?T , que parecem muito mais viáveis ​​no design atual da linguagem e fornecem muito mais valor a longo prazo.

Eu também expressaria --noImplicitVoid um pouco diferente - digamos que não permite atribuir null e undefined a tipos não anuláveis ​​(ou seja, padrão) em vez de "exigir que os tipos sejam ?T se eles puderem ser anulados ". Tenho certeza de que queremos dizer a mesma coisa, apenas semântica; concentra-se no uso ao invés da definição, o que, se eu entendo como o TS funciona, é a única coisa que ele realmente pode impor.

E algo agora veio à mente: teríamos então quatro níveis de voidability (isso também é verdade para Flow, que eu acho que está inspirando uma boa parte desta conversa):

interface Foo {
  w: string;
  x?: string;
  y: ?string;
  z?: ?string;
}

Sob --noImplicitVoid , w só pode ser uma string válida. Esta é uma grande vitória para a segurança de tipo. Adeus, erro de um bilhão de dólares! Sem --noImplicitVoid , é claro, a única restrição é que ele deve ser especificado, mas pode ser nulo ou indefinido. Esse é um comportamento bastante perigoso da língua, eu acho, porque parece que está garantindo mais do que realmente é.

x é completamente tolerante nas configurações atuais. Pode ser uma string, nulo, indefinido e nem mesmo precisa estar presente nos objetos que o implementam. Sob --noImplicitVoid , as coisas ficam um pouco mais complexas ... você pode não defini-lo, mas se você _do_ definir algo para ele, não pode ser nulo? Acho que a maneira como o Flow lida com isso é que você pode definir x como undefined (imitando a inexistência), mas não null . Isso pode ser um pouco opinativo demais para o TypeScript, no entanto.

Além disso, faria sentido lógico exigir a verificação de anulação x antes de usá-lo, mas isso, novamente, seria uma alteração importante. Esse comportamento pode fazer parte da sinalização --noImplicitVoid ?

y pode ser definido como qualquer string ou valor nulo, mas _deve_ estar presente de alguma forma, mesmo se nulo. E, é claro, acessá-lo exige uma verificação de vazio. Esse comportamento pode ser um pouco surpreendente. Devemos considerar _não_ configurá-lo para ser o mesmo que configurá-lo para undefined ? Em caso afirmativo, o que o tornaria diferente de x , exceto exigir um cheque nulo?

E, finalmente, z não precisa ser especificado, pode ser definido para absolutamente qualquer coisa (bem, exceto uma string não, é claro) e (por um bom motivo) requer uma verificação de anulação antes de acessá-lo. Tudo faz sentido aqui!

Há um pouco de sobreposição entre x e y , e suspeito que x eventualmente se tornaria obsoleto pela comunidade, preferindo a forma z para segurança máxima .

@tejacques

Não é necessário que haja nenhum tipo? T quando o código estiver disponível, porque essa análise pode ser realizada (embora nem sempre seja em todos os casos com uma precisão perfeita o tempo todo).

Esta afirmação está incorreta. Muitos aspectos da nulidade não podem ser considerados, mesmo com o código-fonte completo disponível.

Por exemplo: ao chamar em uma interface (estaticamente), você não pode saber se passar null está certo ou não se a interface não está anotada de acordo. Em um sistema de tipo estrutural como o TS, nem é fácil saber quais objetos são implementações da interface e quais não são. Em geral, você não se importa, mas se quiser deduzir a nulidade do código-fonte, você o faria.

Outro exemplo: se eu tiver um array list e uma função unsafe(x) cujo código-fonte mostra que não aceita um argumento null , o compilador não pode dizer se isso linha é segura ou não: list.filter(unsafe) . E, de fato, a menos que você possa saber estaticamente quais serão todos os conteúdos possíveis de list , isso não pode ser feito.

Outros casos estão ligados a herança e muito mais.

Não estou dizendo que uma ferramenta de análise de código que sinalizaria violação flagrante de contratos nulos não tenha valor (tem). Estou apenas apontando que minimizar a utilidade das anotações nulas quando o código-fonte está disponível é IMHO um erro.

Eu disse em algum lugar nesta discussão que acho que a inferência de nulidade pode ajudar a reduzir a compatibilidade com versões anteriores em muitos casos simples. Mas não pode substituir completamente as anotações de origem.

@tejacques meu mal, eu interpretei mal seu comentário (por algum motivo meu cérebro decidiu void===null :( Eu culpo só de acordar).
Obrigado pela postagem extra, porém, fez muito mais sentido para mim do que sua postagem original. Na verdade, gosto bastante dessa ideia.

Vou responder a vocês três separadamente porque escrever em uma postagem é uma bolha ilegível.

@Griffork Sem problemas. Às vezes é difícil transmitir tudo corretamente por meio do texto e acho que valeu a pena esclarecer de qualquer maneira.

@dallonf Sua reformulação é exatamente o que quero dizer - estamos na mesma página.

Acho que a diferença entre x?: T e y: ?T estaria nas dicas de ferramentas / uso da função e na digitação e proteção usados.

A declaração como um argumento opcional altera as dicas de ferramentas / uso da função, portanto, fica claro que é opcional:
a?: T não precisa ser passado como um argumento na chamada de função, pode ser deixado em branco
Se uma declaração não tiver ?: ela deve ser passada como um argumento na chamada da função e não pode ser deixada em branco

w: T é um argumento não void obrigatório (com --noImplicitVoid )
não requer guarda

x?: T é um argumento opcional, então o tipo é realmente T | undefined
requer um guarda if (typeof x !== 'undefined') .
Observe o glifo triplo !== para verificação exata de undefined .

y: ?T é um argumento obrigatório e o tipo é realmente T | void
requer um guarda if (y == null) .
Observe o glifo duplo == que corresponde a null e undefined ie void

z?: ?T é um argumento opcional, e o tipo é realmente T | undefined | void que é T | void
requer um guarda if (z == null) .
Observe novamente o glifo duplo == que corresponde a null e undefined ie void

Como acontece com todos os argumentos opcionais, você não pode ter argumentos obrigatórios após os opcionais. Então essa é a diferença. Agora, você também poderia apenas fazer uma guarda null no argumento opcional, e isso também funcionaria, mas a principal diferença é que você não poderia passar o null para a função se você chamou; entretanto, você poderia passar undefined .

Eu acho que todos esses realmente têm um lugar, então não necessariamente depreciaria a sintaxe do argumento opcional atual, mas eu concordo que a forma z é a mais segura.

Editar: redação atualizada e corrigidos alguns erros de digitação.

@ jods4 Concordo com praticamente tudo o que você disse. Não estou tentando minimizar a importância da digitação não void . Estou apenas tentando empurrar uma fase de cada vez. Se a equipe de TS não puder fazer isso mais tarde, pelo menos estaremos melhor, e se eles puderem fazer isso depois de implementar mais verificações e ?T então a missão está cumprida.

Acho que o caso de Arrays é realmente complicado. Você sempre pode fazer algo assim:

`` `.ts
function numToString (x: número) {
return x.toString ();
}
var nums: número [] = Matriz (100);
numToString (nums [0]); // Você está ferrado!

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

Isso realmente não está ajudando muito, já que é inesperado, mas não é um erro no JavaScript real hoje.

A única outra coisa que quero enfatizar é que você pode progredir mesmo com interfaces, é apenas muito mais trabalho e esforço via análise estática do que via sistema de tipos, mas não é muito diferente do que já acontece agora.

Aqui está um exemplo. Vamos supor que --noImplicitVoid esteja desligado

`` `.ts
interface ITransform{
(x: T): U;
}

interface IHaveName {
nome: string;
}

função transformar(x: T, fn: ITransform) {
return fn (x);
}

var nomeado = {
nome: "Foo"
};

var wrongName = {
nome: 1234
};

var namedNull = {
nome: nulo
};

var someFun = (x: IHaveName) => x.name;
var someFunHandlesVoid = (x: IHaveName) => {
if (x! = null && x.name! = null) {
return x.name;
}
retornar "Sem nome";
};

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"

Você está certo de que não pode pegar tudo, mas pode pegar um monte de coisas.

Nota final - qual deve ser o comportamento do acima quando --noImplicitVoid está ativado?

Agora someFun e someFunHandlesVoid são ambos verificados pelo mesmo tipo e produzem as mesmas mensagens de erro que someFun produziu. Mesmo que someFunHandlesVoid lide com o void, chamá-lo com null ou undefined é um erro porque a assinatura indica que não é void . Ele precisaria ser digitado como (x: ?IHaveName) : string para aceitar null ou undefined . Se mudarmos seu tipo, ele continuará a funcionar como antes.

Esta é a parte que é uma alteração importante, mas tudo o que precisamos fazer para consertar foi adicionar um único caractere ? à assinatura de tipo. Podemos até ter outro sinalizador --warnImplicitVoid que faz a mesma coisa que um aviso para que possamos migrar lentamente.

Me sinto um idiota total por fazer isso, mas vou fazer mais uma postagem.

Neste ponto, não tenho certeza do que fazer para continuar. Existe uma ideia melhor? Nós deveríamos:

  • continuar discutindo / especificando como isso deve se comportar?
  • transformar isso em três novas propostas de recursos?

    • Análise Aprimorada

    • Talvez / Tipo de opção ?T

    • --noImplicitVoid opção de compilador

  • enviar ping aos membros da equipe do TypeScript para obter informações?

Estou inclinado para novas propostas e continuar a discussão lá, uma vez que é quase desumano pedir à equipe do TypeScript para acompanhar este tópico considerando sua duração.

@tejacques

  • Você está perdendo um typeof no exemplo de triplo igual a dallonf.
  • Parece que você está faltando ? no exemplo de jods4.

Por mais que eu ache que as coisas devam ficar neste tópico, acho que ele não está mais sendo "observado" mais (talvez mais como visto ocasionalmente). Portanto, a criação de alguns novos tópicos definitivamente aumentaria a tração.
Mas espere alguns dias / uma semana para que as pessoas levantem suas cabeças e forneçam feedback primeiro. Você vai querer que sua proposta seja bem sólida.

Editar: existe marcação memorizada.

Comentar neste tópico é muito fútil neste estágio. Mesmo que seja feita uma proposta que a equipe do TypeScript considere aceitável (tentei fazer isso acima em agosto), não há como eles a encontrarem em meio ao barulho.

O melhor que você pode esperar é que o nível de atenção seja o suficiente para que a equipe do TypeScript apresente sua própria proposta e a implemente. Caso contrário, apenas esqueça e use o Flow.

1 para dividir isso, mas por agora, a opção --noImplicitVoid pode
aguarde a implementação do tipo anulável.

Até agora, quase todos chegamos a um acordo sobre a sintaxe e a semântica de
tipos anuláveis, então, se alguém pudesse escrever uma proposta e implementação
disso, isso seria ouro. Eu tenho uma proposta de um processo semelhante
sobre enums de outros tipos, mas não tive tempo para
implementá-lo devido a outros projetos.

Na quarta-feira, 18 de novembro de 2015, 21:24, Tom Jacques [email protected] escreveu:

Eu me sinto um idiota total por fazer isso, mas vou fazer mais um
publicar.

Neste ponto, não tenho certeza do que fazer para continuar. Existe uma ideia melhor?
Nós deveríamos:

  • continuar discutindo / especificando como isso deve se comportar?
  • transformar isso em três novas propostas de recursos?

    • Análise Aprimorada

    • Talvez / Tipo de opção? T

    • Opção de compilador --noImplicitVoid

  • enviar ping aos membros da equipe do TypeScript para obter informações?

Estou inclinado a novas propostas e continuo a discussão lá desde
é quase desumano pedir à equipe do TypeScript para acompanhar este tópico
considerando quanto tempo é.

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -157928828
.

+1 para a opção --noImplicitNull (proibir atribuição nula e nula).

Tentei atenuar esse problema com um tipo especial Op<A> = A | NullType . Isso parece funcionar muito bem. Veja aqui .

+1 para _-- noImplicitNull_ também POR FAVOR: +1:

+1 para --noImplicitNull

Isso deve ser fechado?

@Gaelan Dado que # 7140 foi mesclado, se você gostaria de registrar uma nova edição dedicada de --noImplicitNull conforme sugerido por algumas pessoas aqui, então provavelmente é seguro fazê-lo agora.

@isiahmeadows Provavelmente seria melhor deixar isto aberto então.

Isso deve ser fechado?

Achamos que https://github.com/Microsoft/TypeScript/issues/2388 é a parte de renomeação deste trabalho. É por isso que ainda não declaramos esse recurso completo.

se você gostaria de registrar um novo problema dedicado para --noImplicitNull como sugerido por algumas pessoas aqui, então provavelmente é seguro fazê-lo agora.

Não tenho certeza se entendi o que é solicitada semântica desta nova bandeira. Eu recomendaria abrir uma nova edição com uma proposta clara.

@mhegazy A ideia postulada anteriormente nesta edição para --noImplicitNull era que tudo tinha que ser explicitamente ?Type ou !Type . IMHO Não acho que vale a pena o boilerplate quando há outro sinalizador que infere não anulável por padrão que o IIRC já foi implementado quando os próprios tipos anuláveis ​​foram.

Fechando agora que o # 7140 e o # 8010 estão ambos mesclados.

Desculpe se eu comento sobre um assunto encerrado, mas não conheço melhor lugar onde perguntar e não acho que valha a pena um novo exemplar se não houver interesse.
Seria viável lidar com nulos implícitos por arquivo?
Tipo, lidar com um monte de arquivos td com noImplicitNull (porque eles vêm de um tipo definitivo e foram concebidos dessa forma), mas lidar com minha fonte como implícitaNull?
Alguém acharia isso útil?

@ massimiliano-mantione, consulte https://github.com/Microsoft/TypeScript/issues/8405

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

Antony-Jones picture Antony-Jones  ·  3Comentários

MartynasZilinskas picture MartynasZilinskas  ·  3Comentários

weswigham picture weswigham  ·  3Comentários

fwanicka picture fwanicka  ·  3Comentários

bgrieder picture bgrieder  ·  3Comentários