В чем смысл опции класса[T]?


я не в состоянии понять точку Option[T] класс в Scala. Я имею в виду, я не в состоянии видеть какие-либо авансы None over 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 усложняет ситуацию, вводя новую оболочку значений (Option[T]) вместо того, чтобы следовать простому подходу, используемому в Java?

обновление:

я отредактировал свой код в соответствии с предложением @Mitch. Я до сих пор не в состоянии увидеть какое-либо конкретное преимущество Option[T]. Я должен проверить для исключительных null или None в обоих случаях. : (

если я правильно понял из ответа @Michael, это единственное преимущество Option[T] is что он явно говорит программисту, что этот метод не может вернуть None? Это единственная причина такого выбора дизайна?

18 80

18 ответов:

вы получите точку 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 существует, Скала не говорит вам как ваш код должен быть написан. Это может мягко подтолкнуть вас, но если вы хотите вернуться к беззащитной сети, это ваш выбор.


вы прибили его здесь:

единственное преимущество варианта[T] является что он явно говорит программист, что этот метод может ничего не вернуть?

за исключением "только". Но позвольте мне повторить это по-другому:main преимущество Option[T] over T безопасность типа. Оно гарантирует, что вы не будете отправлять T метод объекта, который не может существовать, так как компилятор не позволит вам.

Вы сказали, что вы должны проверить на nullability в обоих случаях, но если вы забыли-или не знаете-вы должны проверить на null, компилятор скажет вам? Или будут ваши пользователи?

конечно, из-за своей совместимости с Java, Scala позволяет нули так же, как Java делает. Так что если вы используете Java-библиотек, если вы плохо использовать письменные библиотек Scala, или если вы используете плохо написано персональный библиотеки Scala, вам все равно придется иметь дело с нулевыми указателями.

другие два важных преимущества Option Я могу думать, являются:

  • документация: сигнатура типа метода сообщит вам, всегда ли возвращается объект или нет.

  • Монадическом композиционность.

последний занимает гораздо больше времени, чтобы полностью оценить, и это не очень хорошо подходит к простым примерам, так как он показывает свою силу только на сложном коде. Итак, я приведу пример ниже, но я хорошо знаю, что это вряд ли будет означать что-либо, кроме людей, которые его уже получают.

for {
  person <- getUsers
  email <- person.getEmail // Assuming getEmail returns Option[String]
} yield (person, email)

сравниваем:

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)

Я надеюсь, что это проливает некоторый свет на то, как опции могут сделать жизнь немного легче.

разница незначительная. Имейте в виду, чтобы быть действительно функцией это должны return a value-null на самом деле не считается "нормальным возвращаемым значением" в этом смысле, больше a снизу тип/ничего.

но, в практическом смысле, когда вы вызываете функцию, которая необязательно возвращает что-то, вы бы сделали:

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на функцию, принимающую параметры, не беспокоясь. поэтому вы не проверяете значение null и не заботитесь об исключениях. Сравните это с версией java, представленной в synesso образец.

У вас есть довольно мощные возможности композиции с опцией:

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") )

может быть, кто-то еще указал на это, но я этого не видел:

одним из преимуществ сопоставления шаблонов с параметром [T] против проверки null является то, что параметр является запечатанным классом, поэтому компилятор Scala выдаст предупреждение, если вы пренебрегаете кодом либо в случае Some, либо в случае None. Существует флаг компилятора для компилятора, который превратит предупреждения в ошибки. Таким образом, можно предотвратить неспособность обрабатывать случай "не существует" во время компиляции, а не во время выполнения. Это огромное количество преимущество перед использованием нулевого значения.

Это не поможет избежать нулевой проверки,это там, чтобы заставить нулевую проверку. Смысл становится понятным, когда ваш класс имеет 10 полей, два из которых могут быть null. И ваша система имеет 50 других подобных классов. В мире Java вы пытаетесь предотвратить NPEs на этих полях, используя некоторую комбинацию умственной силы horesepower, соглашения об именах или, возможно, даже аннотации. И каждый Java dev терпит неудачу в этом в значительной степени. Класс Option не только делает значения "nullable" визуально понятными любые разработчики пытаются понять код, но позволяют компилятору принудительно применять этот ранее невысказанный контракт.

[ скопировано из комментарий by Даниил Спивак]

