Я не могу понять смысл Option[T]
класса в Scala. Я имею в виду, я не вижу никаких преимуществ None
сверх null
.
Например, рассмотрим код:
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 */
}
}
}
Теперь предположим, что метод getPerson1
возвращается null
, тогда вызов, сделанный в display
первой строке, main
обязательно завершится ошибкой NPE
. Точно так же, если getPerson2
возвращается None
, display
вызов снова завершится ошибкой с аналогичной ошибкой.
Если да, то почему Scala усложняет ситуацию, вводя новое значение wrapper ( Option[T]
) вместо того, чтобы следовать простому подходу, используемому в Java?
ОБНОВИТЬ:
Я отредактировал свой код в соответствии с предложением @Mitch . Я до сих пор не вижу особых преимуществ Option[T]
. Я должен проверить на исключительное null
или None
в обоих случаях. :(
Если я правильно понял из ответа @ Michael , единственное преимущество Option[T]
заключается в том, что он явно сообщает программисту, что этот метод может вернуть None ? Это единственная причина такого выбора дизайна?
get
, и вы его получите . :-)Ответы:
Вы добьетесь
Option
большего, если заставите себя никогда, никогда не использоватьget
. Это потому, чтоget
это эквивалент «хорошо, отправь меня обратно в нулевую страну».Итак, возьмем тот ваш пример. Как бы вы позвонили
display
без использованияget
? Вот несколько альтернатив:getPerson2 foreach (_.display) for (person <- getPerson2) person.display getPerson2 match { case Some(person) => person.display case _ => } getPerson2.getOrElse(Person("Unknown", 0)).display
Ни одна из этих альтернатив не позволит вам обратиться
display
к тому, чего не существует.Что касается того, почему
get
существует, Scala не говорит вам, как должен быть написан ваш код. Это может слегка подтолкнуть вас, но если вы не хотите отказываться от подстраховки, это ваш выбор.Вы прибили здесь:
Кроме «единственного». Но позвольте мне повторить это по-другому: главное преимущество
Option[T]
overT
- это безопасность типов. Это гарантирует, что вы не отправитеT
метод объекту, который может не существовать, поскольку компилятор вам не позволит.Вы сказали, что в обоих случаях вам нужно проверить на допустимость значения NULL, но если вы забудете - или не знаете, - вы должны проверить значение NULL, компилятор скажет вам? Или ваши пользователи?
Конечно, из-за возможности взаимодействия с Java Scala допускает значения NULL, как и Java. Поэтому, если вы используете библиотеки Java, если вы используете плохо написанные библиотеки Scala или если вы используете плохо написанные личные библиотеки Scala, вам все равно придется иметь дело с нулевыми указателями.
Option
Я могу вспомнить еще два важных преимущества :Документация: подпись типа метода сообщит вам, всегда ли возвращается объект или нет.
Монадическая компонуемость.
Для полного понимания последнего требуется гораздо больше времени, и он не очень подходит для простых примеров, поскольку показывает свою силу только на сложном коде. Итак, я приведу пример ниже, но я прекрасно понимаю, что это вряд ли что-то будет значить, кроме людей, которые уже понимают это.
for { person <- getUsers email <- person.getEmail // Assuming getEmail returns Option[String] } yield (person, email)
источник
get
" -> Другими словами: "Вы не делаетеget
этого!" :)Сравните:
val p = getPerson1 // a potentially null Person val favouriteColour = if (p == null) p.favouriteColour else null
с участием:
val p = getPerson2 // an Option[Person] val favouriteColour = p.map(_.favouriteColour)
Связывание монадических свойств , которое появляется в Scala как функция карты , позволяет нам связывать операции с объектами, не беспокоясь о том, являются ли они «нулевыми» или нет.
Рассмотрим этот простой пример немного дальше. Допустим, мы хотели найти все любимые цвета из списка людей.
// 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
Или, возможно, мы хотели бы найти имя сестры матери отца человека:
// 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)
Я надеюсь, что это проливает свет на то, как различные варианты могут немного облегчить жизнь.
источник
map
вернется,None
и вызов завершится с ошибкой. Чем это лучшеnull
подхода?Разница небольшая. Имейте в виду, что для того, чтобы быть истинной функцией, она должна возвращать значение - null на самом деле не считается «нормальным возвращаемым значением» в этом смысле, скорее, нижний тип / ничего.
Но в практическом смысле, когда вы вызываете функцию, которая может что-то возвращать, вы должны:
getPerson2 match { case Some(person) => //handle a person case None => //handle nothing }
Конечно, вы можете сделать что-то подобное с null, но это делает семантику вызова
getPerson2
очевидной в силу того факта, что он возвращаетOption[Person]
(приятная практическая вещь, кроме того, что кто-то читает документ и получает NPE, потому что они не читают док).Я постараюсь найти функционального программиста, который может дать более строгий ответ, чем я.
источник
Для меня варианты действительно интересны, когда используются для понимания синтаксиса. Взяв synesso из предыдущего примера:
// 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
Если какие-либо из переуступок есть
None
, тоfathersMothersSister
будет,None
но неNullPointerException
будет. Затем вы можете безопасно перейтиfathersMothersSister
к функции, принимающей параметры Option, не беспокоясь. поэтому вы не проверяете значение null и не заботитесь об исключениях. Сравните это с версией java, представленной в примере synesso .источник
<-
синтаксис ограничен «синтаксисом понимания списка», так как он действительно такой же, как более общийdo
синтаксис из Haskell илиdomonad
форма из библиотеки монад Clojure. Привязка его к спискам ведет к недооценке.У вас есть довольно мощные возможности композиции с помощью 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") )
источник
Возможно, кто-то еще указал на это, но я этого не видел:
Одно из преимуществ сопоставления с шаблоном с помощью Option [T] по сравнению с проверкой на null состоит в том, что Option является запечатанным классом, поэтому компилятор Scala выдаст предупреждение, если вы пренебрегли кодированием случая Some или None. У компилятора есть флаг компилятора, который превращает предупреждения в ошибки. Таким образом, можно предотвратить сбой в обработке случая «не существует» во время компиляции, а не во время выполнения. Это огромное преимущество перед использованием нулевого значения.
источник
Это не для того, чтобы помочь избежать нулевой проверки, это для принудительной нулевой проверки. Суть становится ясной, когда ваш класс имеет 10 полей, два из которых могут быть нулевыми. И в вашей системе есть еще 50 подобных классов. В мире Java вы пытаетесь предотвратить NPE в этих полях, используя некоторую комбинацию умственных способностей, соглашения об именах или, возможно, даже аннотации. И каждый Java-разработчик в значительной степени терпит неудачу в этом. Класс Option не только делает значения, допускающие значение NULL, визуально понятными для всех разработчиков, пытающихся понять код, но и позволяет компилятору применять этот ранее невысказанный контракт.
источник
[Скопирована из этого комментария от Daniel Spiewak ]
источник
Option
дляNone
снова. Если бы операторы были написаны как вложенные условные выражения, каждый потенциальный «сбой» проверялся бы и принимал меры только один раз. В вашем примере результатfetchRowById
эффективно проверяется три раза: один раз дляkey
инициализации руководства , снова дляvalue
s и, наконец, дляresult
s. Это элегантный способ его написания, но он не обходится без затрат времени выполнения.Один момент, который, похоже, никто здесь не поднял, заключается в том, что, хотя вы можете иметь нулевую ссылку, существует различие, введенное Option.
То есть вы можете иметь
Option[Option[A]]
, где бы жилиNone
,Some(None)
иSome(Some(a))
гдеa
находится один из обычных обитателейA
. Это означает, что если у вас есть какой-то контейнер, и вы хотите иметь возможность хранить в нем нулевые указатели и извлекать их, вам нужно передать некоторое дополнительное логическое значение, чтобы знать, действительно ли вы получили значение. Подобные проблемы изобилуют API-интерфейсами java-контейнеров, а некоторые варианты без блокировки даже не могут их предоставить.null
является одноразовой конструкцией, она не компилируется сама с собой, она доступна только для ссылочных типов и заставляет вас рассуждать не тотально.Например, когда вы проверяете
if (x == null) ... else x.foo()
вы должны носить в голове всю
else
ветку, чтоx != null
и это уже проверено. Однако при использовании чего-то вроде опцииx match { case None => ... case Some(y) => y.foo }
вы знаете, что это не
None
по конструкции - и вы бы тоже знали, что это неnull
так, если бы не ошибка Хора на миллиард долларов .источник
Option [T] - это монада, которая действительно полезна при использовании функций высокого порядка для управления значениями.
Я предлагаю вам прочитать перечисленные ниже статьи, это действительно хорошие статьи, которые покажут вам, почему вариант [T] полезен и как его можно использовать функционально.
источник
В дополнение к тизеру ответа Рэндалла , понимание того, почему потенциальное отсутствие ценности представлено в виде,
Option
требует понимания того, чтоOption
разделяет многие другие типы в Scala, в частности типы, моделирующие монады. Если один представляет отсутствие значения с нулевым значением, это различие отсутствия-присутствия не может участвовать в контрактах, общих для других монадических типов.Если вы не знаете, что такое монады, или если вы не замечаете, как они представлены в библиотеке Scala, вы не увидите, что
Option
играет вместе, и вы не увидите, что вы упускаете. Есть много преимуществ в использованииOption
вместо нулевой , что бы отметить , даже в отсутствие какого - либо понятия монады (я обсуждаю некоторые из них в «Стоимость опциона / Some против нулевой» Ла Скала пользователей списка рассылки нить здесь ), но говорить о его изоляция похожа на разговор о конкретном типе итератора реализации связанного списка, недоумение, зачем он нужен, при этом упуская из виду более общий интерфейс контейнера / итератора / алгоритма. Здесь тоже работает более широкий интерфейс,Option
источник
Я думаю, что ключ находится в ответе Synesso: Option полезен не в первую очередь как громоздкий псевдоним для null, а как полноценный объект, который затем может помочь вам с вашей логикой.
Проблема с null в том, что это отсутствие объекта. У него нет методов, которые могли бы помочь вам с этим справиться (хотя как языковый дизайнер вы можете добавлять в свой язык все более длинные списки функций, которые имитируют объект, если вам действительно это нравится).
Одна вещь, которую Option может сделать, как вы продемонстрировали, - это имитировать null; затем вам нужно проверить необычное значение «None» вместо необычного значения «null». Если вы забудете, в любом случае произойдут плохие вещи. Опция действительно снижает вероятность того, что это произойдет случайно, так как вам нужно ввести «get» (что должно напомнить вам, что это может быть null, э-э, я имею в виду None), но это небольшое преимущество в обмен на дополнительный объект-оболочку .
Вариант действительно начинает показывать свою мощь, так это помогает вам разобраться с концепцией «я-чего-то хотел-но-у меня-на самом деле-нет».
Давайте рассмотрим некоторые вещи, которые вы можете сделать с вещами, которые могут быть нулевыми.
Возможно, вы захотите установить значение по умолчанию, если у вас есть null. Сравним Java и Scala:
String s = (input==null) ? "(undefined)" : input; val s = input getOrElse "(undefined)"
Вместо несколько громоздкой конструкции?: У нас есть метод, который имеет дело с идеей «использовать значение по умолчанию, если я пуст». Это немного очищает ваш код.
Может быть, вы захотите создать новый объект, только если у вас есть реальная ценность. Сравните:
File f = (filename==null) ? null : new File(filename); val f = filename map (new File(_))
Scala немного короче и снова исключает источники ошибок. Затем рассмотрите совокупную выгоду, когда вам нужно объединить все в цепочку, как показано в примерах Synesso, Daniel и paradigmatic.
Это не обширный улучшение, но если вы все сложите, оно того стоит, чтобы везде сохранить очень высокопроизводительный код (где вы хотите избежать даже крошечных накладных расходов на создание объекта-оболочки Some (x)).
Использование совпадений само по себе не так полезно, кроме как в качестве устройства, предупреждающего вас о случае null / None. Когда это действительно полезно, это когда вы начинаете его связывать, например, если у вас есть список опций:
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.") }
Теперь вы можете сложить кейсы None и кейсы List-is-empty в один удобный оператор, который вытаскивает именно то значение, которое вам нужно.
источник
Возвращаемые значения NULL присутствуют только для совместимости с Java. В противном случае вы не должны использовать их.
источник
Это действительно вопрос стиля программирования. Используя функциональную Java или написав свои собственные вспомогательные методы, вы можете получить функциональность Option, но не отказываться от языка Java:
http://functionaljava.org/examples/#Option.bind
То, что Scala включает его по умолчанию, не делает его особенным. В этой библиотеке доступно большинство аспектов функциональных языков, и она может прекрасно сосуществовать с другим кодом Java. Так же, как вы можете программировать Scala с нулями, вы можете программировать Java без них.
источник
Заранее признавая, что это бойкий ответ, Option - это монада.
источник
Собственно, я разделяю с вами сомнения. Что касается Option, меня действительно беспокоит то, что 1) есть накладные расходы на производительность, так как существует множество "некоторых" оберток, созданных повсюду. 2) Я должен использовать в своем коде много Some и Option.
Итак, чтобы увидеть преимущества и недостатки этого языкового дизайнерского решения, мы должны рассмотреть альтернативы. Поскольку Java просто игнорирует проблему допустимости значений NULL, это не альтернатива. Фактическая альтернатива - язык программирования Fantom. Существуют типы, допускающие и не допускающие значения NULL, и?. ?: вместо map / flatMap / getOrElse в Scala. В сравнении я вижу следующие маркеры:
Преимущество варианта:
Преимущество Nullable:
Так что очевидного победителя здесь нет. И еще одно замечание. Принципиального синтаксического преимущества использования Option нет. Вы можете определить что-то вроде:
def nullableMap[T](value: T, f: T => T) = if (value == null) null else f(value)
Или используйте неявные преобразования, чтобы получить грубый синтаксис с точками.
источник
Реальным преимуществом наличия явных типов опций является то, что вы можете не использовать их в 98% всех мест и, таким образом, статически исключить нулевые исключения. (А в остальных 2% система типов напоминает вам о необходимости правильно проверить, когда вы действительно обращаетесь к ним.)
источник
Другая ситуация, в которой работает Option, - это ситуации, когда типы не могут иметь нулевое значение. Невозможно сохранить null в значениях Int, Float, Double и т. Д., Но с помощью Option вы можете использовать None.
В Java вам нужно будет использовать коробочные версии (Integer, ...) этих типов.
источник