Отримати значення параметра або створити виняток


77

Враховуючи параметр, який ідіоматичний спосіб отримати його значення або спробувати виняток?

def foo() : String = {
  val x : Option[String] = ...
  x.getOrException()
}

12
Чому ні .get?
Таємничий Ден

@MyseriousDan Я щойно натрапив на цю справу. У мене є одна ситуація, коли я абсолютно впевнений на 100%, що вона повинна повернути Деякі, а якщо ні, це серйозна проблема, і її потрібно кинути. Але я хочу кинути власний виняток і будь-які неоднозначні NoSuchElementException. Крім того, більшу частину часу цей метод може безпечно повернути None.
Kai Sellgren

1
Справжнє питання полягає в тому, чому ваш тип каже, що його може не бути, якщо ви знаєте, що це не так. Якщо ви можете висловити свої знання типами, це, як правило, найкраще. Це може передбачати дещо переформування ваших занять, але, як правило, це буде набагато приємніше в довгостроковій перспективі.
Таємничий Ден

1
Відповідь @TravisBrown має бути прийнятою, а не моєю. Моє не є ідіоматичним, я написав його, коли ще був "новачком" (я все ще є, але рівень 2). Нехай це буде прикладом того, чого не можна робити ... ;-)
Себастьян Н.

Ви робите це неправильно, якщо вам потрібно створити виняток. Ідіоматичним способом використання Option є повернення None та взагалі не викидання винятку.
Приянк Десай

Відповіді:


46

(РЕДАКТУВАТИ: це не найкращий чи найідіоматичніший спосіб це зробити. Я написав це, коли не був знайомий із Scala. Я залишаю це тут для прикладу, як цього не робити. Сьогодні я б зробив як @TravisBrown)

Я думаю, це справді зводиться до двох речей:

  • наскільки ви впевнені , що цінність тут є
  • як ти хочеш реагувати, якщо ні?

Якщо на той момент у вашому коді ви очікуєте, що значення буде там, а у віддаленому випадку, якщо це не ви хочете, щоб ваша програма швидко вийшла з ладу , то я б зробив лише нормальний getі дозволив Scala кинути a, NoSuchElementExceptionякщо не було значення :

def foo (): Рядок = {
  val x: Option [String] = ...
  x.get
}

Якщо ви хочете розглянути справу інакше (киньте власний виняток), я думаю, що більш елегантний спосіб буде виглядати так:

def foo (): Рядок = {
  val x: Option [String] = Немає
  x збіг {
    case Деякі (value) => value
    case None => кинути новий MyRuntimeException ("бла")
  }
} 

І звичайно, якщо ви хочете надати своє власне альтернативне значення для випадку, яке Optionє, Noneви просто використаєте getOrElse:

def foo (): Рядок = {
  val x: Option [String] = Немає
  x.getOrElse ("моє альтернативне значення")
} 

Звичайно, слід зазначити, що співставлення з деяким (значення) просто для того, щоб витягти те саме значення, не є ідіоматичною шкалою, і тому getOrElseслід віддавати перевагу, якщо це все, що хочеться зробити. Я просто хотів проілюструвати випадок "якщо визначено, оцінюйте до X, якщо ні, киньте виняток".
Себастьян Н.

1
Щоб процитувати Гійом Belrose : «Я припускаю , що я до сих пір на аматорському годину тоді :-)»
Sebastian N.

14
Ви також можете поєднати пропозиції @ SebastianN., Додавши виняток приблизно getOrElseтак:x.getOrElse(throw new MyRuntimeException("message"))
Jona

4
@TravisBrown: отримавши більше досвіду Scala, я повинен сказати, що ти маєш рацію. Збіг за таким варіантом замість використання getOrElse є непотрібним і не ідіоматичним. Ваша відповідь повинна бути прийнятою.
Себастьян Н.

132

throw«Затвердження» дійсно вираз в Scala, і має тип Nothing, який є підтипом будь-якого іншого типу. Це означає, що ви можете просто використовувати звичайний старий getOrElse:

def myGet[A](oa: Option[A]) = oa.getOrElse(throw new RuntimeException("Can't."))

Ти справді, справді не повинен цим займатися.


1
Чому ні? Який більш елегантний / ідіоматичний спосіб зробити це? Відповідність шаблону?
Себастьян Н.

7
Ви отримуєте набагато більше пробігу від системи типу, якщо моделюєте відмови як значення протягом усієї програми (і зберігаєте винятки для справді виняткових проблем, наприклад OutOfMemoryError).
Тревіс Браун,

23
Я не бачу переваг в моделюванні невдач як значень для будь-яких непоправних проблем (серед яких явно є OOME). Візьмемо, наприклад, читання метаданих із конфігураційного файлу. Для належного виконання програми потрібен чітко сформований файл конфігурації. Якщо файл конфігурації містить помилкові записи через помилку користувача, програма не може продовжувати роботу. Тому видається доцільним розгортати стек і відступати в таких сценаріях.
Джонатан Нойфельд,

