Ist es sinnvoll, sowohl das Konzept von 'null' als auch von 'vielleicht' zu haben?

11

Beim Erstellen eines Clients für eine Web-API in C # stieß ich auf ein Problem hinsichtlich nulleines Werts, bei dem zwei verschiedene Dinge dargestellt werden:

  • nichts , zB ein foodarf oder darf nicht a habenbar
  • unbekannt : Standardmäßig enthält die API-Antwort nur eine Teilmenge von Eigenschaften. Sie müssen angeben, welche zusätzlichen Eigenschaften Sie möchten. So unbekannt bedeutet , dass die Eigenschaft wurde nicht von der API angefordert.

Nach einigem Suchen fand ich heraus, wie der Typ Vielleicht (oder Option) in funktionalen Sprachen verwendet wird und wie er Null-Dereferenzierungsprobleme "löst", indem er den Benutzer dazu zwingt, über das mögliche Fehlen eines Werts nachzudenken. In allen Ressourcen, auf die ich gestoßen bin, ging es jedoch darum, null durch Vielleicht zu ersetzen . Ich habe einige Erwähnungen von dreiwertiger Logik gefunden , aber ich verstehe sie nicht vollständig und meistens wurde sie im Zusammenhang mit "es ist eine schlechte Sache" erwähnt.

Ich frage mich jetzt, ob es Sinn macht, sowohl das Konzept von null als auch von Maybe zu haben , um Unbekanntes bzw. Nichts darzustellen . Ist das die dreiwertige Logik, über die ich gelesen habe, oder hat sie einen anderen Namen? Oder ist der beabsichtigte Weg, ein Vielleicht in ein Vielleicht zu nisten?

Stijn
quelle
9
Es macht keinen Sinn zu haben null. Es ist eine völlig kaputte Idee.
Andrej Bauer
7
Machen Sie kein Vielleicht-Vielleicht-Foo, das eine andere Semantik hat als ein Vielleicht-Foo. Vielleicht ist es eine Monade , und eines der Monadengesetze ist das M M xund M xsollte die gleiche Semantik haben.
Eric Lippert
2
Sie können sich das Design früherer Versionen von Visual Basic ansehen, die Nothing (Verweis auf kein Objekt), Null (Datenbank-Nullsemantik), Empty (eine nicht initialisierte Variable) und Missing (ein optionaler Parameter wurde nicht übergeben) enthielten. Dieses Design war kompliziert und in vielerlei Hinsicht inkonsistent, aber es gibt einen Grund, warum diese Konzepte nicht miteinander in Konflikt gebracht wurden.
Eric Lippert
3
Ja, ich würde vielleicht auch nicht verwenden. Aber in der Tat, wie Maybe adas gleiche ist wie , semantisch, ist das gleiche wie ein + 1 + 1 und isomorph geben von @Andej Antwort. Sie können auch für diesen Typ eine eigene Monadeninstanz definieren und daher verschiedene Monadenkombinatoren verwenden. ein+1Maybe Maybe aein+1+1UserInput a
Euge
12
@EricLippert: Es ist falsch, dass " M (M x)und M xdie gleiche Semantik haben sollte". Nehmen wir M = Listzum Beispiel: Listen von Listen sind nicht dasselbe wie Listen. Wenn Meine Monade ist, gibt es eine Transformation (nämlich die Multiplikation Monade) aus M (M x)zu M xdem erklärt die Beziehung zwischen ihnen, aber sie haben nicht „die gleiche Semantik“.
Andrej Bauer

Antworten:

14

Der nullüberall als Standardwert vorhandene Wert ist nur a wirklich kaputte Idee . Vergessen Sie das also.

Sie sollten immer genau das Konzept haben, das Ihre tatsächlichen Daten am besten beschreibt. Wenn Sie einen Typ benötigen, der "unbekannt", "nichts" und "Wert" angibt, sollten Sie genau das haben. Aber wenn es nicht Ihren tatsächlichen Bedürfnissen entspricht, sollten Sie es nicht tun. Es ist eine gute Idee zu sehen, was andere Leute verwenden und was sie vorgeschlagen haben, aber Sie müssen ihnen nicht blind folgen.

Leute, die Standardbibliotheken entwerfen, versuchen, gängige Verwendungsmuster zu erraten, und das tun sie normalerweise gut. Wenn Sie jedoch eine bestimmte Sache benötigen, sollten Sie diese selbst definieren. In Haskell können Sie beispielsweise Folgendes definieren:

