Вопрос

Я не могу понять смысл Option[T] Класс в Скале. Я имею в виду, я не могу увидеть никаких преимуществ 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 в обоих случаях. :(

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

Это было полезно?

Решение

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


Ты прибил это здесь:

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

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

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

Конечно, из -за своей совместимости с Java Scala позволяет nulls так же, как 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)

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

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

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

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

Конечно, вы можете сделать что -то подобное с NULL - но это делает семантику призывы getPerson2 очевидно в силу того, что он возвращается Option[Person] (Хорошая практическая вещь, кроме как полагаться на кого -то, кто читает документ и получение NPE, потому что они не читают документ).

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

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

// 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, представленной в Синессо пример.

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

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] против нулевой проверки является то, что опция является герметичным классом, поэтому компилятор Scala выпустит предупреждение, если вы пренебрегаете кодированием или ни одного случая. В компиляторе есть флаг компилятора, который превратит предупреждения в ошибки. Таким образом, можно предотвратить неспособность справиться с делом «не существует» во время компиляции, а не во время выполнения. Это огромное преимущество в отношении использования нулевого значения.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Как вы продемонстрировали, вариант может сделать одну вещь, чтобы подражать нулю; Затем вам нужно проверить на необычайную ценность «нет» вместо необычайной стоимости «нулевой». Если вы забудете, в любом случае, произойдут плохие вещи. Опция действительно делает его менее вероятной случайностью, так как вам нужно напечатать «Get» (что должно напоминать вам, что это возможно Null, э -э, я имею в виду нет), но это небольшое преимущество в обмен на объект дополнительной обертки.

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

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

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

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

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

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

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

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

Это не огромный Улучшение, но если вы все добавите, оно того стоит везде, сохранив очень высокопроизводительный код (где вы хотите избежать даже крошечных накладных расходов на создание некоторых (x) объекта обертки).

Использование матчей на самом деле не так полезно само по себе, кроме как устройство, чтобы предупредить вас о случае NULL/NO. Когда это действительно полезно, когда вы начинаете его цеплять, например, если у вас есть список вариантов:

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, или, написав свои собственные вспомогательные методы, вы можете иметь функциональность своей опции, но не отказаться от языка Java:

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

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

Заранее признавая, что это ответ, вариант - это Монада.

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

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

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top