J'ai lu sur la (non) commodité d'avoir null
au lieu de (par exemple) Maybe
. Après avoir lu cet article , je suis convaincu qu'il serait préférable de l'utiliserMaybe
(ou quelque chose de similaire). Cependant, je suis surpris de voir que tous les langages de programmation impératifs ou orientés objet "bien connus" utilisent toujours null
(ce qui permet un accès non contrôlé à des types qui peuvent représenter une valeur "rien"), et qui Maybe
est principalement utilisé dans les langages de programmation fonctionnels.
Par exemple, regardez le code C # suivant:
void doSomething(string username)
{
// Check that username is not null
// Do something
}
Quelque chose sent mauvais ici ... Pourquoi devrions-nous vérifier si l'argument est nul? Ne devrions-nous pas supposer que chaque variable contient une référence à un objet? Comme vous pouvez le voir, le problème est que, par définition, presque toutes les variables peuvent contenir une référence nulle. Et si nous pouvions décider quelles variables sont "nulles" et lesquelles ne le sont pas? Cela nous permettrait d'économiser beaucoup d'efforts lors du débogage et de la recherche d'une "NullReferenceException". Imaginez que, par défaut, aucun type ne puisse contenir une référence nulle . Au lieu de cela, vous déclareriez explicitement qu'une variable peut contenir une référence nulle , uniquement si vous en avez vraiment besoin. Telle est l'idée derrière Peut-être. Si vous avez une fonction qui échoue dans certains cas (par exemple la division par zéro), vous pouvez retourner unMaybe<int>
, indiquant explicitement que le résultat peut être un entier, mais aussi rien! C'est une des raisons de préférer Peut-être au lieu de null. Si vous êtes intéressé par plus d'exemples, je vous suggère de lire cet article .
Les faits sont que, malgré les inconvénients de rendre la plupart des types annulables par défaut, la plupart des langages de programmation OO le font. C'est pourquoi je m'interroge sur:
- Quel genre d'arguments auriez-vous à implémenter
null
dans votre langage de programmation à la placeMaybe
? Y a-t-il des raisons ou s'agit-il simplement de «bagages historiques»?
Veuillez vous assurer de bien comprendre la différence entre null et peut-être avant de répondre à cette question.
la source
null
ou son concept n'existe pas (IIRC Haskell en est un exemple).null
s depuis longtemps. Ce n'est pas facile de laisser tomber ça.Réponses:
Je pense que c'est avant tout un bagage historique.
Le langage le plus important et le plus ancien avec
null
est le C et le C ++. Mais ici,null
ça a du sens. Les pointeurs sont encore un concept assez numérique et de bas niveau. Et comment quelqu'un d'autre a dit, dans l'état d'esprit des programmeurs C et C ++, avoir à dire explicitement que le pointeur peut êtrenull
n'a pas de sens.Java vient en deuxième position. Considérant que les développeurs Java essayaient de se rapprocher du C ++, afin qu'ils puissent faire une transition du C ++ vers Java plus simple, ils ne voulaient probablement pas jouer avec un tel concept de base du langage. En outre, l'implémentation explicite
null
nécessiterait beaucoup plus d'efforts, car vous devez vérifier si la référence non nulle est réellement définie correctement après l'initialisation.Toutes les autres langues sont identiques à Java. Ils copient généralement la façon dont C ++ ou Java le font, et compte tenu de la façon dont le concept de base
null
des types implicites de référence est, il devient vraiment difficile de concevoir un langage utilisant explicitementnull
.la source
null
ne serait pas un gros changement, je pense.std::string
ne peut pas êtrenull
.int&
ne peut pas êtrenull
. Uneint*
boîte et C ++ autorisent un accès non contrôlé pour deux raisons: 1. parce que C l'a fait et 2. parce que vous êtes censé comprendre ce que vous faites lorsque vous utilisez des pointeurs bruts en C ++.En fait,
null
c'est une excellente idée. Étant donné un pointeur, nous voulons désigner que ce pointeur ne fait pas référence à une valeur valide. Nous prenons donc un emplacement de mémoire, le déclarons invalide et respectons cette convention (une convention parfois appliquée avec des erreurs de segmentation). Maintenant, chaque fois que j'ai un pointeur, je peux vérifier s'il contientNothing
(ptr == null
) ouSome(value)
(ptr != null
,value = *ptr
). Je veux que vous compreniez que cela équivaut à unMaybe
type.Les problèmes avec ceci sont:
Dans de nombreux langages, le système de type n'aide pas ici à garantir une référence non nulle.
Il s'agit d'un bagage historique, car de nombreux langages impératifs ou POO traditionnels n'ont eu que des avancées incrémentielles dans leurs systèmes de types par rapport aux prédécesseurs. Les petits changements ont l'avantage que les nouvelles langues sont plus faciles à apprendre. C # est un langage courant qui a introduit des outils au niveau du langage pour mieux gérer les valeurs nulles.
Les concepteurs d'API peuvent revenir
null
en cas d'échec, mais pas une référence à la réalité même en cas de succès. Souvent, la chose (sans référence) est retournée directement. Cet aplatissement d'un niveau de pointeur rend impossible l'utilisationnull
comme valeur.C'est juste de la paresse du côté du concepteur et ne peut pas être aidé sans imposer une imbrication appropriée avec un système de type approprié. Certaines personnes peuvent également essayer de justifier cela par des considérations de performances ou par l'existence de vérifications facultatives (une collection peut renvoyer
null
ou l'élément lui-même, mais également fournir unecontains
méthode).À Haskell, il y a une vue nette sur le
Maybe
type en tant que monade. Cela facilite la composition des transformations sur la valeur contenue.D'un autre côté, les langages de bas niveau comme C traitent à peine les tableaux comme un type distinct, donc je ne sais pas trop à quoi nous nous attendons. Dans les langages POO avec polymorphisme paramétré, un
Maybe
type vérifié à l'exécution est plutôt trivial à implémenter.la source
null
moins qu'elles ne le faisaient dans le passé?null
s » - je parle desNullable
types et de l'??
opérateur par défaut. Il ne résout pas le problème en ce moment en présence de code existant, mais il est un pas vers un avenir meilleur.Nullable
.Ma compréhension est que
null
c'était une construction nécessaire afin d'abstraire les langages de programmation de l'assemblage. 1 Les programmeurs devaient avoir la capacité d'indiquer qu'un pointeur ou une valeur de registre étaitnot a valid value
et estnull
devenu le terme courant pour cette signification.En renforçant le point qui
null
n'est qu'une convention pour représenter un concept, la valeur réelle pournull
utilisé peut / peut varier en fonction du langage de programmation et de la plate-forme.Si vous conceviez un nouveau langage et que vous vouliez éviter
null
mais utiliser à lamaybe
place, j'encouragerais un terme plus descriptif tel quenot a valid value
ounavv
pour indiquer l'intention. Mais le nom de cette non-valeur est un concept distinct de si vous devez autoriser les non-valeurs à exister même dans votre langue.Avant de pouvoir décider de l'un de ces deux points, vous devez définir ce que la signification de
maybe
signifierait pour votre système. Vous pouvez trouver que c'est juste un renommage denull
la signification denot a valid value
ou vous pouvez trouver qu'il a une sémantique différente pour votre langue.De même, la décision de vérifier l'accès par rapport à l'
null
accès ou à la référence est une autre décision de conception de votre langue.Pour fournir un peu d'histoire,
C
supposait implicitement que les programmeurs comprenaient ce qu'ils tentaient de faire lorsqu'ils manipulaient la mémoire. Comme il s'agissait d'une abstraction supérieure à l'assemblage et aux langages impératifs qui l'ont précédé, je me risquerais à penser que l'idée de protéger le programmeur d'une référence incorrecte ne lui était pas venue à l'esprit.Je crois que certains compilateurs ou leurs outils supplémentaires peuvent fournir une mesure de vérification contre l'accès au pointeur non valide. D'autres ont donc noté ce problème potentiel et pris des mesures pour s'en protéger.
Que vous puissiez ou non l'autoriser dépend de ce que vous voulez que votre langue accomplisse et du degré de responsabilité que vous souhaitez imposer aux utilisateurs de votre langue. Cela dépend également de votre capacité à créer un compilateur pour restreindre ce type de comportement.
Alors pour répondre à vos questions:
"Quel genre d'arguments ..." - Eh bien, cela dépend de ce que vous voulez que le langage fasse. Si vous souhaitez simuler l'accès au bare-metal, vous pouvez l'autoriser.
"Est-ce juste un bagage historique?" Peut-être, peut-être pas.
null
avait certainement / a un sens pour un certain nombre de langues et contribue à l'expression de ces langues. Un précédent historique peut avoir affecté des langues plus récentes et leur acceptation,null
mais c'est un peu beaucoup de faire signe de la main et de déclarer le concept de bagage historique inutile.1 Voir cet article Wikipédia bien que Hoare soit reconnu pour les valeurs nulles et les langages orientés objet. Je crois que les langues impératives ont progressé le long d'un arbre généalogique différent de celui d'Algol.
la source
null
).object o = null; o.ToString();
compile très bien pour moi, sans erreurs ni avertissements dans VS2012. ReSharper se plaint de cela, mais ce n'est pas le compilateur.Si vous regardez les exemples de l'article que vous avez cité, la plupart du temps, l'utilisation
Maybe
ne raccourcit pas le code. Cela n'empêche pas la nécessité de vérifierNothing
. La seule différence est qu'il vous rappelle de le faire via le système de type.Remarque, je dis "rappelez-vous", pas forcer. Les programmeurs sont paresseux. Si un programmeur est convaincu qu'une valeur ne peut pas l'être
Nothing
, il va déréférencer leMaybe
sans le vérifier, tout comme il déréférence un pointeur nul maintenant. Le résultat final est que vous convertissez une exception de pointeur nul en une exception "peut-être vide déréférencé".Le même principe de la nature humaine s'applique dans d'autres domaines où les langages de programmation tentent de forcer les programmeurs à faire quelque chose. Par exemple, les concepteurs Java ont essayé de forcer les gens à gérer la plupart des exceptions, ce qui a donné lieu à un grand nombre de références qui ignorent silencieusement ou propagent aveuglément les exceptions.
Ce qui
Maybe
est bien, c'est quand beaucoup de décisions sont prises via la correspondance de motifs et le polymorphisme au lieu de vérifications explicites. Par exemple, vous pouvez créer des fonctions distinctesprocessData(Some<T>)
etprocessData(Nothing<T>)
, ce que vous ne pouvez pas fairenull
. Vous déplacez automatiquement votre gestion des erreurs vers une fonction distincte, ce qui est très souhaitable dans la programmation fonctionnelle où les fonctions sont transmises et évaluées paresseusement plutôt que d'être toujours appelées de manière descendante. Dans la POO, la façon préférée de découpler votre code de gestion des erreurs est avec des exceptions.la source
const
, mais de le rendre facultatif. Certains types de code de niveau inférieur, comme les listes liées par exemple, seraient très ennuyeux à implémenter avec des objets non nullables.map :: Maybe a -> (a -> b)
et êtrebind :: Maybe a -> (a -> Maybe b)
défini dessus, vous pouvez donc continuer et effectuer d'autres calculs pour la plupart avec une refonte à l'aide d'une instruction if else. EtgetValueOrDefault :: Maybe a -> (() -> a) -> a
vous permet de gérer le cas nullable. C'est beaucoup plus élégant que la correspondance de motifs sur leMaybe a
explicitement.Maybe
est une façon très fonctionnelle de penser un problème - il y a une chose, et elle peut ou non avoir une valeur définie. Dans un sens orienté objet, cependant, nous remplaçons cette idée d'une chose (qu'elle ait ou non une valeur) par un objet. De toute évidence, un objet a une valeur. Si ce n'est pas le cas, nous disons que l'objet l'estnull
, mais ce que nous voulons vraiment dire, c'est qu'il n'y a aucun objet du tout. La référence que nous avons à l'objet ne signifie rien. La traductionMaybe
en un concept OO n'a rien de nouveau - en fait, cela crée simplement un code plus encombré. Vous devez toujours avoir une référence nulle pour la valeur duMaybe<T>
. Vous devez toujours faire des vérifications nulles (en fait, vous devez faire beaucoup plus de vérifications nulles, encombrant votre code), même si elles sont maintenant appelées "peut-être des vérifications". Bien sûr, vous écrirez du code plus robuste comme le prétend l'auteur, mais je dirais que ce n'est que le cas parce que vous avez rendu le langage beaucoup plus abstrait et obtus, nécessitant un niveau de travail inutile dans la plupart des cas. Je prendrais volontiers une NullReferenceException de temps en temps plutôt que de traiter du code spaghetti en faisant une vérification Peut-être à chaque fois que j'accède à une nouvelle variable.la source
Maybe<T>
doit autorisernull
comme valeur, car la valeur peut ne pas exister. Si je l'aiMaybe<MyClass>
, et qu'il n'a pas de valeur, le champ de valeur doit contenir une référence nulle. Il n'y a rien d'autre pour que ce soit vraiment sûr.Maybe
pourrait être une classe abstraite, avec deux classes qui en héritent:Some
(qui a un champ pour la valeur) etNone
(qui n'a pas ce champ). De cette façon, la valeur n'est jamaisnull
.Le concept de
null
remonte facilement à C, mais ce n'est pas là que réside le problème.Mon langage de tous les jours est C # et je garderais
null
une différence. C # a deux types de types, valeurs et références . Les valeurs ne peuvent jamais existernull
, mais il y a des moments où j'aimerais pouvoir exprimer qu'aucune valeur ne convient parfaitement. Pour ce faire, C # utilise desNullable
types, ceint
serait donc la valeur etint?
la valeur nullable. C'est ainsi que je pense que les types de référence devraient également fonctionner.Voir aussi: La référence nulle n'est peut-être pas une erreur :
la source
Je pense que c'est parce que la programmation fonctionnelle se préoccupe beaucoup des types , en particulier des types qui combinent d'autres types (tuples, fonctions comme types de première classe, monades, etc.) que la programmation orientée objet (ou du moins initialement).
Je pense que les versions modernes des langages de programmation dont vous parlez (C ++, C #, Java) sont toutes basées sur des langages qui n'avaient aucune forme de programmation générique (C, C # 1.0, Java 1). Sans cela, vous pouvez toujours faire une sorte de différence entre les objets nullable et non nullable dans le langage (comme les références C ++, qui ne peuvent pas être
null
, mais sont également limitées), mais c'est beaucoup moins naturel.la source
Je pense que la raison fondamentale est que relativement peu de contrôles nuls sont nécessaires pour rendre un programme "sûr" contre la corruption de données. Si un programme essaie d'utiliser le contenu d'un élément de tableau ou d'un autre emplacement de stockage qui est censé avoir été écrit avec une référence valide mais ne l'a pas été, le meilleur cas est qu'une exception soit levée. Idéalement, l'exception indiquera exactement où le problème s'est produit, mais ce qui importe, c'est qu'une sorte d'exception soit levée avant que la référence nulle ne soit stockée quelque part, ce qui pourrait endommager les données. À moins qu'une méthode ne stocke un objet sans essayer de l'utiliser d'une manière ou d'une autre, une tentative d'utilisation d'un objet constituera - en soi - une sorte de «contrôle nul».
Si l'on veut s'assurer qu'une référence nulle qui apparaît là où elle ne devrait pas provoquer une exception particulière autre que
NullReferenceException
, il sera souvent nécessaire d'inclure des vérifications nulles partout. D'un autre côté, le simple fait de garantir qu'une exception se produira avant qu'une référence nulle puisse causer des "dommages" au-delà de tout ce qui a déjà été fait nécessitera souvent relativement peu de tests - les tests ne seront généralement requis que dans les cas où un objet stockerait une référence sans essayer de l'utiliser, et soit la référence nulle écraserait une référence valide, soit elle ferait en sorte qu'un autre code interpréterait mal d'autres aspects de l'état du programme. De telles situations existent, mais ne sont pas si courantes; la plupart des références nulles accidentelles seront capturées très rapidementque l'on vérifie ou non .la source
"
Maybe
," comme écrit, est une construction de niveau supérieur à null. En utilisant plus de mots pour le définir, Peut-être est "un pointeur vers une chose ou un pointeur vers rien, mais le compilateur n'a pas encore reçu suffisamment d'informations pour déterminer laquelle." Cela vous oblige à vérifier explicitement chaque valeur en permanence, sauf si vous créez une spécification de compilateur suffisamment intelligente pour suivre le code que vous écrivez.Vous pouvez facilement réaliser une implémentation de Maybe avec un langage qui a des valeurs nulles. C ++ en a un sous la forme de
boost::optional<T>
. Faire l'équivalent de null avec Maybe est très difficile. En particulier, si j'ai unMaybe<Just<T>>
, je ne peux pas l'affecter à null (car un tel concept n'existe pas), tandis qu'unT**
dans une langue avec null est très facile à affecter à null. Cela oblige à utiliserMaybe<Maybe<T>>
, ce qui est totalement valide, mais vous obligera à faire beaucoup plus de vérifications pour utiliser cet objet.Certains langages fonctionnels utilisent Peut-être parce que null nécessite un comportement non défini ou une gestion des exceptions, aucun des deux n'est un concept facile à mapper dans les syntaxes du langage fonctionnel. Peut-être remplit-il mieux le rôle dans de telles situations fonctionnelles, mais dans les langages procéduraux, null est roi. Ce n'est pas une question de bien ou de mal, mais simplement de savoir ce qui permet de dire plus facilement à l'ordinateur ce que vous voulez qu'il fasse.
la source