Использование Option повсеместно кажется немного неловким. Я делаю что-то неправильно? - PullRequest
7 голосов
/ 19 марта 2010

В результате статей, которые я прочитал о классе Option, который помогает вам избежать исключения NullPointerException, я начал использовать его повсеместно. Представьте себе что-то вроде этого:

var file:Option[File] = None

и позже, когда я его использую:

val actualFile = file.getOrElse(new File("nonexisting"))
if(actualFile.getName.equals("nonexisting")) { // instead of null checking

}
else { // value of file was good

}

Подобные вещи не кажутся мне такими «правильными». Я также заметил, что .get устарела. , Это то, что вы, ребята, делаете с Option тоже, или я ошибаюсь?

Ответы [ 5 ]

15 голосов
/ 19 марта 2010

Обычно не рекомендуется возвращать Option, а затем использовать getOrElse для получения некоторого значения часового, что означает "не найден". Вот для чего предназначен Option: чтобы показать, что значение не найдено!

Option действительно показывает свою мощь, когда используется в сочетании с конструкциями функционального программирования, такими как map и foreach. Это наиболее эффективно при работе с несколькими вариантами. Например, предположим, что я пишу метод, который принимает строку и возвращает мне файл, но только если файл существует и является файлом, а не каталогом:

import java.io._;
def niceFile1(s: String): File = {
  val f = new File(s);
  if (f.exists && !f.isDirectory) f else null
}
def niceFile2(s: String): Option[File] = {
  val f = new File(s);
  if (f.exists && !f.isDirectory) Some(f) else None
}

Пока что использовать null проще - по крайней мере, пока вы не забудете, что это может дать вам null, и вы получите NPE. В любом случае, давайте теперь попробуем его использовать.

def niceFopen1(s: String) = {
  val f = niceFile1(s);
  if (f!=null) new FileInputStream(f) else null;
}
def niceFopen2(s: String) = niceFile2(s).map(f => new FileInputStream(f))

Смотри, что случилось! В первом случае нам приходилось выполнять логические тесты вручную и создавать временные переменные. Тьфу! Во втором случае map сделал всю грязную работу за нас: None был сопоставлен с None, а Some(file) был сопоставлен с Some(fileinputstream). Легко!

Но это еще лучше. Может быть, мы хотим найти размер целой пачки файлов:

def totalSize2(ss: Seq[String]) = {
  (0L /: ss.flatMap(niceFile2)){(sum,f) => sum+f.length}
}

Подождите, что здесь происходит - как насчет всех None? Разве мы не должны обращать внимание и как-то с ними обращаться? Ну, вот и приходит flatMap: он объединяет все ответы в один список. None - это ответ нулевой длины, поэтому он игнорирует его. Some(f) имеет один ответ - f - поэтому он помещает его в список. Затем мы используем сгиб, чтобы сложить все длины - теперь, когда все элементы в списке допустимы. Довольно мило!

12 голосов
/ 19 марта 2010

Это хорошая идея, чтобы не разрешать значение Option, но применять логику к чему бы то ни было:

findFile.foreach(process(_))

В основномэто обрабатывает File, если он найден, и ничего не делает иначе (и эквивалентно первому for пониманию Томаса, потому что for компилируется с вызовом foreach).Это более краткая версия:

findFile match {
  case Some(f) => process(f)
  case None =>
}

Более того, в этом есть то, что вы можете связывать операций, что-то вроде:

(findLiveFile orElse fileBackupFile orElse findTempFile).foreach(process(_)
7 голосов
/ 19 марта 2010

В большинстве случаев вы бы использовали сопоставление с шаблоном

file match {
   case Some(f) => { .. } //file is there
   case _ => { .. } //file is not there 
}

Если вас интересует только файл, если он там, вы можете использовать для выражения

for(f <- file) { //file is there 
}

Затем вы можете связать выражения для работы на нескольких уровнях в контейнерах

for{ 
  option <- List(Some(1), None, Some(2))
  f <- option
} yield f

res0: List[Int] = List(1, 2)

В качестве альтернативы вы можете использовать isDefined и получить:

if(option.isDefined) {
   val x = option.get;
} else {
}

get не считается устаревшим в Scala 2.8.0. Beta-1.

2 голосов
/ 19 марта 2010

Вот альтернатива:

var file:Option[File] = None
// ...
file map (new File(_)) foreach { fh =>
  // ...
}

Однако, если вам нужно что-то сделать, если файл существует, и что-то еще, если это не так, оператор match более уместен:

var file:Option[File] = None
// ...
file map (new File(_)) match {
  case Some(fh) =>
    // ...
  case None =>
    // ...
}

Это почти то же самое, что и выражение if, но мне нравится, что оно лучше связывает природу для таких вещей, как Option, где я также хочу извлечь значение.

1 голос
/ 19 марта 2010

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

1. Файл не должен быть нулевым

load(file.get())

Будет выдано исключение, когда вы попытаетесь фактически использовать файл. Быстро проваливай, ура!

2. Файл имеет значение null, но в этом случае у нас есть разумное значение по умолчанию:

load(file.getOrElse(new File("/defaultFile")))

3. Две ветви логики, основанные на том, существует ли файл:

  file match {
    case Some(f) => { .. } //file is there
    case _ => { .. } //file is not there 
  }

4. Нет, если файл пуст, иначе молча провалиться. Я не думаю, что это очень часто для меня предпочтительнее в реальной жизни:

for (f <- file) {
//do some stuff
}

Или, может быть, более ясно?

if (f.isDefined) {
  //do some stuff
}
...