data UnknownNothing a =
     Unknown
   | Nothing
   | Value a

Es kann sogar vorkommen, dass Sie etwas Beschreibenderes verwenden, an das Sie sich leichter erinnern können:

data UserInput a =
     Unknown
   | NotGiven
   | Answer a

Sie können 10 solcher Typen definieren, die für verschiedene Szenarien verwendet werden sollen. Der Nachteil ist, dass Sie keine bereits vorhandenen Bibliotheksfunktionen (wie die von Maybe) zur Verfügung haben, dies stellt sich jedoch normalerweise als Detail heraus. Es ist nicht so schwer, eigene Funktionen hinzuzufügen.

Andrej Bauer
quelle
Sinnvoll, wenn man sieht, dass es so geschrieben ist. Ich interessiere mich hauptsächlich für Ideen, bestehende Bibliotheksunterstützung ist nur ein Bonus :)
Stijn
Wenn nur die Barriere für die Erstellung solcher Typen in vielen Sprachen geringer wäre. : /
Raphael
Wenn nur Menschen ab den 1960er Jahren aufhören würden, Sprachen zu benutzen (oder ihre Reinkarnationen ab den 1980er Jahren).
Andrej Bauer
@AndrejBauer: was? aber dann hätten Theoretiker nichts zu beanstanden!
Yttrill
Wir beschweren uns nicht, wir sind in der Luft.
Andrej Bauer
4

Soweit ich weiß, ist der Wert nullin C # je nach Typ ein möglicher Wert für einige Variablen (habe ich recht?). Zum Beispiel Instanzen einer Klasse. Für den Rest der Typen (wie int, boolusw.) können Sie diesen Ausnahmewert hinzufügen , indem Sie Variablen mit deklarierte int?oder bool?statt (das ist genau das, was der MaybeKonstruktor tut, wie ich als nächstes beschreiben).

Der MaybeTypkonstruktor aus der funktionalen Programmierung fügt diesen neuen Ausnahmewert für einen bestimmten Datentyp hinzu. Wenn Intes sich also um die Art der Ganzzahlen oder Gameum den Typ des Status eines Spiels handelt, Maybe Intenthält es alle Ganzzahlen plus einen Nullwert (manchmal auch als Nichts bezeichnet ). Das gleiche gilt für Maybe Game. Hier gibt es keine Typen, die mit dem kommennull Wert geliefert werden. Sie fügen es hinzu, wenn Sie es brauchen.

IMO, dieser letzte Ansatz ist der bessere für jede Programmiersprache.

Euge
quelle
Ja, Ihr Verständnis von C # ist korrekt. Kann ich aus Ihrer Antwort ableiten, dass Sie vorschlagen, Maybes zu verschachteln und nullvollständig fallen zu lassen?
Stijn
2
Meiner Meinung nach geht es darum, wie eine Sprache sein sollte. Ich kenne die Best Practices der C # -Programmierung nicht wirklich. In Ihrem Fall wäre die beste Option, einen Datentyp wie von @Andrej Bauer beschrieben zu definieren, aber ich denke, Sie können dies in C # nicht tun.
Euge
@Euge: Algebraische Summentypen können (grob) mit klassischer Subtypisierung im OO-Stil und polymorphem Nachrichtenversand vom Subtyp angenähert werden. So wird es beispielsweise in Scala gemacht, mit einer zusätzlichen Drehung, um geschlossene Typen zu ermöglichen . Der Typ wird zu einer abstrakten Oberklasse, die Typkonstruktoren werden zu konkreten Unterklassen. Die Musterübereinstimmung über die Konstruktoren kann entweder durch Verschieben der Fälle in überladene Methoden in den konkreten Unterklassen oder durch isinstanceTests angenähert werden. Scala hat auch einen "richtigen" Mustervergleich, und C♯ hat erst kürzlich auch einfache Muster erhalten.
Jörg W Mittag
Die "zusätzliche Wendung", die ich für Scala erwähnt habe, ist, dass zusätzlich zu den "Standard" -Modifikatoren "virtuell" (für eine Klasse, von der geerbt werden kann) und final(für eine Klasse, von der nicht geerbt werden kann) auch sealedfür a Klasse, die nur innerhalb derselben Kompilierungseinheit erweitert werden kann . Dies bedeutet, dass der Compiler alle möglichen Unterklassen statisch kennen kann (vorausgesetzt, alle sind entweder sie selbst sealedoder final) und somit Vollständigkeitsprüfungen für Musterübereinstimmungen durchführen, was für eine Sprache mit Laufzeitcode-Laden und uneingeschränkter Vererbung statisch nicht möglich ist.
Jörg W Mittag
Natürlich können Sie Typen mit dieser Semantik in C # entwerfen. Beachten Sie, dass C # nicht zulässt, dass der integrierte Vielleicht-Typ (in C # Nullable genannt) verschachtelt wird. int??ist in C # illegal.
Eric Lippert
3

