De temps en temps, lorsque les programmeurs se plaignent d'erreurs / exceptions nulles, quelqu'un demande ce que nous faisons sans null.
J'ai une idée de base de la fraîcheur des types d'options, mais je n'ai pas les connaissances ou les compétences linguistiques pour les exprimer au mieux. Quelle est une grande explication de ce qui suit écrit d'une manière accessible au programmeur moyen que nous pourrions diriger cette personne vers?
- L'opportunité de faire en sorte que les références / pointeurs soient annulables par défaut
- Fonctionnement des types d'options, y compris des stratégies pour faciliter la vérification des cas nuls tels que
- correspondance de motifs et
- compréhensions monadiques
- Solution alternative telle que message mangeant zéro
- (autres aspects que j'ai ratés)
programming-languages
functional-programming
null
nullpointerexception
non-nullable
Roman A. Taycher
la source
la source
Réponses:
Je pense que le résumé succinct des raisons pour lesquelles la nullité est indésirable est que les États dénués de sens ne devraient pas être représentables .
Supposons que je modélise une porte. Il peut être dans l'un des trois états: ouvert, fermé mais déverrouillé et fermé et verrouillé. Maintenant, je pourrais le modéliser selon les
et il est clair comment mapper mes trois états dans ces deux variables booléennes. Mais cela laisse une quatrième état indésirable disponible:
isShut==false && isLocked==true
. Étant donné que les types que j'ai sélectionnés comme représentation admettent cet état, je dois déployer des efforts mentaux pour m'assurer que la classe n'entre jamais dans cet état (peut-être en codant explicitement un invariant). En revanche, si j'utilisais un langage avec des types de données algébriques ou des énumérations vérifiées qui me permettaient de définiralors je pourrais définir
et il n'y a plus de soucis. Le système de types s'assurera qu'il n'y a que trois états possibles pour une instance de
class Door
. C'est ce à quoi les systèmes de types sont bons - excluant explicitement toute une classe d'erreurs au moment de la compilation.Le problème avec
null
est que chaque type de référence obtient cet état supplémentaire dans son espace qui est généralement indésirable. Unestring
variable pourrait être n'importe quelle séquence de caractères, ou ce pourrait être cettenull
valeur supplémentaire folle qui ne correspond pas à mon domaine problématique. UnTriangle
objet a troisPoint
s, qui ont eux-mêmesX
et desY
valeurs, mais malheureusement lePoint
s ou leTriangle
lui-même peuvent être cette valeur nulle folle qui n'a pas de sens pour le domaine graphique dans lequel je travaille. Etc.Lorsque vous avez l'intention de modéliser une valeur possiblement inexistante, vous devez y adhérer explicitement. Si la façon dont j'ai l'intention de modéliser les gens est que chacun
Person
a unFirstName
et unLastName
, mais seulement certaines personnes ont unMiddleName
s, alors je voudrais dire quelque chose commeoù
string
ici est supposé être un type non nul. Ensuite, il n'y a pas d'invariants délicats à établir et pas deNullReferenceException
s inattendus lorsque vous essayez de calculer la longueur du nom d'une personne. Le système de type garantit que tout code traitant desMiddleName
comptes en rend compteNone
, tandis que tout code traitant desFirstName
peut supposer en toute sécurité qu'il y a une valeur.Ainsi, par exemple, en utilisant le type ci-dessus, nous pourrions créer cette fonction idiote:
sans soucis. En revanche, dans une langue avec des références nullables pour des types comme chaîne, puis en supposant
vous finissez par créer des trucs comme
qui explose si l'objet Personne entrant n'a pas l'invariant de tout étant non nul, ou
ou peut-être
en supposant que le
p
premier / dernier est là mais que le milieu peut être nul, ou peut-être que vous faites des vérifications qui lèvent différents types d'exceptions, ou qui sait quoi. Tous ces choix d'implémentation fous et toutes les choses à penser surgissent parce qu'il y a cette stupide valeur représentable dont vous ne voulez pas ou dont vous n'avez pas besoin.Null ajoute généralement une complexité inutile. La complexité est l'ennemi de tous les logiciels, et vous devez vous efforcer de réduire la complexité chaque fois que cela est raisonnable.
(Notez bien que même ces exemples simples sont plus complexes. Même si a
FirstName
ne peut pas l'êtrenull
, astring
peut représenter""
(la chaîne vide), qui n'est probablement pas non plus un nom de personne que nous avons l'intention de modéliser. En tant que tel, même avec des non chaînes nullables, il se peut que nous représentions des valeurs sans signification. Encore une fois, vous pouvez choisir de combattre cela soit via des invariants et du code conditionnel lors de l'exécution, soit en utilisant le système de type (par exemple pour avoir unNonEmptyString
type). ce dernier est peut-être mal avisé (les "bons" types sont souvent "fermés" sur un ensemble d'opérations courantes, et par exempleNonEmptyString
ne sont pas fermés sur.SubString(0,0)
), mais cela démontre plus de points dans l'espace de conception. En fin de compte, dans tout système de type donné, il y a une certaine complexité dont il sera très bon de se débarrasser, et une autre complexité qui est intrinsèquement plus difficile à éliminer. La clé de cette rubrique est que dans presque tous les systèmes de types, le passage de "références annulables par défaut" à "références non nullables par défaut" est presque toujours un simple changement qui rend le système de type beaucoup plus efficace pour lutter contre la complexité et excluant certains types d'erreurs et d'états dénués de sens. Il est donc assez fou que tant de langues continuent de répéter cette erreur encore et encore.)la source
La bonne chose à propos des types d'options n'est pas qu'ils sont facultatifs. C'est que tous les autres types ne le sont pas .
Parfois , nous devons être capables de représenter une sorte d'état "nul". Parfois, nous devons représenter une option "sans valeur" ainsi que les autres valeurs possibles qu'une variable peut prendre. Donc, un langage qui interdit catégoriquement cela va être un peu paralysé.
Mais souvent , nous n'en avons pas besoin, et autoriser un tel état "nul" ne fait que créer de l'ambiguïté et de la confusion: chaque fois que j'accède à une variable de type référence dans .NET, je dois considérer qu'elle peut être nulle .
Souvent, il ne sera jamais réellement nul, car le programmeur structure le code de sorte qu'il ne puisse jamais se produire. Mais le compilateur ne peut pas vérifier cela, et chaque fois que vous le voyez, vous devez vous demander "est-ce que cela peut être nul? Dois-je vérifier null ici?"
Idéalement, dans les nombreux cas où null n'a pas de sens, cela ne devrait pas être autorisé .
C'est difficile à réaliser dans .NET, où presque tout peut être nul. Vous devez vous fier à l'auteur du code que vous appelez pour être à 100% discipliné et cohérent et avoir clairement documenté ce qui peut et ne peut pas être nul, ou vous devez être paranoïaque et tout vérifier .
Cependant, si les types ne peuvent pas être annulés par défaut , vous n'avez pas besoin de vérifier s'ils sont nuls ou non. Vous savez qu'ils ne peuvent jamais être nuls, car le vérificateur de compilateur / type applique cela pour vous.
Et puis nous avons juste besoin d' une porte arrière pour les rares cas où nous faisons besoin de gérer un état nul. Ensuite, un type "option" peut être utilisé. Ensuite, nous autorisons null dans les cas où nous avons pris une décision consciente que nous devons être en mesure de représenter le cas «sans valeur», et dans tous les autres cas, nous savons que la valeur ne sera jamais nulle.
Comme d'autres l'ont mentionné, en C # ou Java par exemple, null peut signifier l'une des deux choses suivantes:
Le deuxième sens doit être préservé, mais le premier doit être entièrement éliminé. Et même le deuxième sens ne devrait pas être celui par défaut. C'est quelque chose que nous pouvons choisir si et quand nous en avons besoin . Mais lorsque nous n'avons pas besoin que quelque chose soit facultatif, nous voulons que le vérificateur de type garantisse qu'il ne sera jamais nul.
la source
Jusqu'à présent, toutes les réponses se concentrent sur les raisons pour lesquelles
null
c'est une mauvaise chose et sur la façon dont c'est un peu pratique si une langue peut garantir que certaines valeurs ne seront jamais nulles.Ils suggèrent ensuite que ce serait une idée assez intéressante si vous imposez la non-nullité pour toutes les valeurs, ce qui peut être fait si vous ajoutez un concept comme
Option
ouMaybe
pour représenter des types qui n'ont pas toujours une valeur définie. Telle est l'approche adoptée par Haskell.Ce sont de bonnes choses! Mais cela n'empêche pas l'utilisation de types explicitement annulables / non null pour obtenir le même effet. Pourquoi, alors, Option est-elle toujours une bonne chose? Après tout, Scala prend en charge les valeurs nullables (est doit , donc il peut travailler avec les bibliothèques Java) mais prend en charge
Options
aussi bien.Q. Quels sont donc les avantages au-delà de la possibilité de supprimer entièrement les valeurs nulles d'une langue?
A. Composition
Si vous effectuez une traduction naïve à partir d'un code sans valeur
au code compatible avec les options
il n'y a pas beaucoup de différence! Mais c'est aussi une façon terrible d'utiliser Options ... Cette approche est beaucoup plus propre:
Ou même:
Lorsque vous commencez à traiter la liste des options, c'est encore mieux. Imaginez que la liste
people
soit elle-même facultative:Comment cela marche-t-il?
Le code correspondant avec des contrôles nuls (ou même des opérateurs elvis?:) Serait douloureusement long. La véritable astuce ici est l'opération flatMap, qui permet la compréhension imbriquée des options et des collections d'une manière que les valeurs nullables ne peuvent jamais atteindre.
la source
flatMap
serait appelé(>>=)
, c'est-à-dire l'opérateur "bind" pour les monades. C'est vrai, les Haskeller aimentflatMap
tellement les choses que nous les mettons dans le logo de notre langue.Option<T>
ne sera jamais, jamais nulle. Malheureusement, Scala est euh, toujours lié à Java :-) (D'un autre côté, si Scala ne jouait pas bien avec Java, qui l'utiliserait? Oo)Puisque les gens semblent manquer:
null
c'est ambigu.La date de naissance d'Alice est
null
. Qu'est-ce que ça veut dire?La date de décès de Bob est
null
. Qu'est-ce que ça veut dire?Une interprétation "raisonnable" pourrait être que la date de naissance d'Alice existe mais est inconnue, alors que la date de décès de Bob n'existe pas (Bob est toujours en vie). Mais pourquoi en sommes-nous arrivés à des réponses différentes?
Un autre problème:
null
c'est un cas de bord.null = null
?nan = nan
?inf = inf
?+0 = -0
?+0/0 = -0/0
?Les réponses sont généralement «oui», «non», «oui», «oui», «non», «oui» respectivement. Les "mathématiciens" fous appellent NaN la "nullité" et disent qu'il se compare à lui-même. SQL traite les valeurs nulles comme n'étant égal à rien (elles se comportent donc comme des NaN). On se demande ce qui se passe lorsque vous essayez de stocker ± ∞, ± 0 et NaN dans la même colonne de base de données (il y a 2 53 NaN, dont la moitié sont "négatifs").
Pour aggraver les choses, les bases de données diffèrent dans la façon dont elles traitent NULL, et la plupart d'entre elles ne sont pas cohérentes (voir Gestion NULL dans SQLite pour un aperçu). C'est assez horrible.
Et maintenant pour l'histoire obligatoire:
J'ai récemment conçu une table de base de données (sqlite3) avec cinq colonnes
a NOT NULL, b, id_a, id_b NOT NULL, timestamp
. Comme il s'agit d'un schéma générique conçu pour résoudre un problème générique pour des applications assez arbitraires, il existe deux contraintes d'unicité:id_a
n'existe que pour la compatibilité avec une conception d'application existante (en partie parce que je n'ai pas trouvé de meilleure solution), et n'est pas utilisé dans la nouvelle application. En raison de la façon dont fonctionne NULL dans SQL, je peux insérer(1, 2, NULL, 3, t)
et(1, 2, NULL, 4, t)
et ne pas violer la première contrainte d' unicité (car(1, 2, NULL) != (1, 2, NULL)
).Cela fonctionne spécifiquement en raison de la façon dont NULL fonctionne dans une contrainte d'unicité sur la plupart des bases de données (probablement pour qu'il soit plus facile de modéliser des situations "réelles", par exemple, deux personnes ne peuvent pas avoir le même numéro de sécurité sociale, mais pas toutes les personnes en ont un).
FWIW, sans invoquer d'abord un comportement non défini, les références C ++ ne peuvent pas "pointer vers" null, et il n'est pas possible de construire une classe avec des variables de membre de référence non initialisées (si une exception est levée, la construction échoue).
Sidenote: Parfois, vous pouvez souhaiter des pointeurs s'excluant mutuellement (c'est-à-dire qu'un seul d'entre eux peut être non NULL), par exemple dans un iOS hypothétique
type DialogState = NotShown | ShowingActionSheet UIActionSheet | ShowingAlertView UIAlertView | Dismissed
. Au lieu de cela, je suis obligé de faire des trucs commeassert((bool)actionSheet + (bool)alertView == 1)
.la source
assert(actionSheet ^ alertView)
? Ou votre langue ne peut-elle pas booster?L'opportunité de disposer de références / pointeurs peut être annulée par défaut.
Je ne pense pas que ce soit le principal problème avec les valeurs nulles, le principal problème avec les valeurs nulles est qu'elles peuvent signifier deux choses:
Les langages qui prennent en charge les types d'options interdisent ou découragent également généralement l'utilisation de variables non initialisées.
Fonctionnement des types d'options, y compris des stratégies pour faciliter la vérification des cas nuls tels que la correspondance de modèles.
Pour être efficaces, les types d'options doivent être pris en charge directement dans la langue. Sinon, il faut beaucoup de code de chaudière pour les simuler. L'appariement de modèles et l'inférence de type sont deux fonctionnalités clés du langage qui facilitent l'utilisation des types d'options. Par exemple:
En F #:
Cependant, dans un langage comme Java sans prise en charge directe des types d'options, nous aurions quelque chose comme:
Solution alternative telle que message mangeant nul
Le «message mangeant zéro» d'Objective-C n'est pas tant une solution qu'une tentative d'alléger le mal de tête de la vérification nulle. Fondamentalement, au lieu de lever une exception d'exécution lorsque vous essayez d'appeler une méthode sur un objet null, l'expression est évaluée à null elle-même. Suspendre l'incrédulité, c'est comme si chaque méthode d'instance commençait par
if (this == null) return null;
. Mais il y a ensuite une perte d'informations: vous ne savez pas si la méthode a retourné null parce qu'elle est une valeur de retour valide, ou parce que l'objet est réellement nul. Cela ressemble beaucoup à la déglutition d'exceptions et ne fait aucun progrès pour résoudre les problèmes avec null décrits ci-dessus.la source
L'Assemblée nous a apporté des adresses également connues sous le nom de pointeurs non typés. C les a mappés directement en tant que pointeurs typés mais a introduit le null d'Algol en tant que valeur de pointeur unique, compatible avec tous les pointeurs typés. Le gros problème avec null en C est que puisque chaque pointeur peut être nul, on ne peut jamais utiliser un pointeur en toute sécurité sans vérification manuelle.
Dans les langages de niveau supérieur, avoir null est gênant car il véhicule vraiment deux notions distinctes:
Avoir des variables indéfinies est à peu près inutile et donne lieu à un comportement indéfini chaque fois qu'elles se produisent. Je suppose que tout le monde conviendra qu'il faut éviter à tout prix d'avoir des choses non définies.
Le second cas est facultatif et est mieux fourni explicitement, par exemple avec un type d'option .
Disons que nous sommes dans une entreprise de transport et que nous devons créer une application pour aider à créer un horaire pour nos chauffeurs. Pour chaque conducteur, nous stockons quelques informations telles que: les permis de conduire dont il dispose et le numéro de téléphone à appeler en cas d'urgence.
En C on pourrait avoir:
Comme vous le constatez, dans tout traitement de notre liste de pilotes, nous devrons rechercher des pointeurs nuls. Le compilateur ne vous aidera pas, la sécurité du programme dépend de vos épaules.
Dans OCaml, le même code ressemblerait à ceci:
Disons maintenant que nous voulons imprimer les noms de tous les conducteurs ainsi que leurs numéros de permis de camion.
En C:
Dans OCaml, ce serait:
Comme vous pouvez le voir dans cet exemple trivial, il n'y a rien de compliqué dans la version sûre:
Alors qu'en C, vous auriez pu oublier un échec et un boom nul ...
Remarque: ces exemples de code n'ont pas été compilés, mais j'espère que vous avez eu les idées.
la source
NULL
que dans "référence qui peut ne rien indiquer" a été inventé pour une langue algol (Wikipedia est d'accord, voir en.wikipedia.org/wiki/Null_pointer#Null_pointer ). Mais bien sûr, il est probable que les programmeurs d'assemblage aient initialisé leurs pointeurs à une adresse non valide (lire: Null = 0).Microsoft Research a un projet intéressant appelé
Il s'agit d'une extension C # avec un type non nul et un mécanisme pour vérifier que vos objets ne sont pas nuls , bien que, à mon humble avis, l'application du principe de conception par contrat puisse être plus appropriée et plus utile pour de nombreuses situations gênantes causées par des références nulles.
la source
Venant de l'arrière-plan .NET, j'ai toujours pensé que null avait un point, c'est utile. Jusqu'à ce que je connaisse les structures et à quel point il était facile de travailler avec elles en évitant beaucoup de code standard. Tony Hoare s'exprimant à QCon Londres en 2009, s'est excusé d'avoir inventé la référence nulle . Pour le citer:
Voir aussi cette question chez les programmeurs
la source
Robert Nystrom propose un bel article ici:
http://journal.stuffwithstuff.com/2010/08/23/void-null-maybe-and-nothing/
décrivant son processus de réflexion lors de l'ajout de la prise en charge des absences et des échecs à son langage de programmation Magpie .
la source
J'ai toujours considéré Null (ou nil) comme l'absence d'une valeur .
Parfois vous le voulez, parfois non. Cela dépend du domaine avec lequel vous travaillez. Si l'absence est significative: pas de deuxième prénom, votre candidature peut agir en conséquence. D'un autre côté, si la valeur nulle ne doit pas être là: le prénom est nul, alors le développeur reçoit l'appel proverbial à 2 heures du matin.
J'ai également vu du code surchargé et trop compliqué avec des vérifications de null. Pour moi, cela signifie l'une des deux choses suivantes:
a) un bug plus haut dans l'arborescence des applications
b) une conception incorrecte / incomplète
Du côté positif - Null est probablement l'une des notions les plus utiles pour vérifier si quelque chose est absent, et les langues sans le concept de null finiront par compliquer les choses quand il sera temps de faire la validation des données. Dans ce cas, si une nouvelle variable n'est pas initialisée, lesdites langues définiront généralement les variables sur une chaîne vide, 0 ou une collection vide. Cependant, si une chaîne vide ou 0 ou une collection vide sont des valeurs valides pour votre application, vous avez un problème.
Parfois, cela a été contourné en inventant des valeurs spéciales / étranges pour que les champs représentent un état non initialisé. Mais que se passe-t-il alors lorsque la valeur spéciale est entrée par un utilisateur bien intentionné? Et n'entrons pas dans le pétrin que cela fera des routines de validation des données. Si le langage soutenait le concept nul, toutes les préoccupations disparaîtraient.
la source
Les langages vectoriels peuvent parfois s'en tirer sans avoir de null.
Le vecteur vide sert de null typé dans ce cas.
la source