Jaki jest sens klasy Option [T]?

83

Nie jestem w stanie zrozumieć sensu Option[T]zajęć w Scali. To znaczy, nie jestem w stanie dostrzec żadnych zalet Noneponad null.

Na przykład rozważ kod:

object Main{
  class Person(name: String, var age: int){
    def display = println(name+" "+age)
  }

  def getPerson1: Person = {
    // returns a Person instance or null
  }

  def getPerson2: Option[Person] = {
    // returns either Some[Person] or None
  }

  def main(argv: Array[String]): Unit = {
    val p = getPerson1
    if (p!=null) p.display

    getPerson2 match{
      case Some(person) => person.display
      case None => /* Do nothing */
    }
  }
}

Załóżmy teraz, że metoda getPerson1zwraca null, a następnie wywołanie displayw pierwszym wierszu programu mainnie powiedzie się NPE. Podobnie, jeśli getPerson2zwróci None, displaywywołanie ponownie zakończy się niepowodzeniem z podobnym błędem.

Jeśli tak, to dlaczego Scala komplikuje sprawę, wprowadzając nowe opakowanie wartości ( Option[T]) zamiast stosować proste podejście używane w Javie?

AKTUALIZACJA:

Zmodyfikowałem swój kod zgodnie z sugestią @Mitcha . Nadal nie widzę żadnej szczególnej korzyści Option[T]. Muszę przetestować wyjątkowy nulllub Nonew obu przypadkach. :(

Jeśli dobrze zrozumiałem z odpowiedzi @ Michael , czy jedyną zaletą Option[T]jest to, że wyraźnie informuje ona programistę, że ta metoda może zwrócić None ? Czy to jedyny powód takiego wyboru projektu?

missingfaktor
źródło
23
Właściwie metoda „get” w Opcji [T] jest wymawiana: „Dlaczego do diabła nie pasujesz do tego wzorca?”
Mitch Blevins
2
Mitch ma rację. Spróbuj przeformułować swój przykład bez użycia get, a otrzymasz to. :-)
Daniel C. Sobral
Masz osobę p .., która jest java .. .try val p = ... Poza tym Option to coś więcej, jak pokazali poniżej Daniel i Synesso - tutaj kilka WSPANIAŁYCH odpowiedzi.
Michael Neale
@Michael: Ups! Dzięki za wskazanie; poprawił to.
missingfaktor

Odpowiedzi:

72

Osiągniesz cel Optionlepiej, jeśli zmusisz się, by nigdy, przenigdy nie używać get. To dlatego, że getjest odpowiednikiem „ok, odeślij mnie z powrotem do null-land”.

A więc weź ten swój przykład. Jak byś dzwonił displaybez używania get? Oto kilka alternatyw:

getPerson2 foreach (_.display)
for (person <- getPerson2) person.display
getPerson2 match {
  case Some(person) => person.display
  case _ =>
}
getPerson2.getOrElse(Person("Unknown", 0)).display

Żadna z tych alternatyw nie pozwoli Ci przywołać displayczegoś, co nie istnieje.

Jeśli chodzi o dlaczego getistnieje, Scala nie mówi ci, jak powinien być napisany twój kod. Może cię delikatnie szturchnąć, ale jeśli nie chcesz wycofać się z żadnej siatki bezpieczeństwa, to twój wybór.


Udało ci się to tutaj:

Czy jedyną zaletą Option [T] jest to, że wyraźnie informuje programistę, że ta metoda może zwrócić wartość None?

Z wyjątkiem „jedynego”. Ale pozwólcie, że powtórzę to w inny sposób: główną zaletą Option[T]nadwyżki Tjest bezpieczeństwo typów. Gwarantuje to, że nie wyślesz Tmetody do obiektu, który może nie istnieć, ponieważ kompilator ci na to nie pozwoli.

Powiedziałeś, że musisz sprawdzić wartość zerową w obu przypadkach, ale jeśli zapomnisz - lub nie wiesz - musisz sprawdzić wartość null, czy kompilator ci powie? A może Twoi użytkownicy?

