Лучший способ моделирования необязательных аргументов

Как следует из названия, как лучше всего моделировать необязательные аргументы в Scala?

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

Либо потому, что для этого аргумента существует значение по умолчанию, либо сам аргумент вообще не нужен (например, флаг конфигурации или отладки); обратите внимание, что на Java я бы, вероятно, передал null этим аргументам.


Это часто задаваемые вопросы сообщества Scala, созданные специально новичками.

Например:

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

Bergi 12.12.2020 11:37

«Либо потому, что для этого аргумента существует значение по умолчанию». Что ж, этот случай поддерживается языком; нет никакого шаблона.

Alexey Romanov 12.12.2020 22:25

@AlexeyRomanov правда, но у них есть некоторые оговорки. Пожалуйста, проверьте обновленный ответ и дайте мне знать, если у вас есть какие-либо отзывы :) - Для протокола, этот ответ был добавлен в FAQ по Scala Поэтому я хочу, чтобы он был максимально полным и объективным.

Luis Miguel Mejía Suárez 12.12.2020 23:22
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
5
3
2 239
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Простой и принятый сообществом ответ

В целом, сообщество пришло к единому мнению, что все предложения или альтернативы, перечисленные ниже, не стоят того из-за их компромиссов.
Таким образом, рекомендуемое решение состоит в том, чтобы просто использовать тип данных Option и вручную/явно обернуть значение в Some

def test(required: Int, optional: Option[String] = None): String =
  optional.map(_ * required).getOrElse("")

test(required = 100) // ""
test(required = 3, optional = Some("Foo")) // "FooFooFoo"

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

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

Альтернативы и предложения

Неявные преобразования

Из-за шаблонности предыдущего решения снова и снова упоминается распространенная альтернатива использования неявных преобразований; например:

implicit def a2opt[A](a: A): Option[A] = Some(a)

Чтобы предыдущую функцию можно было вызывать так:

test(required = 3, optional = "Foo")

Обратной стороной этого является то, что неявное преобразование скрывает тот факт, что optional был необязательным аргументом (ну, конечно, если бы он назывался по-другому) и что такое преобразование может быть применено во многих других (непреднамеренных) частях кода; по этой причине неявные преобразования, как правило, не рекомендуются.

Альтернативой может быть использование методов расширения вместо неявных преобразований, что-то вроде optional = "foo".opt. Однако тот факт, что метод расширения требует добавления еще большего количества кода, а вызов сайта по-прежнему содержит некоторый шаблон, делает этот метод посредственной промежуточной точкой. (отказ от ответственности, если вы используете кошек, у вас уже есть такой метод расширения в области видимости .some, поэтому вы можете использовать его).

Аргументы по умолчанию

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

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

  1. У вас не всегда есть значение по умолчанию, иногда вы только хотите знать, было ли передано значение или нет. Например, флаг.

  2. Если он находится в своей группе параметров, вам все равно нужно добавить пустые скобки, что может выглядеть некрасиво (это, конечно, субъективное мнение).

def transact[A](config: Config = Config.default)(f: Transaction => A): A

transact()(tx => ???)
  1. У вас может быть только одна перегрузка с аргументами по умолчанию.
object Functions {
  def run[A](query: Query[A], config: Config = Config.default): A = ???
  def run[A](query: String, config: Config = Config.default): A = ???
}

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

Перегрузка

Другой распространенный обходной путь — предоставить перегруженную версию метода; например:

def test(required: Int, optional: String): String =
  optional * required

def test(required: Int): String =
  test(required, optional = "")

Преимущество этого заключается в том, что он инкапсулирует стандартный сайт определения, а не сайт по вызову; также облегчает чтение кода и хорошо поддерживается инструментами.
Тем не менее, самым большим недостатком является то, что это плохо масштабируется, если у вас есть более одного необязательного аргумента; например, для трех аргументов вам нужно семь (7) перегрузок.

Но если у вас много необязательных параметров, может быть, лучше запросить только один единственный аргумент Config / Context и использовать Builder.

Шаблон строителя.

def foo(data: Dar, config: Config = Config.default)

// It probably would be better not to use a case class for binary compatibility.
// And rather define your own private copy method or something.
// But that is outside of the scope of this question / answer.
final case class Config(
    flag1: Option[Int] = None,
    flag2: Option[Int] = None,
    flag3: Option[Int] = None
) {
  def withFlag1(flag: Int): Config =
    this.copy(flag1 = Some(flag))

  def withFlag2(flag: Int): Config =
    this.copy(flag2 = Some(flag))

  def withFlag3(flag: Int): Config =
    this.copy(flag3 = Some(flag))
}

object Config {
  def default: Config = new Config()
}

Запрос на нативную поддержку

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

Примеры таких предложений:

Заключение

Как всегда, выбирайте метод(ы) для использования в зависимости от вашего конкретного случая и API, который вы хотите предоставить.


Скала 3

Может быть, введение типов объединения может открыть возможность более простого способа кодирования необязательных аргументов?

На мой взгляд, есть только одно эмпирическое правило: избегать явной передачи None.

Дос:

  • значения параметров по умолчанию
  • Option со значением по умолчанию None
  • перегрузки

Не:

  • Option без значения по умолчанию None
  • Option с Some значением по умолчанию

Было бы хорошо объяснить обоснование каждого из этих правил :)

Luis Miguel Mejía Suárez 12.12.2020 23:26

Я добавил основное обоснование, думаю, этого достаточно.

Ava 13.12.2020 18:16

Другие вопросы по теме