Est-il sensé d'avoir à la fois le concept de «nul» et de «peut-être»?

11

Lors de la création d'un client pour une API Web en C #, j'ai rencontré un problème concernant nullla valeur où cela représenterait deux choses différentes:

  • rien , par exemple un foopeut ou peut ne pas avoir unbar
  • inconnu : par défaut, la réponse de l'API ne comprend qu'un sous-ensemble de propriétés, vous devez indiquer les propriétés supplémentaires que vous souhaitez. Donc inconnu signifie que la propriété n'a pas été demandée à l'API.

Après quelques recherches, j'ai découvert le type Peut-être (ou Option), comment il est utilisé dans les langages fonctionnels et comment il "résout" les problèmes de déréférencement nul en forçant l'utilisateur à réfléchir à l'absence possible d'une valeur. Cependant, toutes les ressources que j'ai rencontrées parlaient de remplacer null par Maybe . J'ai trouvé quelques mentions de la logique à trois valeurs , mais je ne la comprends pas complètement et la plupart du temps, sa mention était dans le contexte de "c'est une mauvaise chose".

Je me demande maintenant s'il est logique d'avoir à la fois le concept de nul et peut - être , pour représenter respectivement inconnu et rien . Est-ce la logique à trois valeurs sur laquelle je lis ou a-t-elle un autre nom? Ou est-ce la manière prévue d'imbriquer un peut-être dans un peut-être?

Stijn
la source
9
Cela ne fait pas de sanse d'avoir null. C'est une idée complètement brisée.
Andrej Bauer
7
Ne faites pas un peut-être-foo qui a une sémantique différente d'un peut-être-foo. Peut - être est une monade , et l' une des lois monade est que M M xet M xdevrait avoir la même sémantique.
Eric Lippert
2
Vous pourriez envisager d'examiner la conception des premières versions de Visual Basic, qui avaient Nothing (référence à aucun objet), Null (sémantique nulle de la base de données), Empty (une variable non initialisée) et Missing (un paramètre facultatif n'a pas été transmis). Cette conception était compliquée et à bien des égards incohérente, mais il y a une raison pour laquelle ces concepts n'ont pas été confondus les uns avec les autres.
Eric Lippert
3
Oui, je n'utiliserais peut-être pas non plus. Mais, en fait, tout comme Maybe ac'est la même chose , sémantiquement, c'est la même chose , et isomorphe à taper à partir de la réponse @Andej. Vous pouvez également définir votre propre instance de monade pour ce type et donc utiliser différents combinateurs de monade. une+1Maybe Maybe aune+1+1UserInput a
Euge
12
@EricLippert: il est faux que " M (M x)et M xdevrait avoir la même sémantique". Prenez M = Listpar exemple: les listes de listes ne sont pas la même chose que les listes. Quand Mest une monade, il y a une transformation (à savoir la multiplication de la monade) à partir M (M x)de M xlaquelle explique la relation entre elles, mais elles n'ont pas "la même sémantique".
Andrej Bauer

Réponses:

14

La nullvaleur par défaut présente partout n'est qu'une idée vraiment cassée , alors oubliez cela.

Vous devez toujours avoir exactement le concept qui décrit le mieux vos données réelles. Si vous avez besoin d'un type qui indique "inconnu", "rien" et "valeur", vous devriez l'avoir précisément. Mais si cela ne correspond pas à vos besoins réels, vous ne devriez pas le faire. C'est une bonne idée de voir ce que les autres utilisent et ce qu'ils ont proposé, mais vous n'avez pas à les suivre aveuglément.

Les personnes qui conçoivent des bibliothèques standard essaient de deviner les modèles d'utilisation courants, et ils le font généralement bien, mais si vous avez besoin d'une chose spécifique, vous devez la définir vous-même. Par exemple, dans Haskell, vous pouvez définir:

data UnknownNothing a =
     Unknown
   | Nothing
   | Value a

Il se peut même que vous utilisiez quelque chose de plus descriptif et plus facile à retenir:

data UserInput a =
     Unknown
   | NotGiven
   | Answer a

Vous pouvez définir 10 de ces types à utiliser pour différents scénarios. L'inconvénient est que vous n'aurez pas de fonctions de bibliothèque préexistantes disponibles (comme celles de Maybe), mais cela se révèle généralement être un détail. Ce n'est pas si difficile d'ajouter vos propres fonctionnalités.

Andrej Bauer
la source
C'est parfaitement logique quand vous le voyez comme ça. Je suis surtout intéressé par les idées, le support de bibliothèque existant n'est qu'un bonus :)
Stijn
Si seulement la barrière à la création de tels types était plus faible dans de nombreuses langues. : /
Raphael
Si seulement les gens arrêtaient d'utiliser les langues des années 60 (ou leurs réincarnations des années 80).
Andrej Bauer
@AndrejBauer: quoi? mais alors les théoriciens n'auraient rien à redire!
Yttrill
Nous ne nous plaignons pas, nous sommes en altitude.
Andrej Bauer
4

Pour autant que je sache, la valeur nullen C # est une valeur possible pour certaines variables, selon son type (ai-je raison?). Par exemple, des instances d'une classe. Pour le reste des types (comme int, bool, etc.) , vous pouvez ajouter cette valeur d'exception en déclarant des variables avec int?ou à la bool?place (ce qui est exactement ce que le Maybeconstructeur fait, comme je vais décrire suivante).

Le Maybeconstructeur de type de la programmation fonctionnelle ajoute cette nouvelle valeur d'exception pour un type de données donné. Donc, si Intest le type d'entiers ou Gameest le type d'état d'un jeu, alors Maybe Inta tous les entiers plus une valeur nulle (parfois appelée rien ). Pareil pour Maybe Game. Ici, aucun type n'est fourni avec la nullvaleur. Vous l'ajoutez quand vous en avez besoin.

OMI, cette dernière approche est la meilleure pour n'importe quel langage de programmation.

Énorme
la source
Oui, votre compréhension de C # est correcte. Puis-je déduire de votre réponse que vous proposez d'imbriquer Maybeet de supprimer nullcomplètement?
Stijn
2
Mon opinion est de savoir comment une langue devrait être. Je ne connais pas vraiment les meilleures pratiques de programmation C #. Dans votre cas, la meilleure option serait de définir un type de données comme décrit @Andrej Bauer, mais je pense que vous ne pouvez pas le faire en C #.
Euge
@Euge: Les types de somme algébrique peuvent être (approximativement) approximés avec le sous-typage de style OO classique et la distribution de messages polymorphes de sous-type. C'est ainsi que cela se fait dans Scala, par exemple, avec une touche supplémentaire pour permettre les types fermés . Le type devient une superclasse abstraite, les constructeurs de type deviennent des sous-classes concrètes, l'appariement de motifs sur les constructeurs peut être approximé soit en déplaçant les cas dans des méthodes surchargées dans les sous-classes concrètes ou les isinstancetests. Scala a également une correspondance de motifs «correcte», et C♯ a récemment gagné des motifs simples également.
Jörg W Mittag
La "torsion supplémentaire" que j'ai mentionnée pour Scala est qu'en plus des modificateurs "standard" "virtuels" (pour une classe qui peut être héritée) et final(pour une classe qui ne peut pas être héritée), il a également sealed, pour un qui ne peut être étendue que dans la même unité de compilation . Cela signifie que le compilateur peut connaître statiquement toutes les sous-classes possibles (à condition qu'elles soient toutes elles-mêmes sealedou final) et ainsi effectuer des vérifications d'exhaustivité pour les correspondances de modèles, ce qui n'est pas possible statiquement pour un langage avec chargement de code d'exécution et héritage illimité.
Jörg W Mittag
Bien sûr, vous pouvez concevoir des types avec ces sémantiques en C #. Notez que C # ne permet pas d'imbriquer le type peut-être intégré (appelé Nullable en C #). int??est illégal en C #.
Eric Lippert
3

Si vous pouvez définir des unions de type, comme X or Y, et si vous avez un type nommé Nullqui représente uniquement la nullvaleur (et rien d'autre), alors pour n'importe quel type T, il T or Nulln'est en fait pas si différent de Maybe T. Le seul problème avec nullest quand la langue traite un type Tcomme T or Nullimplicitement, en faisant nullune valeur valide pour chaque type (sauf les types primitifs dans les langues qui les ont). C'est ce qui se passe par exemple en Java où une fonction qui prend un Stringaccepte également null.

Si vous traitez les types comme un ensemble de valeurs et orcomme une union d'ensembles, (T or Null) or Nullreprésente alors l'ensemble de valeurs T ∪ {null} ∪ {null}, qui est le même que T or Null. Il existe des compilateurs qui effectuent ce type d'analyses (SBCL). Comme indiqué dans les commentaires, vous ne pouvez pas facilement distinguer entre Maybe Tet Maybe Maybe Tlorsque vous affichez les types en tant que domaines (en revanche, cette vue est très utile pour les programmes typés dynamiquement). Mais vous pouvez également conserver les types en tant qu'expressions algébriques, où (T or Null) or Nullreprésentent plusieurs niveaux de Maybes.

coredump
la source
2
En fait, il est important que ce Maybe (Maybe X)ne soit pas la même chose que Maybe X. Nothingn'est pas la même chose que Just Nothing. Un conflit Maybe (Maybe X)avec Maybe Xrend impossible le traitement Maybe _polymorphe.
Gilles 'SO- arrête d'être méchant'
1

Vous devez découvrir ce que vous souhaitez exprimer, puis trouver un moyen de l'exprimer.

Tout d'abord, vous avez des valeurs dans votre domaine de problème (comme les nombres, les chaînes, les enregistrements clients, les booléens, etc.). Deuxièmement, vous avez des valeurs génériques supplémentaires en plus des valeurs de votre domaine problématique: par exemple, "rien" (l'absence connue d'une valeur), "inconnu" (aucune connaissance de la présence ou de l'absence d'une valeur), non mentionné était " quelque chose "(il y a certainement une valeur, mais nous ne savons pas laquelle). Vous pouvez peut-être penser à d'autres situations.

Si vous vouliez pouvoir représenter toutes les valeurs plus ces trois valeurs supplémentaires, vous créeriez une énumération avec les cas "rien", "inconnu", "quelque chose" et "valeur" et partir de là.

Dans certaines langues, certains cas sont plus simples. Par exemple, dans Swift, vous avez pour chaque type T le type "T optionnel" qui a pour valeur possible nil plus toutes les valeurs de T. Cela vous permet de gérer facilement de nombreuses situations et est parfait si vous n'avez "rien" et " valeur". Vous pouvez l'utiliser si vous avez "inconnu" et "valeur". Vous ne pouvez pas l' utiliser si vous devez gérer "rien", "inconnu" et "valeur". D'autres langues peuvent utiliser "null" ou "peut-être", mais ce n'est qu'un autre mot.

En JSON, vous avez des dictionnaires avec des paires clé / valeur. Ici, une clé peut simplement être manquante dans un dictionnaire, ou elle peut avoir une valeur nulle. Ainsi, en décrivant soigneusement la façon dont les choses sont stockées, vous pouvez représenter des valeurs génériques plus deux valeurs supplémentaires.

gnasher729
la source