Wenn Sie Typ Gewerkschaften, wie definieren X or Y, und wenn Sie einen Typ mit dem Namen , Nullder nur das repräsentiert nullWert (und sonst nichts), dann für jede Art T, T or Nullist eigentlich nicht so verschieden von Maybe T. Das einzige Problem nullbesteht darin, dass die Sprache einen Typ Tals T or Nullimplizit behandelt und nullfür jeden Typ einen gültigen Wert erstellt (außer für primitive Typen in Sprachen, in denen sie vorhanden sind). Dies ist beispielsweise in Java der Fall, wo eine Funktion, die a übernimmt, Stringebenfalls akzeptiert wird null.

Wenn Sie Typen als Wertemenge und orals Vereinigung von Mengen behandeln, (T or Null) or Nullstellt dies die Wertemenge dar T ∪ {null} ∪ {null}, die mit identisch ist T or Null. Es gibt Compiler, die diese Art von Analysen (SBCL) durchführen. Wie in den Kommentaren erwähnt, können Sie nicht leicht zwischen Maybe Tund unterscheiden, Maybe Maybe Twenn Sie Typen als Domänen anzeigen (andererseits ist diese Ansicht für dynamisch typisierte Programme sehr nützlich). Sie können Typen aber auch als algebraische Ausdrücke (T or Null) or Nullbeibehalten , wobei mehrere Ebenen von Maybes dargestellt werden.

Core-Dump
quelle
2
Eigentlich ist es wichtig, dass Maybe (Maybe X)das nicht dasselbe ist wie Maybe X. Nothingist nicht dasselbe wie Just Nothing. Der Konflikt Maybe (Maybe X)mit Maybe Xmacht es unmöglich, Maybe _polymorph zu behandeln .
Gilles 'SO - hör auf böse zu sein'
1

Sie müssen herausfinden, was Sie ausdrücken möchten, und dann einen Weg finden, wie Sie es ausdrücken können.

Erstens haben Sie Werte in Ihrer Problemdomäne (wie Zahlen, Zeichenfolgen, Kundendatensätze, Boolesche Werte usw.). Zweitens haben Sie einige zusätzliche generische Werte zusätzlich zu Ihren Problemdomänenwerten: Zum Beispiel "nichts" (das bekannte Fehlen eines Wertes), "unbekannt" (kein Wissen über das Vorhandensein oder Fehlen eines Wertes), nicht erwähnt wurde " etwas "(es gibt definitiv einen Wert, aber wir wissen nicht, welcher). Vielleicht können Sie sich andere Situationen vorstellen.

Wenn Sie alle Werte plus diese drei zusätzlichen Werte darstellen möchten, erstellen Sie eine Aufzählung mit den Fällen "nichts", "unbekannt", "etwas" und "Wert" und gehen von dort aus.

In einigen Sprachen sind einige Fälle einfacher. Zum Beispiel haben Sie in Swift für jeden Typ T den Typ "optionales T", dessen mögliche Werte Null plus alle Werte von T sind. Auf diese Weise können Sie viele Situationen einfach handhaben und sind perfekt, wenn Sie "nichts" und "nichts" haben. Wert". Sie können es verwenden, wenn Sie "unbekannt" und "Wert" haben. Sie können es nicht verwenden, wenn Sie mit "nichts", "unbekannt" und "Wert" umgehen müssen. Andere Sprachen verwenden möglicherweise "null" oder "vielleicht", aber das ist nur ein anderes Wort.

In JSON haben Sie Wörterbücher mit Schlüssel / Wert-Paaren. Hier fehlt möglicherweise nur ein Schlüssel in einem Wörterbuch oder er hat den Wert null. Wenn Sie also sorgfältig beschreiben, wie Dinge gespeichert werden, können Sie generische Werte plus zwei zusätzliche Werte darstellen.

gnasher729
quelle