Jestem pewien, że projektanci języków takich jak Java czy C # znali problemy związane z istnieniem zerowych referencji (zobacz Czy referencje zerowe są naprawdę złe? ). Także implementacja typu opcji nie jest tak naprawdę dużo bardziej złożona niż odwołania zerowe.
Dlaczego mimo to postanowili to uwzględnić? Jestem pewien, że brak pustych referencji zachęciłby (a nawet wymusiłby) kod lepszej jakości (zwłaszcza lepszy projekt biblioteki) zarówno od twórców języków, jak i użytkowników.
Czy to po prostu z powodu konserwatyzmu - „inne języki to mają, my też musimy to mieć…”?
language-design
null
mrpyo
źródło
źródło
Odpowiedzi:
Oświadczenie: Ponieważ nie znam żadnych projektantów języków, każda udzielona przeze mnie odpowiedź będzie spekulacyjna.
Od samego Tony'ego Hoare'a :
Podkreśl moje.
Oczywiście wtedy nie wydawało mu się to złym pomysłem. Prawdopodobnie zostało to częściowo utrwalone z tego samego powodu - jeśli wydawało się to dobrym pomysłem dla zdobywcy nagrody Turinga wynalazcy Quicksort, nic dziwnego, że wiele osób wciąż nie rozumie, dlaczego jest zły. Prawdopodobnie jest to po części dlatego, że wygodne jest, aby nowe języki były podobne do starszych języków, zarówno ze względów marketingowych, jak i uczenia się. Przykładem:
(Źródło: http://www.paulgraham.com/icad.html )
I oczywiście C ++ ma wartość null, ponieważ C ma wartość null, i nie ma potrzeby wchodzenia w historyczny wpływ C. C # zastąpił J ++, który był implementacją Javy przez Microsoft, a także zastąpił C ++ jako język wyboru dla rozwoju Windows, więc mógł go pobrać z dowolnego z nich.
EDYCJA Oto kolejny cytat z Hoare, który warto rozważyć:
Ponownie podkreśl moje. Sun / Oracle i Microsoft to firmy, a podstawą każdej firmy są pieniądze. Korzyści, jakie im
null
przyniosły, mogły przeważyć nad wadami lub mogli po prostu mieć zbyt krótki termin, aby w pełni rozważyć problem. Jako przykład pomyłki w innym języku, która prawdopodobnie wystąpiła z powodu terminów:(Źródło: http://www.artima.com/intv/bloch13.html )
źródło
null
w swoim języku? Każda odpowiedź na to pytanie będzie spekulacyjna, chyba że pochodzi od projektanta języka. Nie znam żadnej tak częstej strony oprócz Erica Lipperta. Ostatnia część to czerwony śledź z wielu powodów. Ilość kodu strony trzeciej napisanego na interfejsach API MS i Java w oczywisty sposób przewyższa ilość kodu w samym API. Więc jeśli Twoi klienci tego chcąnull
, dajesz imnull
. Podejrzewasz również, że zaakceptowalinull
to kosztem pieniędzy.ICloneable
podobnie jest uszkodzony w .NET; niestety jest to jedno z miejsc, w których nie odkryto braków w Javie.Oczywiście.
Pozwolę sobie być innego zdania! Rozważania projektowe, które weszły w typy wartości zerowalnych w C # 2, były złożone, kontrowersyjne i trudne. Zabrali zespoły projektantów obu języków i środowiska wykonawczego na wiele miesięcy debaty, wdrożenia prototypów i tak dalej, aw rzeczywistości semantyka nokautowalnego boksu została zmieniona bardzo blisko wysyłki C # 2.0, co było bardzo kontrowersyjne.
Cały projekt jest procesem wybierania spośród wielu subtelnie i rażąco niezgodnych celów; Mogę jedynie przedstawić krótki szkic tylko kilku czynników, które należy wziąć pod uwagę:
Ortogonalność cech językowych jest ogólnie uważana za dobrą rzecz. C # ma typy wartości dopuszczających wartości zerowe, typy wartości nie dopuszczających wartości zerowych i typy wartości dopuszczających wartości zerowe. Niewymienne typy referencyjne nie istnieją, co powoduje, że system typów nie jest ortogonalny.
Znajomość istniejących użytkowników C, C ++ i Java jest ważna.
Ważna jest łatwa interoperacyjność z COM.
Ważna jest łatwa współpraca ze wszystkimi innymi językami .NET.
Ważna jest łatwa interoperacyjność z bazami danych.
Ważna jest spójność semantyki; jeśli mamy odniesienie TheKingOfFrance równe null, czy to zawsze oznacza „nie ma teraz króla Francji”, czy może to również oznaczać „zdecydowanie króla Francji; po prostu nie wiem, kto jest teraz”? czy może to oznaczać „samo pojęcie posiadania króla we Francji jest nonsensowne, więc nawet nie zadawaj pytania!”? Null może oznaczać wszystkie te rzeczy i więcej w języku C #, a wszystkie te pojęcia są przydatne.
Koszt wydajności jest ważny.
Ważna jest możliwość poddania się analizie statycznej.
Ważna jest spójność systemu typów; czy zawsze możemy wiedzieć, że odwołanie, które nie ma wartości zerowej, nigdy nie jest w żadnym wypadku uważane za nieprawidłowe? A co z konstruktorem obiektu o niepisającym polu typu referencyjnego? A co z finalizatorem takiego obiektu, w którym obiekt jest finalizowany, ponieważ kod, który miał wypełnić referencję, zwrócił wyjątek ? System typów, który kłamie na temat swoich gwarancji, jest niebezpieczny.
A co z konsekwencją semantyki? Null wartości propagować kiedy używany, ale zerowe referencje rzucać wyjątki podczas eksploatacji. To niespójne; czy ta niespójność jest uzasadniona jakąś korzyścią?
Czy możemy wdrożyć tę funkcję, nie psując innych funkcji? Jakie inne możliwe przyszłe funkcje wyklucza ta funkcja?
Idziesz na wojnę z armią, którą masz, a nie tą, którą chcesz. Pamiętaj, że C # 1.0 nie miał generycznych, więc mówienie o tym
Maybe<T>
jako alternatywie jest kompletnym non-starterem. Czy .NET powinien tracić ważność przez dwa lata, podczas gdy zespół wykonawczy dodawał ogólne, wyłącznie w celu wyeliminowania pustych referencji?Co z spójnością systemu typów? Możesz powiedzieć
Nullable<T>
dla każdego rodzaju wartości - nie, czekaj, to kłamstwo. Nie można powiedziećNullable<Nullable<T>>
. Powinieneś być w stanie? Jeśli tak, to jaka jest jego pożądana semantyka? Czy warto, aby cały system typów miał w tym przypadku specjalny przypadek tylko dla tej funkcji?I tak dalej. Te decyzje są złożone.
źródło
catches
tym, że nic nie robią. Myślę, że lepiej jest pozwolić, aby system wysadził w powietrze, zamiast kontynuować pracę w potencjalnie nieprawidłowym stanie.Null służy bardzo słusznemu celowi reprezentowania braku wartości.
Powiem, że jestem najbardziej głośną osobą, jaką znam na temat nadużywania wartości zerowej oraz wszystkich bólów głowy i cierpienia, jakie mogą powodować, zwłaszcza gdy są stosowane swobodnie.
Moje osobiste stanowisko jest takie, że ludzie mogą stosować wartości zerowe tylko wtedy, gdy mogą uzasadnić, że jest to konieczne i właściwe.
Przykład uzasadniający wartości null:
Data śmierci to zazwyczaj pole zerowalne. Istnieją trzy możliwe sytuacje z datą śmierci. Albo dana osoba zmarła, a data jest znana, dana osoba zmarła, a data jest nieznana, lub dana osoba nie jest martwa, a zatem data śmierci nie istnieje.
Data śmierci jest również polem DateTime i nie ma wartości „nieznana” ani „pusta”. Ma domyślną datę, która pojawia się, gdy tworzysz nową datę, która różni się w zależności od używanego języka, ale technicznie istnieje szansa, że dana osoba faktycznie umarła w tym czasie i oznaczałaby jako „pustą wartość”, gdybyś użyj domyślnej daty.
Dane musiałyby właściwie przedstawiać sytuację.
Osoba nie żyje Data śmierci jest znana (3/9/1984)
Prosty, „3/9/1984”
Osoba zmarła data śmierci nieznana
Co jest najlepsze? Null , „0/0/0000” lub „01/01/1869” (czy jakakolwiek wartość domyślna?)
Osoba nie jest martwa data śmierci nie dotyczy
Co jest najlepsze? Null , „0/0/0000” lub „01/01/1869” (czy jakakolwiek wartość domyślna?)
Pomyślmy więc o każdej wartości nad ...
Faktem jest czasem trzeba Czy trzeba reprezentować nic i pewien typ zmiennej czasami działa dobrze, ale często typy zmiennych muszą być zdolne do reprezentowania nic.
Jeśli nie mam jabłek, mam 0 jabłek, ale co, jeśli nie wiem, ile mam jabłek?
Z całą pewnością zero jest nadużywane i potencjalnie niebezpieczne, ale czasami jest konieczne. W wielu przypadkach jest to ustawienie domyślne, ponieważ dopóki nie podam wartości, brak wartości i coś musi ją reprezentować. (Zero)
źródło
Null serves a very valid purpose of representing a lack of value.
AnOption
lubMaybe
type spełnia ten bardzo ważny cel bez omijania systemu typów.NOT NULL
. Moje pytanie nie było jednak związane z RDBMS ...Person
Typ mastate
pole typuState
, które jest dyskryminowanym połączeniemAlive
iDead(dateOfDeath : Date)
.Nie posunąłbym się tak daleko, że „inne języki to mają, my też musimy to mieć…” jakby to było coś w rodzaju nadążania za Jonesami. Kluczową cechą każdego nowego języka jest możliwość współpracy z istniejącymi bibliotekami w innych językach (czytaj: C). Ponieważ C ma wskaźniki zerowe, warstwa interoperacyjności koniecznie potrzebuje pojęcia null (lub innego odpowiednika „nie istnieje”, który pojawia się, gdy go używasz).
Projektant języka mógł wybrać Typy Opcji i zmusić cię do obsługi ścieżki zerowej wszędzie tam , gdzie rzeczy mogą być zerowe. I to prawie na pewno doprowadziłoby do zmniejszenia liczby błędów.
Ale (szczególnie w przypadku Java i C # ze względu na czas ich wprowadzenia i odbiorców docelowych) użycie typów opcji dla tej warstwy interoperacyjności prawdopodobnie zaszkodziłoby, gdyby nie storpedowało ich przyjęcia. Albo typ opcji jest przekazywany aż do góry, irytując programistów C ++ od połowy do końca lat 90-tych - lub warstwa interoperacyjności rzucałaby wyjątki przy napotkaniu zer, denerwując programistów C ++ od połowy do końca lat 90-tych. ..
źródło
Option
do użytku w Scali, co jest tak proste, jakval x = Option(possiblyNullReference)
. W praktyce nie trzeba długo czekać, aby ludzie zobaczyli zaletyOption
.Match
metody, która przyjmuje delegatów jako argumenty. Następnie przekazujesz wyrażenia lambda doMatch
(punkty bonusowe za używanie nazwanych argumentów) iMatch
wywołujesz właściwe.Po pierwsze, myślę, że wszyscy możemy się zgodzić, że koncepcja nieważności jest konieczna. Istnieją sytuacje, w których musimy przedstawić brak informacji.
Zezwalanie na
null
odwołania (i wskaźniki) to tylko jedna implementacja tej koncepcji i być może najpopularniejsza, chociaż wiadomo, że ma problemy: C, Java, Python, Ruby, PHP, JavaScript, ... wszystkie używają podobnychnull
.Dlaczego ? Jaka jest alternatywa?
W językach funkcyjnych, takich jak Haskell masz
Option
lubMaybe
typu; są one jednak oparte na:Czy oryginalne C, Java, Python, Ruby lub PHP obsługiwały którąkolwiek z tych funkcji? Nie. Wady języka generycznego Javy są najnowsze w historii tego języka i wątpię, żeby inni w ogóle je wdrożyli.
Masz to.
null
jest łatwe, parametryczne algebraiczne typy danych są trudniejsze. Ludzie wybrali najprostszą alternatywę.źródło
Object
”, nie rozpoznają, że praktyczne aplikacje potrzebują niepodzielnych obiektów zmiennych i współdzielonych obiektów niezmiennych (które powinny zachowywać się jak wartości), a także współdzielenia i nieudostępnienia podmioty. Użycie jednegoObject
rodzaju do wszystkiego nie eliminuje potrzeby takich rozróżnień; utrudnia to tylko ich prawidłowe użycie.Samo zero / zero / zero nie jest złe.
Jeśli patrzysz na jego myląco nazwane słynne przemówienie „Błąd miliarda dolarów”, Tony Hoare mówi o tym, jak zezwolenie dowolnej zmiennej na utrzymanie wartości zerowej było wielkim błędem. Alternatywą - używając opcji - czy nie w rzeczywistości pozbyć referencji null. Zamiast tego pozwala określić, które zmienne mogą mieć wartość null, a które nie.
W rzeczywistości, w nowoczesnych językach, które implementują odpowiednią obsługę wyjątków, błędy zerowania zerowego nie różnią się niczym od innych wyjątków - znajdziesz go, naprawisz. Niektóre alternatywy dla odwołań zerowych (na przykład wzorzec obiektu zerowego) ukrywają błędy, powodując dyskretne awarie aż do dużo później. Moim zdaniem znacznie lepiej szybko zawieść .
Pytanie brzmi zatem, dlaczego języki nie wdrażają Opcji? W rzeczywistości prawdopodobnie najpopularniejszy język wszechczasów C ++ ma zdolność definiowania zmiennych obiektowych, których nie można przypisać
NULL
. Jest to rozwiązanie „problemu zerowego”, o którym wspomniał Tony Hoare w swoim przemówieniu. Dlaczego następny najpopularniejszy język maszynowy, Java, nie ma go? Ktoś może zapytać, dlaczego ma tak wiele wad w ogóle, szczególnie w swoim systemie typów. Nie sądzę, żebyś naprawdę mógł powiedzieć, że języki systematycznie popełniają ten błąd. Niektórzy tak robią, inni nie.źródło
null
.null
jest jedynym sensownym domyślnym. Odwołania użyte do enkapsulacji wartości mogą jednak mieć rozsądne zachowanie domyślne w przypadkach, w których typ miałby lub mógłby skonstruować rozsądną domyślną instancję. Wiele aspektów zachowania referencji zależy od tego, czy i w jaki sposób zawierają wartość, ale ...foo
zawiera jedyne odwołanie do elementuint[]
zawierającego,{1,2,3}
a kod chcefoo
przechowywać odniesienie do elementuint[]
zawierającego{2,2,3}
, najszybszym sposobem na osiągnięcie tego byłoby zwiększeniefoo[0]
. Jeśli kod chce, aby metoda wiedziała, że sięfoo
trzyma{1,2,3}
, druga metoda nie zmodyfikuje tablicy ani nie utrwali referencji poza punktem, w którymfoo
chciałaby ją zmodyfikować, najszybszym sposobem na osiągnięcie tego byłoby przekazanie referencji do tablicy. Jeśli Java ma typ „efemerycznego odwołania tylko do odczytu”, to ...Ponieważ języki programowania są ogólnie zaprojektowane tak, aby były praktycznie użyteczne, a nie technicznie poprawne. Faktem jest, że
null
stany są częstym zjawiskiem z powodu złych lub brakujących danych lub stanu, który nie został jeszcze ustalony. Technicznie lepsze rozwiązania są bardziej nieporęczne niż po prostu zezwalanie na stany zerowe i wysysanie z faktu, że programiści popełniają błędy.Na przykład, jeśli chcę napisać prosty skrypt, który działa z plikiem, mogę napisać pseudokod taki jak:
i po prostu zawiedzie, jeśli plik joebloggs.txt nie istnieje. Chodzi o to, że w przypadku prostych skryptów jest to prawdopodobnie w porządku, a dla wielu sytuacji w bardziej złożonym kodzie wiem, że on istnieje i awaria się nie zdarzy, więc zmuszenie mnie do sprawdzenia marnuje mój czas. Bezpieczniejsze alternatywy osiągają swoje bezpieczeństwo, zmuszając mnie do prawidłowego radzenia sobie z potencjalnym stanem awarii, ale często nie chcę tego robić, chcę po prostu zacząć.
źródło
for line in file
) i zgłasza bezsensowny wyjątek odniesienia zerowego, co jest OK dla tak prostego programu, ale powoduje rzeczywiste problemy z debugowaniem w znacznie bardziej złożonych systemach. Gdyby nie istniały wartości zerowe, projektant „pliku otwartego” nie byłby w stanie popełnić tego błędu.let file = something(...).unwrap()
. W zależności od POV jest to prosty sposób na nieobsługiwanie błędów lub zwięzłe twierdzenie, że nie może wystąpić wartość null. Zmarnowany czas jest minimalny, a Ty oszczędzasz czas w innych miejscach, ponieważ nie musisz zastanawiać się, czy coś może być zerowe. Kolejną zaletą (która może być warta dodatkowego połączenia) jest to, że jawnie ignorujesz przypadek błędu; kiedy się nie powiedzie, nie ma wątpliwości, co poszło nie tak i gdzie należy dokonać poprawki.if file exists { open file }
cierpi z powodu wyścigu. Jedynym niezawodnym sposobem sprawdzenia, czy otwarcie pliku się powiedzie, jest próba otwarcia go.Istnieją jasne, praktyczne zastosowania wskaźnika
NULL
(lubnil
, lubNil
, lubnull
,Nothing
lub jakkolwiek to się nazywa w preferowanym języku) wskaźnika.W przypadku języków, które nie mają systemu wyjątków (np. C), wskaźnik zerowy może być używany jako znak błędu, kiedy wskaźnik powinien zostać zwrócony. Na przykład:
Tutaj
NULL
zwrócony zmalloc(3)
służy jako znacznik niepowodzenia.W przypadku argumentów metody / funkcji może wskazywać użycie wartości domyślnej dla argumentu lub zignorować argument wyjściowy. Przykład poniżej.
Nawet dla tych języków z mechanizmem wyjątku wskaźnik zerowy może być używany jako wskaźnik błędu miękkiego (czyli błędów, które można odzyskać), szczególnie gdy obsługa wyjątków jest kosztowna (np. Cel-C):
W tym przypadku błąd miękki nie powoduje awarii programu, jeśli nie zostanie złapany. Eliminuje to szalone try-catch, takie jak Java, i ma lepszą kontrolę nad przebiegiem programu, ponieważ miękkie błędy nie przeszkadzają (a kilku pozostałych trudnych wyjątków zwykle nie można odzyskać i nie przechwycić)
źródło
null
od tych, które powinny. Na przykład, jeśli chcę nowy typ, który zawiera 5 wartości w Javie, mógłbym użyć wyliczenia, ale otrzymuję typ, który może przechowywać 6 wartości (5 chciałem +null
). To wada w systemie typów.Null
można przypisać znaczenie tylko wtedy, gdy wartości typu nie zawierają danych (np. Wartości wyliczeniowe). Gdy tylko twoje wartości staną się bardziej skomplikowane (np. Struct),null
nie można przypisać znaczenia, które ma sens dla tego typu. Nie ma sposobu, aby użyćnull
jako struktury lub listy. I znowu problem z użyciemnull
jako sygnału błędu polega na tym, że nie jesteśmy w stanie stwierdzić, co może zwrócić wartość null lub przyjąć wartość null. Każda zmienna w twoim programie może być,null
chyba że bardzo skrupulatnie sprawdzasz każdynull
przed każdym użyciem, czego nikt nie robi.null
za użyteczną wartość domyślną (np. Posiadanie domyślnej wartościstring
behave jako pustego łańcucha, tak jak to miało miejsce w poprzednim Common Object Model). Jedyne, co byłoby konieczne, to używanie językówcall
zamiastcallvirt
wywoływania członków niebędących wirtualnymi.Istnieją dwa powiązane, ale nieco różne problemy:
null
w ogóle powinien istnieć? A może powinieneś zawsze używaćMaybe<T>
wartości null?Czy wszystkie odniesienia powinny być zerowane? Jeśli nie, która powinna być domyślna?
Konieczność jawnego zadeklarowania dopuszczalnego typu zerowania jako
string?
lub podobnego pozwoliłoby uniknąć większości (ale nie wszystkich)null
przyczyn problemów , nie będąc zbyt różnym od tego, do czego są przyzwyczajeni programiści.Zgadzam się przynajmniej z tobą, że nie wszystkie odniesienia powinny być zerowane. Ale unikanie wartości null nie jest pozbawione złożoności:
.NET inicjuje wszystkie pola,
default<T>
zanim będzie można uzyskać do nich dostęp za pomocą kodu zarządzanego. Oznacza to, że dla potrzebnych typów referencjinull
lub czegoś równoważnego te typy wartości mogą być inicjowane do pewnego rodzaju zera bez uruchamiania kodu. Chociaż oba mają poważne wady, prostotadefault
inicjalizacji mogła przeważyć te wady.Na przykład pola można obejść, wymagając inicjalizacji pól przed wystawieniem
this
wskaźnika na kod zarządzany. Spec # poszedł tą drogą, używając innej składni niż tworzenie łańcuchów konstruktorów w porównaniu z C #.W przypadku pól statycznych upewnienie się, że jest to trudniejsze, chyba że nałożysz silne ograniczenia na rodzaj kodu, który może działać w inicjalizatorze pól, ponieważ nie możesz po prostu ukryć
this
wskaźnika.Jak zainicjować tablice typów referencyjnych? Zastanów się,
List<T>
która jest wspierana przez tablicę o pojemności większej niż długość. Pozostałe elementy muszą mieć pewną wartość.Innym problemem jest to, że nie pozwala na podobne metody
bool TryGetValue<T>(key, out T value)
, która zwracadefault(T)
jakovalue
jeśli nie znajdą niczego. Chociaż w tym przypadku łatwo jest argumentować, że parametr out jest złym projektem, a ta metoda powinna zwrócić związek rozróżniający lub może zamiast tego.Wszystkie te problemy można rozwiązać, ale nie jest to tak proste, jak „zabrania zerowania i wszystko jest w porządku”.
źródło
List<T>
Jest IMHO najlepszym przykładem, ponieważ wymagałoby to, że albo każdyT
ma wartość domyślną, że każdy element w sklepie podkładowej byćMaybe<T>
z dodatkowym „IsValid” pola, nawet gdyT
jestMaybe<U>
lub że kod naList<T>
zachowują się różnie w zależności od od tego, czyT
sam jest typem zerowalnym. InicjowanieT[]
elementów do wartości domyślnej uważałbym za najmniej złe z tych wyborów, ale oczywiście oznacza to, że elementy muszą mieć wartość domyślną.Najbardziej przydatne języki programowania pozwalają na zapisywanie i odczytywanie elementów danych w dowolnych sekwencjach, tak że często niemożliwe będzie ustalenie statyczne kolejności, w jakiej odczyty i zapisy wystąpią przed uruchomieniem programu. Istnieje wiele przypadków, w których kod przechowuje przydatne dane w każdym gnieździe przed ich odczytaniem, ale udowodnienie tego byłoby trudne. Dlatego często konieczne będzie uruchamianie programów, w których przynajmniej teoretycznie byłoby możliwe, aby kod próbował odczytać coś, co nie zostało jeszcze napisane z użyteczną wartością. Bez względu na to, czy jest to zgodne z prawem, nie ma ogólnego sposobu na powstrzymanie kodu przed podjęciem próby. Jedyne pytanie brzmi: co powinno się stać, kiedy to nastąpi.
Różne języki i systemy mają różne podejście.
Jednym z podejść byłoby stwierdzenie, że każda próba odczytania czegoś, co nie zostało napisane, spowoduje natychmiastowy błąd.
Drugim podejściem jest wymaganie od kodu dostarczenia pewnej wartości w każdej lokalizacji, zanim będzie można ją odczytać, nawet jeśli nie byłoby sposobu, aby przechowywana wartość była semantycznie użyteczna.
Trzecim podejściem jest po prostu zignorowanie problemu i pozwolić, aby cokolwiek wydarzyło się „naturalnie”, po prostu się wydarzyło.
Czwarte podejście mówi, że każdy typ musi mieć wartość domyślną, a każdy slot, który nie został napisany z niczym innym, będzie przyjmował wartość domyślną.
Podejście nr 4 jest znacznie bezpieczniejsze niż podejście nr 3 i ogólnie jest tańsze niż podejście nr 1 i 2. To pozostawia pytanie, jaka powinna być wartość domyślna dla typu odniesienia. W przypadku niezmiennych typów referencji w wielu przypadkach sensowne byłoby zdefiniowanie domyślnej instancji i powiedzenie, że domyślna dla dowolnej zmiennej tego typu powinna być referencją do tej instancji. W przypadku zmiennych typów odniesienia nie byłoby to jednak bardzo pomocne. Jeśli podjęta zostanie próba użycia zmiennego typu odwołania przed jego napisaniem, zasadniczo nie ma żadnego bezpiecznego sposobu działania poza pułapką w punkcie próby użycia.
Semantycznie rzecz biorąc, jeśli ktoś ma tablicę
customers
typuCustomer[20]
i próbuje sięCustomer[4].GiveMoney(23)
niczego nie zapisywaćCustomer[4]
, wykonanie będzie musiało zostać uwięzione. Można argumentować, że próba odczytuCustomer[4]
powinna od razu złapać pułapkę, zamiast czekać na próbę wykonania koduGiveMoney
, ale istnieje wystarczająca liczba przypadków, w których warto odczytać boks, dowiedzieć się, że nie zawiera on wartości, a następnie skorzystać z tego informacja, że sama próba odczytu nie powiodła się, często byłaby poważnym utrapieniem.Niektóre języki pozwalają określić, że niektóre zmienne nigdy nie powinny zawierać wartości null, a każda próba zapisania wartości null powinna spowodować natychmiastową pułapkę. To przydatna funkcja. Zasadniczo jednak każdy język, który pozwala programistom tworzyć tablice referencji, będzie musiał albo dopuszczać możliwość zerowania elementów tablicy, albo wymusi inicjalizację elementów tablicy na dane, które nie mogą być znaczące.
źródło
Maybe
/Option
typ rozwiązać problem z # 2, ponieważ jeśli nie mają wartość odniesienia jeszcze ale będzie mieć w przyszłości, można po prostu przechowywaćNothing
w sposóbMaybe <Ref type>
?null
/ właściwego używania ?List<T>
być aT[]
czy aMaybe<T>
? Co z rodzajem podkładu aList<Maybe<T>>
?Maybe
ma to sens dla typu kopii zapasowej,List
ponieważMaybe
zawiera jedną wartość. Miałeś na myśliMaybe<T>[]
?Nothing
można przypisać tylko do wartości typuMaybe
, więc nie jest to tak jak przypisanienull
.Maybe<T>
iT
są dwoma odrębnymi typami.