если единственный способ использовать Option были чтобы шаблон соответствовал, чтобы получить значения, тогда да, я согласен, что это не улучшается вообще по нулю. Однако вам не хватает * огромного * класса своей функциональности. Единственный веская причина для использования Option is если вы используете его более высокого порядка функция полезности. Эффективно, вы нужно использовать ее монадическую природу. Например (предполагая определенную сумму 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

вот, разве это не здорово? Мы можем на самом деле сделать намного лучше, если мы используем for-осмысленностей:

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

вы увидите, что мы никогда не* явная проверка на null, None или любой из его рода. Весь смысл Вариант, чтобы избежать этого проверочный. Вы просто строковые вычисления вдоль и вниз по линии, пока вы * действительно * нужно получить значение. На этот момент, вы можете решить, является ли или не хотите делать явную проверку (который вы должны никогда нужно сделать), укажите значение по умолчанию, бросьте исключения и т. д.

Я никогда, никогда не делаю никакого явного соответствия в отношении Option, и я знаю много другие разработчики Scala, которые находятся в та же лодка. Дэвид Поллак упомянул я только на днях, что он использует такое явное совпадение на Option (или Box, в случае подъема) как знак что разработчик кто написал код не до конца понимает язык и его стандартная библиотека.

Я не хочу быть молотом троллей, но вам действительно нужно посмотреть, как языковые функции *фактически * используются на практике перед тем, как колотить их, как бесполезный. Я абсолютно согласен, что Вариант довольно некомпилирующий как *вы* использовали его, но вы не используете его так оно и было задумано.

один момент, который никто здесь, похоже, не поднял, заключается в том, что, хотя у вас может быть нулевая ссылка, есть различие, введенное опцией.

то есть вы можете иметь Option[Option[A]], который был бы населен None,Some(None) и Some(Some(a)) здесь a является одним из обычных жителей A. Это означает, что если у вас есть какой-то контейнер, и вы хотите иметь возможность хранить в нем указатели null и получать их, вам нужно передать назад некоторое дополнительное логическое значение, чтобы знать если вы действительно получили значение. Такие бородавки предостаточно в Java containers API и некоторые варианты без блокировки даже не могут их предоставить.

null является одноразовой конструкцией, она не сочиняется сама по себе, она доступна только для ссылочных типов, и это заставляет вас рассуждать не полным образом.

например, когда вы проверяете

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

вы должны носить с собой в голове на протяжении else филиал x != null и что это уже проверено. Однако, при использовании чего-то вроде option

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

вы знаю y не Noneпо конструкции - и вы бы знали, что это не было null тоже, если бы не Хоэр ошибка на миллиард долларов.

Option[T]-это монада, которая действительно полезна при использовании функций высокого порядка для управления значениями.

Я предлагаю вам прочитать статьи, перечисленные ниже, это действительно хорошие статьи, которые показывают вам, почему опция[T] полезна и как ее можно использовать функционально.

добавление к Рэндаллу тизер ответ, понимая, почему потенциальное отсутствие значения в лице Option требуется понимание того, что Option разделяет со многими другими типами в Scala-в частности, типы моделирования монад. Если один из них представляет отсутствие значения с null, это различие отсутствия-присутствия не может участвовать в контрактах, разделяемых другими монадическими типами.

если вы не знаете, что такое монады, или если вы не замечаете, как они представлены в библиотеке Scala, вы не видите, что Option подыгрывает, и вы не можете видеть, что вы упускаете. Есть много преимуществ для использования Option вместо null, что было бы примечательно даже при отсутствии какой-либо концепции монады (я обсуждаю некоторые из них в разделе "стоимость опции / некоторые vs null" scala-user список рассылки нить здесь), но говорить об этом изоляция-это как говорить о конкретной реализации связанного списка тип итератора, задаваясь вопросом, почему это необходимо, все время пропуская более общий интерфейс контейнера/итератора/алгоритма. Здесь также работает более широкий интерфейс, и Option предоставляет модель присутствия и отсутствия этого интерфейса.

Я думаю, что ключ находится в ответе Synesso: Option is не в первую очередь полезно как громоздкие псевдоним на null, но как полноценный объект, который затем может помочь вам с вашей логикой.

проблема с null заключается в том, что это отсутствие объекта. У него нет методов, которые могли бы помочь вам справиться с этим (хотя в качестве языкового дизайнера вы можете добавлять все более длинные списки функций на свой язык, которые эмулируют объект, если вы действительно хотите он.)

одна вещь, которую можно сделать, как вы продемонстрировали, - это эмулировать null; затем вам нужно проверить экстраординарное значение "None" вместо экстраординарного значения "null". Если вы забудете, в любом случае, плохие вещи произойдут. Вариант действительно делает его менее вероятно, чтобы произойти случайно, поскольку вам придется введите команду "Get" (что, должен вам напомнить, что это может быть null, er, я имею в виду None), но это небольшое преимущество в обмен на дополнительную обертку объект.

где Option действительно начинает показывать свою силу, это помогает вам справиться с концепцией I-wanted-something-but-I-don't-actually-have-one.

давайте рассмотрим некоторые вещи, которые вы можете сделать с вещами, которые могут быть null.

может быть, вы хотите установить значение по умолчанию, если у вас есть нуль. Давайте сравним Java и Scala:

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

вместо громоздкого ?: постройте у нас есть метод, который имеет дело с идеей "использовать по умолчанию значение, если я null". Это немного очищает ваш код.

может быть, вы хотите создать новый объект, только если у вас есть реальные ценности. Сравните:

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

Scala немного короче и снова избегает источников ошибок. Затем рассмотрите совокупную выгоду, когда вам нужно связать вещи вместе, как показано в примерах Synesso, Daniel и paradigmatic.

- Это не vast улучшение, но если вы все сложите, это того стоит везде сохраняйте очень высокопроизводительный код(где вы хотите избежать даже крошечных накладных расходов на создание объекта-оболочки 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.")
}

Теперь вы можете сложить ни одного случая и список пустых случаев все вместе в одном удобном заявлении, которое вытаскивает именно то значение, которое вы хотите.

Это действительно вопрос стиля программирования. Используя функциональную Java или написав свои собственные вспомогательные методы, вы можете иметь свою функциональность опций, но не отказываться от языка Java:

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

просто потому, что Scala включает его по умолчанию, не делает его особенным. Большинство аспектов функциональных языков доступны в этой библиотеке, и она может прекрасно сосуществовать с другим кодом Java. Просто как вы можете программа Scala с нулями вы можете выбрать для программирования Java без них.

нулевые возвращаемые значения присутствуют только для совместимости с Java. Вы не должны использовать их иначе.

признавая заранее, что это бойкий ответ, вариант-монада.

на самом деле я разделяю с вами сомнения. О опции меня действительно беспокоит, что 1) есть накладные расходы на производительность, так как есть ЛОР "некоторых" оболочек, созданных everywehre. 2) я должен использовать много некоторых и вариант в моем коде.

