将所有过滤器函数应用于值

时间:2017-07-03 13:56:44

标签: scala functional-programming scalaz scala-cats monoids

我有一个看起来像这样的函数:

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))
}

它定义了Builder => Option[Builder]的3个过滤函数(从可选参数转换)。我想将它们应用于现有的builder值,因此如果是None,我可以自行返回,不会更改。

上面的代码是我能想到的最好的代码,但我觉得我应该能够用Monoid做到这一点 - 如果是None,则返回identity

不幸的是,我无法弄清楚如何定义一个有意义的。或者,如果有更好/不同的方式吗?

我是否正在使用Cats,如果这很重要的话。有什么想法吗?

1 个答案:

答案 0 :(得分: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 OptionNone,只需移除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) }

        Mockito.when(clientResource.getJobStatus(JOB_ID))
            .thenReturn(createJobStatus(JOB_ID, com.test.models.JobStatus.State.IDLE))
            .thenReturn(createJobStatus(JOB_ID, com.test.models.JobStatus.State.STARTING))
            .thenReturn(createJobStatus(JOB_ID, com.test.models.JobStatus.State.RUNNING));