Reputation: 6669
I have several Map[String, String]
and want to merge them so values remain the same. By default Map semigroup will concatenate strings, so I'm using Tags.LastVal
.
Currently I'm doing following:
m1.mapValues(Tags.LastVal) |+| m2.mapValues(Tags.LastVal)
This mapping looks verbose and tedious.
Can I somehow tell compiler to use specific Semigroup in the scope, like Semigroup[Map[K, V @@ LastVal]
rather than default one?
Upvotes: 2
Views: 48
Reputation: 139038
Yes, you can make the instance you want implicit in the scope you need and it'll be found by the compiler:
import scalaz._, Scalaz._
def mergeWithReplace(m1: Map[String, String], m2: Map[String, String]) = {
implicit val stringInstance: Semigroup[String] = Semigroup.lastSemigroup
m1 |+| m2
}
And then:
scala> mergeWithReplace(Map("a" -> "foo", "b" -> "qux"), Map("a" -> "bar"))
res0: Map[String,String] = Map(a -> bar, b -> qux)
Even apart from theoretical arguments for coherence, though, this is annoying and fragile. For example, the name of the implicit must be stringInstance
in order to shadow the implicit that was imported from scalaz.std.string
, since that one has a more specific type, even though you're not asking for a more specific type. I don't remember whether there's some excuse for that precedence behavior, and I don't really want to have to care, so I tend to avoid using local instances like that.
In this case there happens to be a nicer solution, though. Scalaz provides Plus
instances for maps, and Plus
doesn't know about the value type, so it combines values by picking the last one. This means you can get what you want by using the <+>
operator from Plus
:
def mergeWithReplace(m1: Map[String, String], m2: Map[String, String]) =
m1 <+> m2
And then:
scala> mergeWithReplace(Map("a" -> "foo", "b" -> "qux"), Map("a" -> "bar"))
res0: scala.collection.immutable.Map[String,String] = Map(a -> bar, b -> qux)
You're not usually going to get that lucky, though.
Upvotes: 2