Erreur des méthodes de chaîne de tableau sur les valeurs non-tableau, contrairement aux autres méthodes de catégorie de tableau.
_().pop(); // error
_('').pop(); // error
_().first() // undefined
Cela soulève un autre problème, devrions-nous convertir les array-likes en array ?
_('hello').first() // 'h'
_('hello').pop() // ?
Cela semble toujours être un problème - nous l'avons rencontré après avoir fait passer notre version de 1.8.3 à 1.9.0
Edit : Il semble que cela ait été corrigé dans la version 1.9.1. Si tel est le cas, ce problème doit-il rester ouvert ?
Ce comportement est toujours présent dans 1.10.2.
Un contraste un peu plus convaincant dans mon option, puisque les deux impliquent de modifier une valeur en place :
Object.assign(undefined, {a: 1}) // error
_().extend({a: 1}) // undefined
Array.prototype.push.call(undefined, 1) // error
_().push(1) // error
Je suis un peu d'accord que c'est incohérent.
Il convient de considérer dans quel type de situation réelle cela se produirait le plus probablement et à quoi s'attendre de l'objet enveloppé dans de tels cas.
Au milieu d'une chaîne, l'objet enveloppé est prévisible. Par exemple, dans la chaîne ci-dessous, l'objet enveloppé qui est passé à push
sera soit Array
soit undefined
. Pour cette raison, je dirais que les méthodes wrapper Array
devraient implémenter une vérification nulle. C'est facile.
_.chain(something).map(f).push(x) // hoping to push x to an array
Si l'objet enveloppé prédit est plus susceptible d'être autre chose qu'un tableau, il s'agit sans doute d'une erreur du programmeur. Cela ne me dérange pas de lancer une exception dans ce cas.
_.chain(something).map(f).join('').push(x) // hoping to push x to a string??
Au début d'une chaîne, l'histoire peut sembler un peu différente à première vue, mais je soutiendrai que c'est la même chose et que nous devrions la laisser à un contrôle nul. Au moins, le programmeur doit avoir une raison de s'attendre à ce que something
soit un tableau mutable, car sinon, il n'y aurait aucune raison d'appeler push
:
function giveMeAMutableArraylike(something) {
_.chain(something).push(x)
}
L'attente selon laquelle something
est un tableau mutable pourrait ne pas être satisfaite pour diverses raisons :
giveMeAMutableArraylike
a fini par être payé avec null
ou undefined
pour une raison quelconque. Ceci est comparable au premier exemple de chaîne au milieu et peut être résolu avec le même contrôle nul.giveMeAMutableArraylike
quelque chose qu'il ne peut manifestement pas faire, c'est-à-dire rompre le contrat. Ceci est comparable au deuxième exemple de chaîne au milieu. Encore une fois, je pense que c'est bien de lancer une erreur dans ce cas.something
est une valeur nue au lieu d'un tableau avec un seul élément, c'est-à-dire v
au lieu de [v]
. Il s'agit d'une situation courante dans de nombreuses API JavaScript. Si le contrat de giveMeAMutableArraylike
permet, il est évidemment faux de lancer une erreur indépendamment de ce que v
se trouve être.Dans ce dernier cas, Underscore ne peut pas savoir si giveMeAMutableArraylike
autorise ou non les éléments nus simples, il appartient donc au programmeur de giveMeAMutableArraylike
d'implémenter son contrat particulier. Il n'y a rien qu'une bibliothèque comme Underscore puisse faire qui fera la bonne chose pour tous les contrats possibles :
| Comportement actuel | Envelopper dans un tableau à un seul élément
---|---|---
Le contrat autorise un seul élément nu | _Le programmeur doit intervenir_ | Underscore fait automatiquement ce qu'il faut
Le contrat n'autorise pas un seul élément nu | Underscore fait automatiquement ce qu'il faut | _Le programmeur doit intervenir_
De plus, comment décidez-vous si une valeur doit être enveloppée ? Certains contrats peuvent vouloir envelopper tout ce qui n'est pas un Array
, tandis que d'autres peuvent vouloir passer des arguments
et des objets simples tels quels. Encore une fois, c'est quelque chose qu'une bibliothèque comme Underscore ne peut pas deviner au nom de l'utilisateur.
Des situations plus exotiques sont également envisageables, par exemple un contrat qui autorise des objets immuables de type tableau tels que des chaînes et qui les convertira d'abord en Array
. Il est impossible de couvrir toutes les variantes possibles. Selon le principe du moindre changement, il n'y a tout simplement aucun argument convaincant pour soutenir certains contrats au détriment de contrats qui étaient déjà pris en charge. Il existe également de solides arguments en faveur d'une mise en œuvre aussi minime que possible.
Je pense donc que la bonne solution consiste à insérer simplement une vérification nulle et à en rester là. C'est le seul changement au comportement de Underscore qui semble défendable dans toutes les situations imaginables et il est également cohérent avec le comportement de _.extend
. Une vérification nulle pourrait toujours être considérée comme une correction de bogue, tandis que faire autre chose que cela la transformerait rapidement en un changement cassant arbitraire.
@jashkenas pourriez-vous éclairer cela ? Si vous êtes d'accord, je préparerai une demande d'extraction qui implémente la vérification nulle.
@jgonggrijp — Pouvez-vous développer en une phrase ou deux le comportement que vous souhaitez implémenter avec la vérification null ?
Est-ce que les méthodes de tableau lèveront explicitement une exception lorsqu'elles seront appelées sur une valeur nulle ? Ou qu'ils vont noop lorsqu'ils sont appelés sur une valeur nulle ?
@jashkenas Oui. Le comportement que j'implémenterais est un no-op, suivant le style des fonctions de tableau Underscore :
Sauf que la vérification length
ne serait pas nécessaire, donc je voudrais simplement insérer
if (obj == null) return chainresult(this, obj);
entre ces deux lignes :
Plus des tests bien sûr, et si ces tests révèlent un besoin, peut-être aussi une vérification null qui transforme la méthode en no-op avant cette ligne :
Ça me va bien !
Commentaire le plus utile
Cela soulève un autre problème, devrions-nous convertir les array-likes en array ?