Oczywiście, ze względu na współdziałanie z Javą, Scala dopuszcza wartości null tak samo jak Java. Więc jeśli używasz bibliotek Java, jeśli używasz źle napisanych bibliotek Scala lub jeśli używasz źle napisanych osobistych bibliotek Scala, nadal będziesz musiał radzić sobie ze wskaźnikami zerowymi.

Inne dwie ważne zalety, które przychodzą Optionmi do głowy, to:

  • Dokumentacja: podpis typu metody powie ci, czy obiekt jest zawsze zwracany, czy nie.

  • Komponowalność monadyczna.

Ta ostatnia zajmuje znacznie więcej czasu, aby w pełni docenić i nie nadaje się do prostych przykładów, ponieważ pokazuje swoją siłę tylko w złożonym kodzie. Podam więc przykład poniżej, ale doskonale zdaję sobie sprawę, że to prawie nic nie znaczy, z wyjątkiem ludzi, którzy już to rozumieją.

for {
  person <- getUsers
  email <- person.getEmail // Assuming getEmail returns Option[String]
} yield (person, email)
Daniel C. Sobral
źródło
5
„zmusić się, by nigdy, przenigdy nie używać get” -> Innymi słowy: „Ty nie get!” :)
fredoverflow
31

Porównać:

val p = getPerson1 // a potentially null Person
val favouriteColour = if (p == null) p.favouriteColour else null

z:

val p = getPerson2 // an Option[Person]
val favouriteColour = p.map(_.favouriteColour)

Monadyczna właściwość bind , która pojawia się w Scali jako funkcja mapy , pozwala nam łączyć operacje na obiektach bez martwienia się o to, czy są one „zerowe”, czy nie.

Weź ten prosty przykład trochę dalej. Powiedzmy, że chcieliśmy znaleźć wszystkie ulubione kolory z listy osób.

// list of (potentially null) Persons
for (person <- listOfPeople) yield if (person == null) null else person.favouriteColour

// list of Options[Person]
listOfPeople.map(_.map(_.favouriteColour))
listOfPeople.flatMap(_.map(_.favouriteColour)) // discards all None's

A może chcielibyśmy znaleźć imię siostry matki ojca:

// with potential nulls
val father = if (person == null) null else person.father
val mother = if (father == null) null else father.mother
val sister = if (mother == null) null else mother.sister

// with options
val fathersMothersSister = getPerson2.flatMap(_.father).flatMap(_.mother).flatMap(_.sister)

Mam nadzieję, że to rzuci trochę światła na to, jak opcje mogą trochę ułatwić życie.

Synesso
źródło
W twoim ostatnim przykładzie, co jeśli ojciec osoby jest nieważny? mapzwróci, Nonea wywołanie zakończy się niepowodzeniem z pewnym błędem. Jak to jest lepsze niż nullpodejście?
missingfaktor
5
Nie. Jeśli osoba jest Brak (lub ojciec, matka lub siostra), to fathersMothersSister będzie Brak, ale żaden błąd nie zostanie wyrzucony.
paradygmatyczny
6
Myślę, że masz na myśli płaską mapę, a nie mapę.
retronim
Dzięki za edycję Daniel. Nie próbowałem kodu przed wysłaniem. Następnym razem będzie lepiej.
Synesso
2
val favouriteColour = if (p == null) p.favouriteColour else null // dokładnie błąd, którego Option pomaga uniknąć! Ta odpowiedź jest dostępna od lat i nikt nie zauważył tego błędu!
Mark Lister
22

Różnica jest subtelna. Pamiętaj, aby być naprawdę funkcją, musi zwracać wartość - null nie jest tak naprawdę uważane za „normalną wartość zwracaną” w tym sensie, bardziej za typ dolny / nic.

Ale w praktycznym sensie, gdy wywołujesz funkcję, która opcjonalnie zwraca coś, zrobiłbyś:

getPerson2 match {
   case Some(person) => //handle a person
   case None => //handle nothing 
}

To prawda, możesz zrobić coś podobnego z null - ale to sprawia, że ​​semantyka wywoływania jest getPerson2oczywista ze względu na fakt, że zwraca Option[Person](fajna praktyczna rzecz, inna niż poleganie na kimś, kto przeczyta dokument i uzyska NPE, ponieważ nie czytają doc).

