Erro de métodos de cadeia de matriz em valores que não são de matriz, o que é diferente de outros métodos de categoria de matriz.
_().pop(); // error
_('').pop(); // error
_().first() // undefined
Isso traz outro problema, devemos converter array-likes para array?
_('hello').first() // 'h'
_('hello').pop() // ?
Isso parece ainda ser um problema - nós o encontramos depois de aumentar nossa versão de 1.8.3 para 1.9.0
Edit: Parece que isso foi corrigido em 1.9.1, em caso afirmativo, esse problema precisa permanecer em aberto?
Este comportamento ainda está presente em 1.10.2.
Um contraste um pouco mais convincente na minha opção, pois ambos envolvem a modificação de um valor no local:
Object.assign(undefined, {a: 1}) // error
_().extend({a: 1}) // undefined
Array.prototype.push.call(undefined, 1) // error
_().push(1) // error
Concordo um pouco que isso é inconsistente.
Vale a pena considerar em que tipo de situação do mundo real isso provavelmente seria encontrado e o que esperar do objeto encapsulado nesses casos.
No meio de uma cadeia, o objeto encapsulado é previsível. Por exemplo, na cadeia abaixo, o objeto encapsulado que é passado para push
será Array
ou undefined
. Por esta razão, eu diria que os métodos wrapper Array
devem implementar uma verificação nula. Isso é facil.
_.chain(something).map(f).push(x) // hoping to push x to an array
Se o objeto encapsulado previsto for mais provável que seja algo diferente de uma matriz, então é sem dúvida um erro do programador. Eu não me importo de lançar uma exceção nesse caso.
_.chain(something).map(f).join('').push(x) // hoping to push x to a string??
No início de uma cadeia, a história pode parecer um pouco diferente à primeira vista, mas vou argumentar que é a mesma e que devemos deixá-la em cheque nulo. Pelo menos deve ser o caso de o programador ter algum motivo para esperar que something
seja um array mutável, pois, caso contrário, não haveria razão para chamar push
:
function giveMeAMutableArraylike(something) {
_.chain(something).push(x)
}
A expectativa de que something
seja um array mutável do tipo pode não ser atendida por vários motivos:
giveMeAMutableArraylike
acabou sendo pago com null
ou undefined
por qualquer motivo. Isso é comparável ao primeiro exemplo de cadeia no meio e pode ser resolvido com a mesma verificação de nulo.giveMeAMutableArraylike
fazer algo que obviamente não pode fazer, ou seja, quebrar o contrato. Isso é comparável ao segundo exemplo da cadeia no meio. Novamente, acho que não há problema em lançar um erro neste caso.something
é um valor simples em vez de um array com um único elemento, ou seja, v
em vez de [v]
. Essa é uma situação comum em muitas APIs JavaScript. Se o contrato de giveMeAMutableArraylike
permitir isso, obviamente é errado lançar um erro, independentemente do que v
seja.Neste último caso, o Underscore não pode saber se giveMeAMutableArraylike
permite elementos simples simples ou não, então cabe ao programador de giveMeAMutableArraylike
implementar seu contrato específico. Não há nada que uma biblioteca como Underscore possa fazer que faça a coisa certa para todos os contratos possíveis:
| Comportamento atual | Envolver em array de elemento único
---|---|---
Contrato permite elemento único nu | _Programador precisa intervir_ | O sublinhado faz a coisa certa automaticamente
Contrato não permite elemento único nu | O sublinhado faz a coisa certa automaticamente | _O programador precisa intervir_
Além disso, como você decide se um valor deve ser encapsulado? Alguns contratos podem querer encapsular qualquer coisa que não seja Array
, enquanto outros podem querer passar arguments
e objetos simples como estão. Novamente, isso é algo que uma biblioteca como Underscore não pode adivinhar em nome do usuário.
Situações mais exóticas também são concebíveis, por exemplo, um contrato que permite objetos semelhantes a arrays imutáveis , como strings, e que os converterá em Array
primeiro. É impossível cobrir todas as variações possíveis. Pelo princípio da menor mudança, simplesmente não há argumento convincente para apoiar alguns contratos em detrimento de contratos que já foram suportados. Há também um forte argumento para manter a implementação tão mínima quanto possível.
Então, acho que a solução certa é inserir apenas uma verificação nula e deixar por isso mesmo. Esta é a única mudança no comportamento de Underscore que parece defensável em todas as situações concebíveis e também é consistente com o comportamento de _.extend
. Uma verificação nula ainda pode ser considerada uma correção de bug, enquanto fazer qualquer coisa além disso rapidamente a transformaria em uma alteração arbitrária.
@jashkenas você poderia iluminar isso? Se você concordar, prepararei um pull request que implementa a verificação nula.
@jgonggrijp — Você pode elaborar em uma frase ou duas o comportamento que deseja implementar com a verificação nula?
É que os métodos de matriz lançarão explicitamente uma exceção quando chamados em um valor nulo? Ou que eles vão noop quando chamados em um valor nulo?
@jashkenas Sim. O comportamento que eu implementaria é um no-op, seguindo o estilo das funções do array Underscore:
Exceto que a verificação length
não seria necessária, então eu apenas inseriria
if (obj == null) return chainresult(this, obj);
entre estas duas linhas:
Além de testes, é claro, e se esses testes revelarem uma necessidade, talvez também uma verificação nula que transforme o método em no-op antes desta linha:
Soa bem para mim!
Comentários muito úteis
Isso traz outro problema, devemos converter array-likes para array?