Применить все функции фильтра к значению

У меня есть функция, которая выглядит так:

def createBuilder(builder: InitialBuilder, name: Option[String], useCache: Boolean, timeout: Option[Long]): Builder = {
    val filters: List[Builder => Option[Builder]] = List(
      b => name.map(b.withName),
      b => if (useCache) Some(b.withCache) else None,
      b => timeout.map(b.withTimeout))

    filters.foldLeft(builder)((b,filter) => filter(b).getOrElse(b))
}

Он определяет 3 функции фильтра из Builder => Option[Builder] (преобразование из необязательных параметров). Я хочу применить их к существующему значению builder, поэтому в случае None я могу вернуть себя без изменений.

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

К сожалению, я не могу понять, как определить тот, который имеет смысл. Или, если есть лучший/другой способ сделать это?

Я использую Cats, если это имеет значение. Любые идеи?


person Igal Tabachnik    schedule 03.07.2017    source источник
comment
вернуть идентификатор в случае None. Вернуть идентификатор (ноль) моноида? Это означает, что если у вас будет Monoid[String].zero, вы вернете пустую строку, это нормально?   -  person Yuval Itzchakov    schedule 03.07.2017
comment
@YuvalItzchakov хм, я уверен, что имел в виду применение функции identity для возврата исходного builder...   -  person Igal Tabachnik    schedule 03.07.2017
comment
А, так ты говоришь о Monoid[Builder]?   -  person Yuval Itzchakov    schedule 03.07.2017
comment
Да, звучит правильно :)   -  person Igal Tabachnik    schedule 03.07.2017
comment
Мне трудно понять, как создание моноида для строителя создаст более чистую абстракцию для этого.   -  person Yuval Itzchakov    schedule 03.07.2017
comment
@YuvalItzchakov без понятия! Вот почему я здесь, наверное :D   -  person Igal Tabachnik    schedule 03.07.2017
comment
Меня также беспокоит, что тип фильтра A => M[A], который выглядит как Клейсли. Но опять же, не знаю, что здесь делать...   -  person Igal Tabachnik    schedule 03.07.2017
comment
Но хотите ли вы абстрагироваться от типа? Есть ли у вас какие-либо другие типы, подобные строителям, которые требуют обобщения?   -  person Yuval Itzchakov    schedule 03.07.2017
comment
@YuvalItzchakov на самом деле не этот конкретный, но есть и другой устаревший код шаблона построителя, от которого можно было бы извлечь выгоду.   -  person Igal Tabachnik    schedule 03.07.2017
comment
На самом деле это не похоже на A => M[A]. Кажется более естественным сопоставить вашу filters со списком эндоморфных функций val fs: List[Builder => Builder] = filters.map(f => (b: Builder) => f(b).getOrElse(b)), а затем foldK этой List. (Я думаю, что foldK должен работать автоматически с -Ypartial-unification, но я не могу заставить его работать, поэтому вам, возможно, придется указать аргументы типа вручную. Кроме того, кошки сворачивают список в обратном порядке, потому что он использует compose вместо andThen для combineK 9_. )   -  person Kolmar    schedule 03.07.2017


Ответы (1)


Я думаю, что в вашем случае структура A => M[A] немного лишняя. Функции фильтра, которые вы используете в примере, на самом деле эквивалентны Option[Builder => Builder]. Это потому, что вы не используете их аргумент Builder, чтобы решить, должен ли результат быть Some или None. И вы можете еще больше упростить функции до Builder => Builder с помощью .getOrElse(identity).

Вот 2 реализации, которые используют эту идею. Они даже особо не полагаются на кошек.

def createBuilder(
  builder: InitialBuilder, name: Option[String], useCache: Boolean, timeout: Option[Long]
): Builder = {
  def builderStage[T](param: Option[T])(modify: T => Builder => Builder): Builder => Builder =
    param.fold(identity[Builder](_))(modify)

  val stages: List[Builder => Builder] = List(
    builderStage(name)(n => _ withName n),
    // `Boolean` is equivalent to `Option[Unit]`, and we convert it to that representation
    // Haskell has a special function to do such a conversion `guard`.
    // In Scalaz you can use an extension method `useCache.option(())`.
    // In cats a similar `option` is provided in Mouse library.
    // But you can just write this manually or define your own extension
    builderStage(if (useCache) ().some else none)(_ => _.withCache),
    builderStage(timeout)(t => _ withTimeout t)
  )

  // It should be possible to use `foldK` method in cats, to do a similar thing.
  // The problems are that it may be more esoteric and harder to understand, 
  // it seems you have to provide type arguments even with -Ypartial-unification,
  // it folds starting from the last function, because it's based on `compose`.
  // Anyway, `reduceLeft(_ andThen _)` works fine for a list of plain functions. 
  stages.reduceLeft(_ andThen _)(builder)
}

Другая возможность заключается в flatten List из Options, что просто удаляет Nones, не принуждая их к identity:

def createBuilder2(
  builder: InitialBuilder, name: Option[String], useCache: Boolean, timeout: Option[Long]
): Builder = {
  val stages: List[Option[Builder => Builder]] = List(
    name.map(n => _ withName n),
    if (useCache) Some(_.withCache) else None,
    timeout.map(t => _ withTimeout t)
  )

  stages.flatten.reduceLeft(_ andThen _)(builder)
}
person Kolmar    schedule 03.07.2017
comment
Удивительно! Большое спасибо, безусловно, отличные идеи здесь стоит изучить! - person Igal Tabachnik; 04.07.2017