Spróbuję znaleźć funkcjonalnego programistę, który może udzielić ściślejszej odpowiedzi niż ja.

Michael Neale
źródło
1
To jest również moje rozumienie opcji. Wyraźnie informuje programistę, że możemy uzyskać brak, a jeśli jesteś na tyle głupi, by pamiętać o wykonaniu Some (T), ale nie złapiesz również None, masz kłopoty.
cflewis
1
Lewisham - myślę, że kompilator da ci ostrzeżenie, ponieważ Some / None tworzą algebraiczny typ danych (abstrakcyjna cecha zapieczętowana ...) (ale wychodzę tutaj z pamięci).
Michael Neale,
6
Celem typu Option w większości języków, które go używają, jest to, że zamiast zerowego wyjątku czasu wykonywania otrzymujesz błąd typu czasu kompilacji - kompilator może wiedzieć, że nie masz akcji dla warunku Brak podczas korzystania z danych, co powinno być błędem typu.
Justin Smith
15

Dla mnie opcje są naprawdę interesujące, gdy są obsługiwane przez składnię ze zrozumieniem. Biorąc synesso poprzedni przykład:

// with potential nulls
val father = if (person == null) null else person.father
val mother = if (father == null) null else father.mother
val sister = if (mother == null) null else mother.sister

// with options
val fathersMothersSister = for {
                                  father <- person.father
                                  mother <- father.mother
                                  sister <- mother.sister
                               } yield sister

Jeśli którykolwiek z przydziałów jest None, fathersMothersSisterbędzie, Noneale nie NullPointerExceptionzostanie podniesiony. Następnie możesz bezpiecznie przejść fathersMothersSisterdo funkcji pobierającej parametry opcji bez obaw. więc nie sprawdzasz wartości null i nie dbasz o wyjątki. Porównaj to z wersją java przedstawioną w przykładzie synesso .

paradygmatyczny
źródło
3
Szkoda, że ​​w Scali <-składnia jest ograniczona do „składni list złożonych”, ponieważ w rzeczywistości jest taka sama, jak bardziej ogólna doskładnia z Haskell lub domonadforma z biblioteki monad Clojure. Wiązanie go do list oznacza krótką sprzedaż.
seh
11
„Do rozumienia” w Scali to w zasadzie „do” w Haskellu, nie są ograniczone do list, możesz użyć wszystkiego, co implementuje: def map [B] (f: A => B): C [B] def flatMap [B] (f: A => C [B]): C [B] def filter (p: A => Boolean): C [A]. IOW, dowolna monada
GClaramunt
2
@seh Głosowałem za komentarzem @ GClaramunt, ale nie mogę wystarczająco podkreślić jego punktu. W Scali nie ma związku między zrozumieniami a listami - z wyjątkiem tego, że ta ostatnia jest użyteczna z pierwszą. Odsyłam do stackoverflow.com/questions/1052476/… .
Daniel C. Sobral
Tak, ja wiem, że nie ma związku, ale zgadzam się, że warto zwrócić uwagę; Komentowałem pierwszy wiersz tej odpowiedzi, w którym paradygmatyczny wspomina o „składni rozumienia listy”. To problem dydaktyczny, a nie problem z projektowaniem języka.
seh
9

Masz całkiem potężne możliwości kompozycji dzięki Option:

def getURL : Option[URL]
def getDefaultURL : Option[URL]


val (host,port) = (getURL orElse getDefaultURL).map( url => (url.getHost,url.getPort) ).getOrElse( throw new IllegalStateException("No URL defined") )
Viktor Klang
źródło
Czy mógłbyś to całkowicie wyjaśnić?
Jesvin Jose
8

Może ktoś inny zwrócił na to uwagę, ale ja tego nie widziałem:

