I think in your case A => M[A]
structure is a bit superfluous. The filter functions you use in the example are actually equivalent to Option[Builder => Builder]
. That's because you don't use their Builder
argument to decide whether the result should be Some
or None
. And you can further simplify the functions to Builder => Builder
with .getOrElse(identity)
.
Here are 2 implementations that use this idea. They don't even really rely on cats.
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)
}
Another possibility is to flatten
the List
of Option
s, which simply removes None
s without coercing them to 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)
}