Few days ago I started to learn Cats and I want to implement method appendOptional
for Map[String, _: Show]
.
I started with the following idea:
def appendOptional[T: Show](to: Map[String, String], values: (String, Option[T])*): Map[String, String] =
values.foldLeft(values) {
case (collector, (key, Some(value))) =>
collector + (key -> implicitly[Show[T]].show(value))
case (collector, _) => collector
}
And to use it like:
def createProps(initial: Map[String, String], name: Option[String], age: Option[Int])
val initial = Map("one" -> "one", "two" -> "two")
val props = appendOptional(initial, "name" -> name, "age" -> age)
I understand that this approach is quite naive and straightforward because implicitly[Show[T]].show(value)
will actually lookup for Show[Any]
.
Also, I had an idea to accept HList
with context bound, but I have not found any example of that.
One more variant is to create a lot of overloaded methods (as it is done in a lot of libraris):
def appendOptional[T1: Show, T2: Show](to: Map[String, String], v1: (String, Option[T1], v2: (String, Option[T2])))
Question: Is there a way to define context bound for varargs functions?
Strictly speaking, the first is the correct way to define the bound; varargs don't mean the type of arguments varies, only their number.
The way to achieve what you want with varying types is more involved and requires packing Show
instances together with values. E.g.
case class HasShow[A](x: A)(implicit val ev: Show[A])
def appendOptional(to: Map[String, String], values: (String, Option[HasShow[_]])*): Map[String, String] =
values.foldLeft(values) {
// value.ev.show(value.x)) can be extracted into a method on HasShow as well
case (collector, (key, Some(value: HasShow[a]))) =>
collector + (key -> value.ev.show(value.x))
case (collector, _) => collector
}
val props = appendOptional(initial, "name" -> name.map(HasShow(_)), "age" -> age.map(HasShow(_)))
You can sprinkle some more implicit conversions for HasShow
to simplify the call-site, but this way you can see what's going on better.
For this specific case I think a better and simpler solution would be
implicit class MapOp(self: Map[String, String]) extends AnyVal {
def appendOptional[A: Show](key: String, value: Option[A]) =
value.fold(self)(x => self + (key -> Show.show(x)))
}
val props = initial.appendOptional("name", name).appendOptional("age", age)