Jedną z zalet dopasowywania wzorców z opcją Option [T] w porównaniu ze sprawdzaniem wartości null jest to, że Option jest klasą zapieczętowaną, więc kompilator Scala wyświetli ostrzeżenie, jeśli zaniedbasz kodowanie przypadku Some lub None. W kompilatorze znajduje się flaga kompilatora, która zamienia ostrzeżenia w błędy. Można więc zapobiec niepowodzeniu obsługi przypadku „nie istnieje” w czasie kompilacji, a nie w czasie wykonywania. Jest to ogromna zaleta w porównaniu z użyciem wartości zerowej.

Paul Snively
źródło
7

Nie jest po to, aby pomóc uniknąć sprawdzenia zerowego, ale po to, aby wymusić sprawdzenie zerowe. Sprawa staje się jasna, gdy twoja klasa ma 10 pól, z których dwa mogą być zerowe. Twój system ma 50 innych podobnych klas. W świecie Javy próbujesz zapobiegać NPE na tych polach, używając kombinacji mentalnej władzy, konwencji nazewnictwa, a może nawet adnotacji. A każdy programista Java w znacznym stopniu zawodzi. Klasa Option nie tylko sprawia, że ​​wartości „dopuszczające wartość null” są wizualnie jasne dla wszystkich programistów próbujących zrozumieć kod, ale także umożliwia kompilatorowi wymuszenie tego wcześniej niewypowiedzianego kontraktu.

Adam Rabung
źródło
6

[Skopiowane z tego komentarza przez Daniel Śpiewak ]

Jeśli jedynym sposobem użycia Optionbyłoby dopasowanie wzorca w celu uzyskania wartości, to tak, zgadzam się, że nie poprawia się w ogóle nad wartością zerową. Jednak brakuje Ci * ogromnej * klasy jego funkcjonalności. Jedynym przekonującym powodem do używania Optionjest to, że używasz funkcji narzędziowych wyższego rzędu. Efektywnie musisz wykorzystać jego monadyczny charakter. Na przykład (zakładając pewną ilość przycinania API):

val row: Option[Row] = database fetchRowById 42
val key: Option[String] = row flatMap { _ get “port_key” }
val value: Option[MyType] = key flatMap (myMap get)
val result: MyType = value getOrElse defaultValue

Czy to nie było fajne? W rzeczywistości możemy zrobić dużo lepiej, jeśli użyjemy for-zrozumień:

val value = for {
row <- database fetchRowById 42
key <- row get "port_key"
value <- myMap get key
} yield value
val result = value getOrElse defaultValue

Zauważysz, że * nigdy * nie sprawdzamy jawnie pod kątem null, None lub żadnego podobnego. Cały sens Option polega na tym, aby uniknąć jakiejkolwiek z tych kontroli. Po prostu wykonujesz obliczenia wzdłuż linii i przesuwasz się w dół linii, aż * naprawdę * potrzebujesz uzyskać wartość. W tym momencie możesz zdecydować, czy chcesz przeprowadzić jawne sprawdzanie (czego nigdy nie powinieneś robić), podać wartość domyślną, zgłosić wyjątek itp.

Nigdy, przenigdy nie dokonuję żadnych bezpośrednich dopasowań Optioni znam wielu innych programistów Scala, którzy są na tej samej łodzi. David Pollak wspomniał mi niedawno, że używa takiego wyraźnego dopasowania na Option(lub Boxw przypadku Lift) jako znaku, że programista, który napisał kod, nie w pełni rozumie język i jego standardową bibliotekę.

Nie chcę być młotem trolla, ale naprawdę musisz przyjrzeć się, jak funkcje językowe są * faktycznie * używane w praktyce, zanim uderzysz je jako bezużyteczne. Absolutnie zgadzam się, że Option jest dość nieatrakcyjny, ponieważ * ty * go używałeś, ale nie używasz go tak, jak został zaprojektowany.