чтобы увидеть преимущества и недостатки этой конструкции решения мы должны принимать во внимание альтернативы. Поскольку Java просто игнорирует проблему обнуления, это не альтернатива. Фактическая альтернатива обеспечивает Программирование Fantom язык. Есть нулевые и ненулевые типы там и ?. ?: операторы вместо карты Scala / flatMap / getOrElse. Я вижу следующие пули в сравнении:

опции:
  1. более простой язык - никаких дополнительных конструкций языка не требуется
  2. униформа с другими монадическими типами
значение NULL-это:
  1. более короткий синтаксис в типичных случаях
  2. более высокую производительность (как вы не нужно создать новые объекты опций и лямбды для map, flatMap)

таким образом, здесь нет очевидного победителя. И еще одно замечание. Нет никакого основного синтаксического преимущества для использования опции. Вы можете определить что-то вроде:

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

или использовать некоторые неявные преобразования, чтобы получить синтаксис плюс с точки.

реальное преимущество наличия явных типов опций заключается в том, что вы можете не используйте их в 98% всех мест, и таким образом статически исключите нулевые исключения. (И в других 2% система типов напоминает вам, чтобы проверить правильно, когда вы на самом деле доступ к ним.)

другая ситуация, когда опция работает, находится в ситуациях, когда типы не могут иметь значение null. Невозможно хранить null в Int, Float, Double и т. д. значение, но с опцией вы можете использовать нет.

в Java вам нужно будет использовать коробочные версии (Integer,...) из тех типов.