Podczas tworzenia klienta interfejsu API sieci Web w języku C # napotkałem problem dotyczący null
wartości, która reprezentowałaby dwie różne rzeczy:
- nic , np.
foo
może mieć lub może nie miećbar
- nieznany : domyślnie odpowiedź API zawiera tylko podzbiór właściwości, musisz wskazać, które dodatkowe właściwości chcesz. Tak nieznany oznacza, że właściwość nie była wymagana od API.
Po kilku poszukiwaniach dowiedziałem się o typie Może (lub Opcji), w jaki sposób jest on używany w językach funkcjonalnych i jak „rozwiązuje” problemy zerowania dereferencji, zmuszając użytkownika do myślenia o możliwym braku wartości. Jednak wszystkie zasoby natknąłem mówił o wymianie NULL z Może . Znalazłem kilka wzmianek o trójwartościowej logice , ale nie do końca ją rozumiem i przez większość czasu wspomniano o niej w kontekście „to zła rzecz”.
Zastanawiam się teraz, czy sensownie jest mieć zarówno wartość zerową, jak i Być może odpowiednio reprezentować nieznane i nic . Czy to logika trójwartościowa, o której czytam, czy też ma inną nazwę? A może zamierzony sposób na zagnieżdżenie „Może w być”?
null
. Jest to całkowicie zepsuty pomysł.M M x
iM x
powinno mieć tę samą semantykę.Maybe a
jest taka sama jak , semantycznie, jest taka sama jak , a izomorficzne do pisania z @Andej odpowiedź. Możesz również zdefiniować własną instancję monady dla tego typu, a zatem użyć różnych kombinatorów monad. a + 1 + 1Maybe Maybe a
UserInput a
M (M x)
iM x
powinien mieć tę samą semantykę”. WeźmyM = List
na przykład: listy list nie są tym samym, co listy. KiedyM
jest monada, jest transformacja (czyli mnożenie monada) odM (M x)
doM x
co wyjaśnia relacje między nimi, ale oni nie mają „te same semantyki”.Odpowiedzi:
null
Wartość jako domyślną obecny wszędzie jest po prostu naprawdę złamane pomysł , więc zapomnij o tym.Zawsze powinieneś mieć dokładnie taką koncepcję, która najlepiej opisuje twoje rzeczywiste dane. Jeśli potrzebujesz typu, który wskazuje „nieznane”, „nic” i „wartość”, powinieneś mieć właśnie to. Ale jeśli nie odpowiada twoim rzeczywistym potrzebom, nie powinieneś tego robić. Dobrym pomysłem jest sprawdzenie, czego używają inni ludzie i co zaproponowali, ale nie musisz ich ślepo podążać.
Ludzie, którzy projektują standardowe biblioteki, próbują odgadnąć typowe wzorce użytkowania i zwykle robią to dobrze, ale jeśli potrzebujesz konkretnej rzeczy, powinieneś ją zdefiniować sam. Na przykład w Haskell można zdefiniować:
Może być nawet tak, że powinieneś używać czegoś bardziej opisowego, łatwiejszego do zapamiętania:
Możesz zdefiniować 10 takich typów, które będą używane w różnych scenariuszach. Wadą jest to, że nie będziesz mieć dostępnych wcześniej istniejących funkcji bibliotecznych (takich jak te
Maybe
), ale zwykle okazuje się to szczegółem. Dodanie własnej funkcjonalności nie jest trudne.źródło
O ile mi wiadomo, wartość
null
w C # jest możliwą wartością dla niektórych zmiennych, w zależności od jej typu (mam rację?). Na przykład wystąpienia niektórych klas. Dla pozostałych typów (jakint
,bool
itp) można dodać tę wartość wyjątku przez deklarowania zmiennych zint?
lubbool?
zamiast (to jest dokładnie to, coMaybe
robi konstruktor, jak opiszę następne).Konstruktor
Maybe
typów z programowania funkcjonalnego dodaje tę nową wartość wyjątku dla danego typu danych. Więc jeśliInt
jest typem liczb całkowitych lubGame
typem stanu gry, toMaybe Int
ma wszystkie liczby całkowite plus wartość zerową (czasami nazywaną niczym ). To samo dotyczyMaybe Game
. Tutaj nie ma typów, które pochodzą znull
wartością. Dodajesz go, kiedy go potrzebujesz.IMO, to ostatnie podejście jest lepsze dla każdego języka programowania.
źródło
Maybe
inull
całkowite upuszczenie ?isinstance
testy. Scala ma również „właściwe” dopasowanie do wzorca, a C♯ faktycznie niedawno zyskał również proste wzorce.final
(dla klasy, której nie można odziedziczyć), ma ona równieżsealed
dla klasa, którą można rozszerzyć tylko w ramach tej samej jednostki kompilacji . Oznacza to, że kompilator może statycznie znać wszystkie możliwe podklasy (pod warunkiem, że wszystkie z nich są albo same,sealed
albofinal
), a tym samym sprawdzać kompletność dopasowań wzorców, co nie jest możliwe statycznie dla języka z ładowaniem kodu środowiska wykonawczego i nieograniczonym dziedziczeniem.int??
jest nielegalne w C #.Jeśli można zdefiniować związki typu, jak
X or Y
i jeśli masz typ o nazwieNull
, która reprezentuje tylkonull
wartość (i nic więcej), a następnie dla każdego typuT
,T or Null
w rzeczywistości nie jest tak różne odMaybe T
. Jedynym problememnull
jest to, że język traktuje typT
jakoT or Null
niejawnie, co czyninull
prawidłową wartość dla każdego typu (z wyjątkiem typów pierwotnych w językach, które je mają). Tak dzieje się na przykład w Javie, gdzie funkcja, która przyjmujeString
również, akceptujenull
.Jeśli traktujesz typy jako zbiór wartości i
or
jako sumę zbiorów, wówczas(T or Null) or Null
reprezentuje zestaw wartościT ∪ {null} ∪ {null}
, który jest taki sam jakT or Null
. Istnieją kompilatory, które wykonują tego rodzaju analizy (SBCL). Jak powiedziano w komentarzach, nie można łatwo odróżnić typów od domenMaybe T
iMaybe Maybe T
kiedy wyświetlasz je (z drugiej strony widok ten jest całkiem przydatny w przypadku programów z dynamicznym pisaniem). Ale możesz także zachować typy jako wyrażenia algebraiczne, gdzie(T or Null) or Null
reprezentują wiele poziomów Maybesa.źródło
Maybe (Maybe X)
jest, że to nie to samo, coMaybe X
.Nothing
to nie to samo coJust Nothing
. PołączenieMaybe (Maybe X)
zMaybe X
tym uniemożliwia leczenieMaybe _
polimorficzne.Musisz dowiedzieć się, co chcesz wyrazić, a następnie znaleźć sposób, jak to wyrazić.
Po pierwsze, masz wartości w domenie problemowej (takie jak liczby, ciągi znaków, rekordy klientów, booleany itp.). Po drugie, masz dodatkowe ogólne wartości nad wartościami domen problemowych: Na przykład „nic” (znany brak wartości), „nieznany” (brak wiedzy o obecności lub braku wartości), nie wspomniano „ coś ”(zdecydowanie jest pewna wartość, ale nie wiemy która). Może możesz pomyśleć o innych sytuacjach.
Jeśli chcesz być w stanie przedstawić wszystkie wartości plus te trzy dodatkowe wartości, utwórz enum z przypadkami „nic”, „nieznane”, „coś” i „wartość” i stamtąd.
W niektórych językach niektóre przypadki są prostsze. Na przykład w Swift masz dla każdego typu T typ „opcjonalny T”, który ma jako możliwe wartości zero plus wszystkie wartości T. To pozwala łatwo poradzić sobie z wieloma sytuacjami i jest idealny, jeśli masz „nic” i „ wartość". Możesz go użyć, jeśli masz „nieznane” i „wartość”. Można nie używać go jeśli trzeba obsłużyć „nic”, „nieznane” i „wartość”. Inne języki mogą używać „null” lub „może”, ale to tylko inne słowo.
W JSON masz słowniki z parami klucz / wartość. Tutaj klucz może po prostu brakować w słowniku lub może mieć wartość null. Dokładnie opisując sposób przechowywania rzeczy, możesz przedstawić wartości ogólne plus dwie dodatkowe wartości.
źródło