missingfaktor
źródło
Jest to smutna konsekwencja tutaj: nie ma skok oparte zwarcie w grze, tak, że każda kolejna wypowiedź testach Optionna Noneraz. Gdyby stwierdzenia zostały zapisane jako zagnieżdżone warunki warunkowe, każde potencjalne „niepowodzenie” zostałoby przetestowane i zastosowane tylko raz. W twoim przykładzie wynik fetchRowByIdjest efektywnie sprawdzany trzy razy: raz w celu keyzainicjowania przewodnika , ponownie dla value's i na koniec dla result' s. To elegancki sposób, aby to napisać, ale nie jest pozbawiony kosztów związanych z uruchomieniem.
seh
4
Myślę, że źle zrozumiałeś wyrozumiałość Scali. Drugi przykład zdecydowanie NIE jest pętlą, jest tłumaczony przez kompilator na serię operacji na płaskiej mapie - tak jak w pierwszym przykładzie.
Kevin Wright
Minęło dużo czasu, odkąd napisałem tutaj swój komentarz, ale właśnie widziałem Kevina. Kevin, do kogo się odnosiłeś, pisząc „źle zrozumiałeś?” Nie rozumiem, jak to mogłem być ja , ponieważ nigdy nie wspomniałem o pętli.
seh
6

Jedna kwestia, której nikt inny tutaj nie poruszył, to fakt, że chociaż możesz mieć odniesienie zerowe, istnieje rozróżnienie wprowadzone przez Option.

Czyli można mieć Option[Option[A]], co będzie zamieszkana przez None, Some(None)a Some(Some(a))gdzie ajest jednym ze zwykłych mieszkańców A. Oznacza to, że jeśli masz jakiś kontener i chcesz mieć możliwość przechowywania w nim zerowych wskaźników i pobierania ich, musisz przekazać z powrotem jakąś dodatkową wartość logiczną, aby wiedzieć, czy faktycznie otrzymałeś wartość. Takie brodawki obfitują w API kontenerów java, a niektóre warianty bez blokad nie mogą ich nawet zapewnić.

null jest konstrukcją jednorazową, nie komponuje się ze sobą, jest dostępna tylko dla typów referencyjnych i zmusza do rozumowania w sposób niecałkowity.

Na przykład, kiedy sprawdzasz

if (x == null) ...
else x.foo()

musisz nosić w głowie po całej elsegałęzi, x != nullże to już zostało sprawdzone. Jednak przy użyciu czegoś takiego jak opcja

x match {
case None => ...
case Some(y) => y.foo
}

wiesz , że nie jest to Nonekonstrukcja - i wiedziałbyś, że tak nie było null, gdyby nie pomyłka Hoare'a za miliard dolarów .

Edward KMETT
źródło
3

Opcja [T] to monada, która jest naprawdę przydatna, gdy używasz funkcji wyższego rzędu do manipulowania wartościami.

Sugeruję przeczytanie artykułów wymienionych poniżej, są to naprawdę dobre artykuły, które pokazują, dlaczego Option [T] jest przydatna i jak można ją wykorzystać w funkcjonalny sposób.

Brian Hsu
źródło
Dodam do listy polecanych lektur ostatnio opublikowany poradnik Tony'ego Morrisa „Co oznacza Monada?”: Projects.tmorris.net/public/what-does-monad-mean/artifacts/1.0/…
Randall Schulz
3

Dodając do zapowiedzi odpowiedzi Randalla , zrozumienie, dlaczego potencjalny brak wartości jest reprezentowany przez, Optionwymaga zrozumienia, co Optiondzieli się z wieloma innymi typami w Scali - w szczególności typami modelującymi monady. Jeśli jeden reprezentuje brak wartości z wartością zerową, to rozróżnienie między nieobecnością a obecnością nie może uczestniczyć w kontraktach wspólnych dla innych typów monadycznych.

Jeśli nie wiesz, czym są monady lub nie zauważysz, jak są one reprezentowane w bibliotece Scali, nie zobaczysz, z czym Optionwspółgrają, i nie możesz zobaczyć, co tracisz. Istnieje wiele korzyści z używania Optionzamiast null, który byłby godny uwagi, nawet w przypadku braku jakiejkolwiek koncepcji monada (omówię niektóre z nich w „Koszt opcji / Niektóre vs null” scala-user Mailing List wątek tutaj ), ale mówimy o izolacja przypomina mówienie o typie iteratora konkretnej implementacji listy połączonej, zastanawianie się, dlaczego jest to konieczne, a jednocześnie pomijanie bardziej ogólnego interfejsu kontenera / iteratora / algorytmu. Działa tu też szerszy interfejs,Option

