Возвращение Either / Option / Try / Or считается жизнеспособным / идиоматическим подходом, когда функция имеет предварительные условия для аргументов? - PullRequest
2 голосов
/ 27 июня 2019

Прежде всего, я очень плохо знаком с Scala и не имею никакого опыта написания с ним производственного кода, поэтому мне не хватает понимания того, что считается хорошей / лучшей практикой в ​​сообществе.Я наткнулся на эти ресурсы:

  1. https://github.com/alexandru/scala-best-practices
  2. https://nrinaudo.github.io/scala-best-practices/

Там упоминается, что бросать исключения - не очень хорошая практика, котораязаставил меня задуматься о том, что было бы хорошим способом определить предварительные условия для функции, потому что

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

После небольшого исследования кажется, что использование Option / Either / Try / Or (скалактическое) - лучший подход, так как вы можете использовать что-то вроде T Or IllegalArgumentException в качестве возврататип, чтобы четко указать, что функция на самом деле является частичной, используя исключение как способ хранения сообщения, которое может быть заключено в другие исключения.

Однако, не имея опыта работы с Scala, я не совсем понимаю, является ли это действительно жизнеспособным подходом для реального проекта или использование Predef.require - это путь.Я был бы признателен, если бы кто-нибудь объяснил, как обычно это делается в сообществе Scala и почему.

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

Ответы [ 3 ]

6 голосов
/ 27 июня 2019

Option или Either - определенно способ функционального программирования.

С Option важно документировать , почему None может быть возвращено.

При Either левая сторона - это неудачное значение («ошибка»), а правая сторона - это успешное значение.Левая сторона не обязательно должна быть Exception (или ее подтипом), это может быть простое сообщение об ошибке String (псевдонимы типа здесь ваш друг) или пользовательский тип данных, подходящий для вашего приложения..

Например, я обычно использую следующий шаблон, когда обработку ошибок с помощью Either:

// Somewhere in a package.scala
type Error = String // Or choose something more advanced
type EitherE[T] = Either[Error, T]

// Somewhere in the program
def fooMaybe(...): EitherE[Foo] = ...

Try следует использовать только для переноса unsafe (в большинстве случаев простой Java) код, который дает вам возможность сопоставления с шаблоном по результату:

Try(fooDangerous()) match {
   case Success(value) => ...
   case Failure(value) => ...
}

Но я бы предложил использовать только Try локально, а затем перейти к вышеупомянутым даннымтипы оттуда.

Некоторые расширенные типы данных, такие как cats.effect.IO или monix.reactive.Observable, содержат встроенную обработку ошибок.

Я бы также посоветовал изучить cats.data.EitherT для класса типов-основанная обработка ошибок.Прочитайте документацию, это определенно стоит.


Как примечание, для всех, кто прибывает из Java, Scala рассматривает все Exception с, как Java обрабатывает RuntimeException с.Это означает, что даже когда небезопасный фрагмент кода из одной из ваших зависимостей выдает (проверено) IOException, Scala никогда не потребует от вас catch или иным образом обработать исключение.Так что, как правило, при использовании Java-зависимостей почти всегда заключайте их в Try (или IO, если они выполняют побочные эффекты или блокируют поток).

5 голосов
/ 27 июня 2019

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

Обычно не рекомендуется создавать исключения, так как они нарушают законы FP.Вы можете использовать любую библиотеку, которая может возвращать более продвинутый тип, чем Option, например Scalaz Validation, если вам нужно составить результаты способами, которые неудобны с Option.

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

  1. Введите ограниченные аргументы, которые обеспечивают предварительные условия.Пример: val i: Int Refined Positive = 5 на основе https://github.com/fthomas/refined. Вы также можете написать свои собственные типы, которые обертывают примитивные типы и утверждают некоторые свойства.Проблема здесь в том, что если у вас есть аргументы, которые имеют несколько взаимозависимых допустимых значений, которые являются взаимоисключающими для каждого аргумента.Например x > 1 and y < 1 или x < 1 and y > 1.В таком случае вы можете вернуть необязательное значение вместо использования этого подхода.
  2. Частичные функции, которые по своей сути напоминают необязательные типы возврата: case i: Int if i > 0 => ....Документы: https://www.scala -lang.org / api / 2.12.1 / scala / PartialFunction.html .

Например: PF's def lift: (A) ⇒ Option[B] преобразует PF в вашу обычную функцию.

Превращает эту частичную функцию в простую функцию, возвращающую результат Option.

Что аналогично возврату опции.Проблема с частичными функциями в том, что они немного неудобны в использовании и не полностью совместимы с FP.

Я думаю, Predef.require относится к очень редким случаям, когда вы не хотите, чтобы какие-либо недопустимые данные создавались ибольше похоже на остановку «все, если это случается».Примером может быть то, что вы получите аргументы, которые вы никогда не должны были получить.

2 голосов
/ 27 июня 2019

Вы используете тип возврата функции, чтобы указать тип результата.
Если вы хотите описать функцию, которая может произойти сбой по какой-либо причине, из типов, которые вы упомянули, вы, вероятно, вернете Try или Either: я собираюсь «попытаться» дать вам результат, или я собираюсь вернуться «или» успех или неудача.
Теперь вы можете указать пользовательское исключение

case class ConditionException(message: String) extends RuntimeException(message)

, что вы вернетесь, если ваше условие не будет выполнено, например,

import scala.util._
def myfunction(a: String, minLength: Int): Try[String] = {
  if(a.size < minLength) {
    Failure(ConditionException(s"string $a is too short")
  } else {
    Success(a)
  }
}

и с Либо вы получите

import scala.util._
def myfunction(a: String, minLength: Int): Either[ConditionException,String] = {
  if(a.size < minLength) {
    Left(ConditionException(s"string $a is too short")
  } else {
    Right(a)
  }
}

Не то, чтобы решение Either четко указывало на ошибку, которую может вернуть ваша функция

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