Подтвердить что ты не робот

Когда использовать опцию

Я изучаю Scala в течение некоторого времени и не могу четко понять использование опции. Это помогает мне избегать нулевых проверок, когда я связываю функции (согласно docs). Это ясно для меня:)

Далее я вижу, что Опция может быть своего рода индикатором для разработчика, где здесь возможно нулевое значение, и это нужно обработать. Это правда? Если да, я должен использовать Опцию, если это возможно? Например

class Racer {
    val car = new Car()
}

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

class Racer {
    var car = new Car()
}

Здесь я делаю так, чтобы автомобиль мог измениться. И возможно, кому-то назначить нулевой автомобиль. Должен ли я использовать опцию здесь? Если да, то я замечаю, что все поля моего класса являются кандидатами на опцию. И мой код выглядит следующим образом:

class Racer {
    var car: Option[Car] = None
    var currentRace: Option[Race] = None
    var team: Option[Team] = None
    ...
}

Хорошо ли это? Для меня это похоже на использование опции.

def foo(): Result = {
    if( something )
        new Result()
    else
        null
}

У меня есть метод, который может возвращать null. Должен ли я возвращать вариант? Должен ли я всегда делать это, если возможно, чтобы метод возвращал null?

Любые мысли об этом были бы полезны. Спасибо заранее!

Мой вопрос аналогичен Почему опция, но я думаю, что это не то же самое. Это больше о том, когда, а не почему.:)

4b9b3361

Ответ 1

Вам следует избегать null как можно больше в Scala. Он действительно существует только для взаимодействия с Java. Таким образом, вместо null используйте Option, когда возможно, что функция или метод может возвращать "нет значения" или когда логически допустимо для переменной-члена иметь "нет значения".

Что касается вашего примера Racer: Действительно ли Racer действительно, если он не имеет car, currentRace и team? Если нет, то вы не должны использовать эти параметры переменных-членов. Не просто используйте их, потому что теоретически можно назначить их null; сделайте это, только если логически вы считаете его допустимым объектом Racer, если какая-либо из этих переменных-членов не имеет значения.

Другими словами, лучше сделать вид, будто null не существует. Использование кода null в Scala является запахом кода.

def foo(): Option[Result] = if (something) Some(new Result()) else None

Обратите внимание, что Option имеет много полезных методов для работы.

val opt = foo()

// You can use pattern matching
opt match {
  case Some(result) => println("The result is: " + result)
  case None         => println("There was no result!")
}

// Or use for example foreach
opt foreach { result => println("The result is: " + result) }

Кроме того, если вы хотите запрограммировать в функциональном стиле, вы должны как можно больше избегать изменчивых данных, что означает: избегать использования var, использовать val вместо этого, использовать неизменяемые коллекции и создавать свои собственные классы насколько это возможно.

Ответ 2

Когда речь заходит о функциональном программировании в Scala, Option гораздо предпочтительнее, чем null, поскольку он безопасен по типу и отлично сочетается с другими конструкциями в функциональной парадигме.

В частности, вы можете легко написать идиоматический код, используя функции высокого порядка на Option. Этот Scala вариант Cheat Sheet является полезным для чтения на эту тему.

Ответ 3

В то время как Option может сделать определение класса более подробным, альтернативой является не знать, когда вам нужно проверить, определена ли переменная. Я думаю, что в вашем примере, если ваш класс был неизменным (все поля были val s), вам не понадобилось бы столько Option s.

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

val result = foo(x)
if (result != null) {
  println(result)
}

Если вместо этого вы определяете foo для возврата Option[Result], система типа заставляет клиента обрабатывать возможность результата undefined. Они также обладают всеми возможностями классов коллекций.

result foreach println

Дополнительным преимуществом использования метода collection с Option вместо тестирования для null является то, что если ваш метод расширен до многоэлементной коллекции (например, List), вам может и не понадобиться вообще измените свой код. Например, изначально вы можете предположить, что ваш Racer может иметь только одну команду, поэтому вы определяете val crew: Option[Crew], и вы можете проверить, является ли команда старше 30 с помощью crew.forall(_.age > 30).getOrElse(false). Теперь, если вы изменили определение на val crew: List[Crew], ваш старый код все равно будет компилироваться, и теперь вы проверите, будут ли все члены вашего экипажа старше 30 лет.

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

val resource = Option(this.getClass.getResource("foo"))

Если getResource возвращено null, тогда resource равно None, а в противном случае - Some[URL]. Круто!

К сожалению, Option, вероятно, будет иметь некоторые накладные расходы, потому что это связано с дополнительным созданием объекта. Однако эта служебная нагрузка минимальна по сравнению с исключением нулевого указателя!

Ответ 4

Идея Options состоит в том, чтобы избавиться от null, поэтому да, вы должны использовать их вместо возврата "null или значение". Это также почему, если вы хотите моделировать необязательные поля (связь 0..1 с другими объектами), использование Option - это, безусловно, хорошо.

Небольшой недостаток заключается в том, что он объявляет объявление класса с большим количеством необязательного поля немного подробным, как вы отметили.

Еще одна вещь, в scala, вам предлагается использовать "неизменяемые объекты", поэтому в вашем примере поля должны быть немного val s.;)