seh
źródło
Wielkie dzięki za link. To było naprawdę przydatne. :)
missingfaktor
Twój komentarz w wątku był tak zwięzły, że prawie nie trafiłem w sedno. Naprawdę chciałbym, żeby null został zbanowany.
Alain O'Dea
2

Myślę, że klucz znajduje się w odpowiedzi Synesso: Opcja nie jest przede wszystkim użyteczna jako uciążliwy alias dla null, ale jako pełnoprawny obiekt, który może pomóc ci w logice.

Problem z wartością null polega na tym, że jest to brak obiektu. Nie ma metod, które mogłyby pomóc ci sobie z tym poradzić (chociaż jako projektant języka możesz dodawać do swojego języka coraz dłuższe listy funkcji, które emulują obiekt, jeśli naprawdę masz na to ochotę).

Jak zademonstrowałeś, jedna rzecz, jaką Option może zrobić, to emulować wartość null; musisz następnie przetestować niezwykłą wartość „Brak” zamiast nadzwyczajnej wartości „null”. Jeśli zapomnisz, w obu przypadkach wydarzy się coś złego. Opcja sprawia, że ​​jest mniej prawdopodobne, że zdarzy się to przez przypadek, ponieważ musisz wpisać „get” (co powinno przypomnieć, że może to być null, eee, mam na myśli None), ale jest to mała korzyść w zamian za dodatkowy obiekt opakowujący .

Tam, gdzie Option naprawdę zaczyna pokazywać swoją moc, pomaga ci radzić sobie z koncepcją-chciałem-coś-ale-tak-nie-mam-jednego.

Rozważmy kilka rzeczy, które możesz chcieć zrobić z rzeczami, które mogą być zerowe.

Może chcesz ustawić wartość domyślną, jeśli masz null. Porównajmy Javę i Scalę:

String s = (input==null) ? "(undefined)" : input;
val s = input getOrElse "(undefined)"

Zamiast nieco kłopotliwej konstrukcji?: Mamy metodę, która radzi sobie z ideą „użyj wartości domyślnej, jeśli mam wartość null”. To trochę czyści twój kod.

Może chcesz stworzyć nowy obiekt tylko wtedy, gdy masz prawdziwą wartość. Porównać:

File f = (filename==null) ? null : new File(filename);
val f = filename map (new File(_))

Scala jest nieco krótsza i ponownie unika źródeł błędów. Następnie rozważ łączną korzyść, gdy musisz połączyć rzeczy w łańcuch, jak pokazano w przykładach autorstwa Synesso, Daniela i paradygmatu.

Nie jest to duża poprawa, ale jeśli dodasz wszystko, warto wszędzie zapisać kod o bardzo wysokiej wydajności (w którym chcesz uniknąć nawet niewielkiego narzutu związanego z tworzeniem obiektu otoki Some (x)).

Użycie dopasowania nie jest tak naprawdę pomocne samo w sobie, z wyjątkiem jako urządzenie ostrzegające o przypadku null / None. Kiedy jest to naprawdę pomocne, gdy zaczynasz go łączyć, np. Jeśli masz listę opcji:

val a = List(Some("Hi"),None,Some("Bye"));
a match {
  case List(Some(x),_*) => println("We started with " + x)
  case _ => println("Nothing to start with.")
}

Teraz możesz złożyć wszystkie przypadki None i List-is-empty w jednej poręcznej instrukcji, która wyciąga dokładnie taką wartość, jaką chcesz.

Rex Kerr
źródło
2

Zwracane wartości null są obecne tylko w celu zapewnienia zgodności z językiem Java. Nie powinieneś ich używać inaczej.

ryeguy
źródło
1

To naprawdę kwestia stylu programowania. Używając Funkcjonalnej Javy lub pisząc własne metody pomocnicze, możesz mieć funkcjonalność Option, ale nie rezygnujesz z języka Java:

http://functionaljava.org/examples/#Option.bind

