Какой смысл класса Опция [T]?

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

ОБНОВЛЕНИЕ:

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

Если я понял правильно от ответа @Michael, единственное преимущество Option[T] это, это явно говорит программисту, что этот метод не мог возвратить Ни один? Действительно ли это - единственная причина позади этого проектного решения?

82
задан Community 23 May 2017 в 11:47
поделиться

15 ответов

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


Ты прибил его сюда:

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

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

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

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

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

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

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

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

for {
  person <- getUsers
  email <- person.getEmail // Assuming getEmail returns Option[String]
} yield (person, email)
72
ответ дан 24 November 2019 в 09:10
поделиться

На самом деле я разделяю сомнение с вами. О варианте это действительно беспокоит меня, что 1) Существует эксплуатация накладных расходов, так как происходит ломор «Некоторые» обертки, созданные Everywred. 2) Я должен использовать много некоторых и вариант в моем коде.

Так что чтобы увидеть преимущества и недостатки этого решения о дизайне языка, мы должны учитывать альтернативы. Поскольку Java просто игнорирует проблему Nullability, это не альтернатива. Фактическая альтернатива обеспечивает язык программирования FATOM. Там нет нуля и ненужные типы и?, ?: Операторы вместо карты Scala / FlatMap / GetOrelse. Я вижу следующие пули в сравнении:

Преимущество вариантов:

  1. Упрощенный язык - никаких дополнительных языковых конструкций не требуется
  2. Униформа с другими монадическими типами

Преимущество NULLBALE:

  1. Более короткий синтаксис в типичных случаях
  2. Лучшая производительность (как вам не нужно создавать новые опционные объекты и лямбдас для карты, плоскость)

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

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

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

0
ответ дан 24 November 2019 в 09:10
поделиться

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

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

3
ответ дан 24 November 2019 в 09:10
поделиться

Разница в тонком. Имейте в виду, чтобы быть по-настоящему функцией Должен Возврат Вернуть значение - NULL на самом деле не считается «нормальным возвращаемым значением» в этом смысле, более нижний тип / ничего.

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

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

предоставлено, вы можете сделать что-то похожее с NULL - но это делает семантику звонков GetPerson2 Очевидно в силу того факта, что он возвращает вариант [человек] (приятная практическая вещь, кроме того, чтобы полагаться на кого-то, чтение доктора и получая NPE, потому что они не читают документ).

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

22
ответ дан 24 November 2019 в 09:10
поделиться

Сравните:

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)

Монадическое свойство bind , которое появляется в Scala как функция map , позволяет нам связывать операции с объектами, не беспокоясь о том, являются они «нулевыми» или нет.

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

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

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

31
ответ дан 24 November 2019 в 09:10
поделиться

Заблаговременно признав, что это - правдоподобный ответ, "Вариант - это монада".

0
ответ дан 24 November 2019 в 09:10
поделиться

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

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") )
9
ответ дан 24 November 2019 в 09:10
поделиться

Для меня варианты действительно интересны при работе с синтаксисом для понимания. Возьмем синтаксис предыдущий пример:

// 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

Если какое-либо из назначений будет Нет , то отцыМатьСёстра будет Нет , но не будет поднят NullPointerException . После этого вы можете безопасно передать fatherhersMothersSisterв функцию, принимающую опциональные параметры, не беспокоясь. Таким образом, вы не будете проверять на ноль и не будете заботиться об исключениях. Сравните это с java версией, представленной в примере synesso.

15
ответ дан 24 November 2019 в 09:10
поделиться

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

2
ответ дан 24 November 2019 в 09:10
поделиться

Думаю, ключ найден в ответе Синессо: Вариант не в первую очередь полезен как громоздкий псевдоним для нуля, а как полноценный объект, который затем может помочь вам с логикой.

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

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

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

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

Может, ты хочешь установить значение по умолчанию, если у тебя есть ноль. Давайте сравним 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(_))

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

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

2
ответ дан 24 November 2019 в 09:10
поделиться

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

Если вы не знаете, какие монады являются или если вы не замечаете, как они представлены в библиотеке Scala, вы не увидите, что вариант играет вместе, и вы можете Посмотрите, что вы пропустите. Есть много преимуществ для использования варианта вместо нулевого, что было бы примечательно, даже в отсутствие каких-либо монадских концепций (я обсуждаю некоторые из них в «Стоимости варианта / некоторые против NULL» Scala- Пользователь Поток списка рассылки Здесь ), но разговоры об этом изоляции вроде как разговоры о конкретном связанном типовом титере реализации списка, задаваясь вопросом, почему необходимо, все то время отсутствует на более общей контейнере / ITERATOR / алгоритм интерфейс. Здесь также работает более широкий интерфейс, и вариант обеспечивает модель присутствия и отсутствия этого интерфейса.

3
ответ дан 24 November 2019 в 09:10
поделиться

Это не для того, чтобы помочь избежать нулевой проверки, он должен заставить нулевую проверку. Точка становится понятной, когда у вашего класса имеет 10 полей, два из которых могут быть нулевыми. И ваша система имеет 50 других подобных классов. В мире Java вы пытаетесь предотвратить NPE на этих областях, используя некоторую комбинацию психического горесевера, имен, а также даже аннотаций. И каждый Java Dev не удается в этом в значительной степени. Класс опции не только делает «отнурительные» значения, визуально очищающие для любых разработчиков, пытающихся понять код, но позволяет компилятору принять это ранее невысказанный контракт.

7
ответ дан 24 November 2019 в 09:10
поделиться

[скопировано из этого комментария Daniel Spiewak ]

Если бы единственным способом использования Option был сопоставить с образцом, чтобы получить значения, тогда да, я согласен, что это ничего не улучшается по сравнению с нулевым значением. Однако вам не хватает * огромного * класса его функциональности. Единственный веская причина использовать вариант - если вы используете его более высокого порядка служебные функции. Фактически, вы нужно использовать его монадическую природу. Например (при условии, что определенная сумма обрезки API):

 val row: Option [Row] = database fetchRowById 42
ключ val: Option [String] = row flatMap {_ get «port_key»}
значение val: Option [MyType] = key flatMap (myMap get)
val результат: MyType = значение getOrElse defaultValue

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

 значение val = для {
row <- база данных fetchRowById 42
key <- строка получить "port_key"
value <- myMap получить ключ
} доходность
val результат = значение getOrElse defaultValue

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

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

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

6
ответ дан 24 November 2019 в 09:10
поделиться

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

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

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

1
ответ дан 24 November 2019 в 09:10
поделиться

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

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

8
ответ дан 24 November 2019 в 09:10
поделиться
Другие вопросы по тегам:

Похожие вопросы: