I have a function that looks like this:
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))
}
It defines 3 filter functions from Builder => Option[Builder]
(converting from optional parameters). I want to apply them to an existing builder
value, so in case of a None
, I can return itself, unchanged.
The code above is the best I could come up with, but it feels that I should somehow be able to do this with a Monoid - return the identity
in case of a None.
Unfortunately, I can't figure out how to define one that makes sense. Or, if there's a better/different way of doing this?
I'm using Cats, if that matters. Any ideas?
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)
}