1
@JonathanNeufeld Коментар Тревіса Брауна звучить як більш пуристичний підхід ФП. Це не для всіх / кожного випадку використання. Я більше на вашому кінці - просто під заставу і не варто занадто сильно керувати логікою / химерними типами систем.
StephenBoesch,

Я не думаю, що @TravisBrown є пуристом, інші його відповіді на SO показують інакше. Однак метання або моделювання невдачі справді є питанням контексту. У багатьох випадках збій програми не є варіантом (але для непоправної помилки, такої як OOME). У цьому випадку управління відмовою повинно бути модельним у всьому коді та простежуватися за проектом.
Juh_

14

Просто використовуйте метод .get.

def get[T](o:Option[T]) = o.get

Він викине NoSuchElementException, якщо o є екземпляром None.

В основному, я працював би з такими варіантами:

def addPrint(oi:Option[Int]) = oi.map(_+1).foreach(println)
addPrint(Some(41))
addPrint(Some(1336))
addPrint(None)

щоб уникнути вашого конкретного запитання.


4
Я припускав, що ОП хоче більше контролю над кинутим винятком, але навіть якщо ні, getце погана ідея - це просто документи про те, що ти робиш щось неприємне. Використання getOrElseта явне створення винятку важче пропустити, коли ви вирішите, що це не просто одноразовий код і хочете зробити його безпечнішим.
Тревіс Браун,

Звичайно, в основному мені здається, що ви ніколи не повинні використовувати будь-який варіант отримання опцій, якщо вам цього не потрібно. Я б використовував foreach або карту для обчислень з опціями.
Фелікс

12

Сподіваюся, це допоможе вам зрозуміти, як зображати помилки (і загалом ефекти) за допомогою типів.

Стратегії обробки помилок у функціональній Scala

  • Використовуйте Optionдля повернення необов’язкових значень. Наприклад - не вдається знайти сутність у сховищі.

  • Використовуйте, Option(possiblyNull)щоб уникнути випадків Some(null).

  • Використовуйте Either[Error, T]для повідомлення про очікувану помилку. Наприклад - формат електронної пошти неправильний, не вдається проаналізувати рядок на число тощо.

  • Змоделюйте свої помилки як ADT (просто кажучи різновид ієрархій типів), щоб використовувати їх, наприклад, зліва від обох, щоб представляти більш складні сценарії помилок.

  • Кидок Exceptionлише для сигналізації про несподівані та невиправні помилки. Як відсутній конфігураційний файл.

  • Використовуйте Either.catchOnlyабо ( Tryабо Cats.IOдодатково), а не блок блокування для обробки несподіваних несправностей. Підказка: Ви все ще можете використовувати ADT, але поширювати їх з підкинутих. Детальніше про EitherпротиTry .

  • Використовуйте Validatedтип даних з бібліотеки Cats для накопичення помилок, а не швидкого відмови ( Either), але віддайте перевагу будь-якому з них на рівні модуля, щоб спростити склад програми (мати однакові типи). Наприклад - перевірка даних форми, накопичення помилок аналізу.

  • Використовуйте згадані типи і не оптимізуйте програму превентивно - оскільки, швидше за все, вузькі горловини будуть в бізнес-логіці, а не в типах ефектів.

Такий підхід спростить обслуговування та оновлення вашого коду, оскільки ви можете міркувати про це, не переходячи до специфікації реалізації (вона ж локальна-міркування). Також - зменшіть помилки - ви не можете пропустити помилку в типі. І скласти програму легше (за допомогою map, flatMapі іншими комбінаторами) - так як це простіше , на рівні типу, а не з нелокальними винятками і побічними ефектами.
Детальніше про вивчення функціональної Scala.


Але майте на увазі, що іноді при такому підході типи можуть складатись, і складніше складати речі. Дано, наприклад: x: Future[Either[Error, Option[T]]]Що ви можете зробити:

  • Використовуйте mapта flatMapв поєднанні зі збігом зразків для складання різних значень таких типів, наприклад:
x.faltMap { case Right(Some(v)) => anotherFuture(v); case Left(er) => ... }
  • Якщо це не допомагає, ви можете спробувати використовувати MonadTransformers (не лякайтеся назви, це просто обгортки навколо типів ефектів, таких як Either і Future)
  • Крім того, можна спростити свої помилки ADT, розширивши їх з Throwable, щоб об’єднати з Future, тоді це будеFuture[Option[T]]


І нарешті, у вашому випадку одним із варіантів буде:

def foo() : Either[Error, String] = {
    val x : Option[String] = ...
    x match {
        case Some(v) => Right(v)
        case None => Left(Error(reason))
    }
}

Досить приємний звіт. Ви закінчили останню "думку" в середині речення;)
Стівен Бош,

@YordanGeorgiev посилання прогнило, видалено
Альберт Бікеєв

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.