To, że Scala zawiera go domyślnie, nie czyni go wyjątkowym. Większość aspektów języków funkcjonalnych jest dostępna w tej bibliotece i może ona dobrze współistnieć z innym kodem Java. Tak jak możesz wybrać programowanie Scali z wartościami zerowymi, możesz programować Javę bez nich.

Sam Pullara
źródło
0

Przyznając z góry, że jest to prosta odpowiedź, Option to monada.

Randall Schulz
źródło
Wiem, że to monada. Z jakiego innego powodu miałbym dołączyć sporny tag „monada”?
missingfaktor
^ Powyższe stwierdzenie nie oznacza, że ​​rozumiem, czym jest monada. : D
missingfaktor
4
Monady są fajne. Jeśli ich nie używasz lub przynajmniej nie udajesz, że rozumiesz, to nie jesteś fajny ;-)
paradygmatyczny
0

Właściwie to dzielę z tobą wątpliwości. Jeśli chodzi o Option, naprawdę przeszkadza mi to, że 1) istnieje narzut wydajności, ponieważ istnieje wiele opakowań „Some” tworzonych każdego dnia. 2) W moim kodzie muszę używać dużo Some i Option.

Aby zobaczyć zalety i wady tej decyzji dotyczącej projektowania języka, powinniśmy wziąć pod uwagę alternatywy. Ponieważ Java po prostu ignoruje problem null, nie jest to alternatywa. Rzeczywistą alternatywą jest język programowania Fantom. Istnieją typy dopuszczające wartość null i nie dopuszczające wartości null oraz?. ?: operatory zamiast map / flatMap / getOrElse Scali. W porównaniu widzę następujące punkty:

Zaleta opcji:

  1. prostszy język - nie są wymagane żadne dodatkowe konstrukcje językowe
  2. mundur z innymi typami monadycznymi

Zaleta Nullable:

  1. krótsza składnia w typowych przypadkach
  2. lepsza wydajność (ponieważ nie musisz tworzyć nowych obiektów Option i lambd dla mapy, flatMap)

Więc nie ma tutaj oczywistego zwycięzcy. I jeszcze jedna uwaga. Używanie Option nie ma żadnej głównej korzyści syntaktycznej. Możesz zdefiniować coś takiego:

def nullableMap[T](value: T, f: T => T) = if (value == null) null else f(value)

Lub użyj niektórych niejawnych konwersji, aby uzyskać pritty składnię z kropkami.

Alexey
źródło
Czy ktoś wykonał solidne testy porównawcze wydajności na nowoczesnej maszynie wirtualnej? Analiza ucieczki oznacza, że ​​na stosie można zaalokować wiele tymczasowych obiektów Option (znacznie tańszych niż sterta), a generacja GC dość wydajnie obsługuje nieco mniej tymczasowych obiektów. Oczywiście, jeśli szybkość jest ważniejsza dla twojego projektu niż unikanie NPE, opcje prawdopodobnie nie są dla ciebie.
Justin W
Nie wspominaj o narzutach związanych z wydajnością bez numerów, które mogłyby to potwierdzić. Jest to niezwykle częsty błąd podczas argumentowania przeciwko abstrakcjom takim jak Option. Z radością cofnę mój głos negatywny, jeśli wskażesz lub opublikujesz test porównawczy lub usuniesz komentarz dotyczący wyników :)
Alain O'Dea
0

Prawdziwą zaletą posiadania jawnych typów opcji jest to, że nie można ich używać w 98% wszystkich miejsc, a tym samym statycznie wyklucza zerowe wyjątki. (A w pozostałych 2% system typów przypomina ci o prawidłowym sprawdzeniu, kiedy faktycznie uzyskujesz do nich dostęp).

Andreas Rossberg
źródło
-3

Inną sytuacją, w której opcja działa, są sytuacje, w których typy nie mogą mieć wartości null. Nie jest możliwe przechowywanie wartości null w wartości Int, Float, Double itp., Ale z Opcją można użyć None.

W Javie trzeba by użyć pudełkowych wersji (Integer, ...) tych typów.

Arne
źródło