If I have a Shapeless HMap[MappingA] (with implicits properly defined for the type MappingA[K, V]), can I type-safely transform/map it to a HMap[MappingB].
class MappingA[K, V]
implicit val intToString = new MappingA[Int, String]
implicit val stringToInt = new MappingA[String, Int]
class MappingB[K, V]
implicit val longToString = new MappingA[Long, String]
implicit val stringToLong = new MappingA[String, Long]
val hm1 = HMap[MappingA](1 -> "one", "two" -> 2)
// How to...
val hm2: HMap[MappingB] = ??? // transform/map hm1
// expected for hm2 in this basic example
// HMap[MappingB](1L -> "one", "two" -> 2L)
Related
I am facing this weird problem related to scala implicit resolution
Here is the code snippet
import scala.collection.Factory
import scala.collection.immutable.Seq
sealed trait A
sealed trait B
case class BImpl() extends B
case class AImpl() extends A
object implicitsContainer {
type AB = (A, B)
implicit def toStringAnyTuples[C[X] <: Iterable[X], A <: AB]
(col: C[A])
(implicit factory: Factory[(String, Any), C[(String, Any)]])
: C[(String, Any)] = {
factory.fromSpecific(col.iterator.map(f => f._1.toString -> f._2))
}
}
object Main extends App {
import implicitsContainer._
def a(f: Seq[(String, Any)]): Seq[(String, Any)] = f
val w: Seq[(AImpl, BImpl)] = Seq(AImpl() -> BImpl())
val x: Seq[(String, Any)] = a(w)
// Won't compile
// val y: Seq[(String, Any)] = a(Seq(AImpl() -> BImpl()))
}
Scala automatically picking up the implicit method
implicit def toStringAnyTuples[C[X] <: Iterable[X], A <: AB](col: C[A])
(implicit factory: Factory[(String, Any), C[(String, Any)]])
: C[(String, Any)] = {
factory.fromSpecific(col.iterator.map(f => f._1.toString -> f._2))
}
for this: -
val w: Seq[(AImpl, BImpl)] = Seq(AImpl() -> BImpl())
val x: Seq[(String, Any)] = a(w)
but throws an error for this
val y: Seq[(String, Any)] = a(Seq(AImpl() -> BImpl()))
and the error is:-
Error:(44, 47) type mismatch;
found : (AImpl, BImpl)
required: (String, Any)
val y: Seq[(String, Any)] = a(Seq(AImpl() -> BImpl()))
and one more point, if I remove the type from w
val w = Seq(AImpl() -> BImpl())
val x: Seq[(String, Any)] = a(w)
then also this will work fine.
The only error is with
val y: Seq[(String, Any)] = a(Seq(AImpl() -> BImpl()))
I am using: -
SCALA -> 2.13.3
SBT -> 1.3.13
JAVA -> 14
It's just type inference issue. Type parameter of Seq.apply was not inferred. Try
val y: Seq[(String, Any)] = a(Seq[(AImpl, BImpl)](AImpl() -> BImpl()))
or
val y: Seq[(String, Any)] = a(Seq[(A, B)](AImpl() -> BImpl()))
What you are experiencing is a symptom of the way inference works in the Scala compiler.
Here is a smaller example that shows the same issue:
object Test {
class C[T](x: T)
implicit def conv(c: C[Int]): C[String] = ???
def m(cs: C[String]) = 1
val ci = new C(1)
def t1: Int = m(ci) // OK
def t2: Int = m(new C(1)) // found: Int, expected: String
}
When type-checking new C(1), the compiler pushes down the expected type String to type
checking the expression 1, which fails. In the line above, type checking ci with expected type
C[String] succeeds thanks to the implicit conversion.
My suggestion here would be to define an extension method that performs the conversion, instead
of making the conversion implicit. This is also recommended for clarity - implicit conversions like
the one defined in your example can lead to surprising, hard to diagnose issues.
In my example, it would look like this:
object Test {
class C[T](x: T)
implicit class CExt(private val c: C[Int]) extends AnyVal {
def toCString: C[String] = ???
}
def m(cs: C[String]) = 1
val ci = new C(1)
def t1: Int = m(ci.toCString)
def t2: Int = m(new C(1).toCString)
}
I'm creating shapeless HMap using the code like below from scala-exercises.
import shapeless.HMap
class BiMapIS[K, V]
implicit val intToString = new BiMapIS[Int, String]
implicit val stringToInt = new BiMapIS[String, Int]
val hm = HMap[BiMapIS](23 -> "foo", "bar" -> 13)
I would like to create HMap from variable arguments as below (I'm having long list of arguments so just checking whether I can simplify the code littlebit) -
import shapeless.{HMap, HNil}
import java.util.{List => JList}
val entities: JList[(_, _)] = ???
class BiMapIS[K, V]
implicit val intToString = new BiMapIS[Int, String]
implicit val stringToInt = new BiMapIS[String, Int]
import collection.JavaConverters._
val entitiesSeq = entities.asScala.toList
val hm = HMap[BiMapIS](entitiesSeq:_*)
Is there any way I can create HMap from variable args?
I'm using shapless 2.33 with scala 2.12 https://mvnrepository.com/artifact/com.chuusai/shapeless_2.12/2.3.3
Try
val entitiesSeq = entities.asScala.toMap[Any, Any]
val hm = new HMap[BiMapIS](entitiesSeq)
I am pretty new to scala, and i have hard time with generic in some case.
I try to find simple example to illustrate my issue
let's start by setting 3 simple maps
scala> val map1 = Map[String, String]("A" -> "toto", "B" -> "tutu")
map1: scala.collection.immutable.Map[String,String] = Map(A -> toto, B -> tutu)
scala> val map2 = Map[String, Int]("B" -> 2)
map2: scala.collection.immutable.Map[String,Int] = Map(B -> 2)
scala> val map3 = Map[String, String]("A" -> "foo")
map3: scala.collection.immutable.Map[String,String] = Map(A -> foo)
if i merge using ++
scala> map1 ++ map2
res0: scala.collection.immutable.Map[String,Any] = Map(A -> toto, B -> 2)
My map became [String, Any] which is what i want to reproduce
I create new method in Map class
object MapUtils {
implicit class utils[K, V](map: Map[K, V]) {
def myUselessMethod(merge: Map[K, V]): Map[K, V] = {
map ++ merge
}
}
}
import MapUtils._
scala> map1 myUselessMethod map3
res4: Map[String,String] = Map(A -> foo, B -> tutu)
scala> map1 myUselessMethod map2
<console>:18: error: type mismatch;
found : scala.collection.immutable.Map[String,Int]
required: Map[String,String]
map1 myUselessMethod map2
How should i define my method so that it can return [String, Any]
I also checked scala MAP API to see if i can have some indications
https://www.scala-lang.org/api/2.12.3/scala/collection/Map.html
but
++[B >: (K, V), That](that: GenTraversableOnce[B])(implicit bf:
CanBuildFrom[Map[K, V], B, That]): That
is somehow indecipherable for me...
Thank you
Just give the compiler some slack in the return type:
object MapUtils {
implicit class utils[K, V](map: Map[K, V]) {
def myUselessMethod[L >: V](merge: Map[K, L]): Map[K, L] = {
map ++ merge
}
}
}
import MapUtils._
val map1 = Map[String, String]("A" -> "toto", "B" -> "tutu")
val map2 = Map[String, Int]("B" -> 2)
map1 myUselessMethod map2
Now it will infer the least upper bound L of V and the value type of the merge-argument map. In case of Int and String, L would be inferred to be Any.
I have a map which is something like
val m = Map("foo" -> "bar", "faz" -> "baz")
I need to write a custom get method, so that the key can be the key in the map with a number in the end.
So for example:
m.get("foo1") should return "bar"
I am looking for a good scala pattern to solve this problem.
Also I am generating the above map from a for loop using yield, so I can't do something like this
val m = CustomMap("foo" -> "bar")
Any solutions will be appreciated.
Thanks
First of all, you can generate a map from a for comprehension, and then convert it to CustomMap. You just need to define a
def apply(map: Map[String, String]) = CustomMap(map.toSeq :_*) in CustomMap - then you can do val m = CustomMap( for { ... } yield ... )
Secondly, if it doesn't have to be named get (it probably shouldn't be anyway), you can do this sort of thing with an implicit:
object PimpMyMap {
val pref = ".*?(\\d+)".r
implicit class Pimped[V](val map: Map[String,V]) extends AnyVal {
def getPrefix(key: String): Option[V] = map.get(key).orElse { key match {
case pref(k) => map.get(k)
case _ => None
}
}
Now you can write things like:
import PimpMyMap._
val map = Map("foo" -> 1)
val one = map.getPrefix("foo123") // Some(1)
val anotherOne = map.getPrefix("foo") // also Some(1);
You can do this with an implicit class and implicit conversion:
import scala.language.implicitConversions
object MapHelpers {
implicit def optionStringToString(maybeS: Option[String]): String = maybeS.getOrElse("")
implicit class MapWithIntKey(val m: Map[String, String]) extends Map[String, String] {
override def get(key: String): Option[String] = {
val intRegex = """(\d+)""".r
val keyWithoutInt = intRegex
.findFirstMatchIn(key)
.map(int => {
val idx = key.indexOf(int.toString)
key.slice(0, idx)
})
.getOrElse(key)
m.get(keyWithoutInt)
}
def +[V1 >: String](
kv: (String, V1)): scala.collection.immutable.Map[String, V1] = m + kv
def -(key: String): scala.collection.immutable.Map[String, String] = m - key
def iterator: Iterator[(String, String)] = m.iterator
}
}
object App {
import MapHelpers._
def testMapImplicit(): Unit = {
val myMap: MapWithIntKey = Map("foo" -> "bar", "faz" -> "baz")
val result: String = myMap.get("foo1")
println("result", result) // bar
}
}
Working Scastie
If you have a sure way to get the real key from the fake key, you can do this with Map.withDefault:
class CustomMap[K, +V] private (underlying: Map[K, Option[V]]) {
def get(k: K): Option[V] = underlying(k)
}
object CustomMap {
def apply[K, V](original: Map[K, V], keyReducer: K => K) = new CustomMap(originalMap.
mapValues(Some(_)).
withDefault(k => originalMap.get(keyReducer(k))
)
}
In your case, you can use this with
val stringKeyReducer: String => String = k.reverse.dropWhile(_.isDigit).reverse
to drop the digits at the end of your strings, so
CustomMap(Map("foo" -> "bar"), stringKeyReducer).get("foo1") = Some("bar")
Here is solution which combines both the answers.
import scala.language.implicitConversions
object MapHelpers {
implicit def optionStringToString(maybeS: Option[String]): String = maybeS.getOrElse("")
implicit class MapWithIntKey(val m: Map[String, String]) extends Map[String, String] {
override def get(key: String): Option[String] = {
val prefix = "(.*?)\\d+".r
m.get(key).orElse{
key match {
case prefix(p) => m.get(p)
case _ => None
}
}
}
def +[V1 >: String](kv: (String, V1)): scala.collection.immutable.Map[String, V1] = m + kv
def -(key: String): scala.collection.immutable.Map[String, String] = m - key
def iterator: Iterator[(String, String)] = m.iterator
}
}
object App {
import MapHelpers._
def testMapImplicit(): Unit = {
val myMap: MapWithIntKey = Map("foo" -> "bar", "faz" -> "baz")
println("result - number match ", myMap.get("foo1"))
println("result - exact match ", myMap.get("foo"))
}
}
App.testMapImplicit()
Working Scastie
Given a (data structure that operates like a) Map. Is there a type level way of inserting to it? That is:
val myMap: Map[Int, String] = Map(1 -> "a", 2 -> "b")
val x: Int = 5
val y: Int = 99
val myMap2 = myMap + (x -> "e")
What I'm hoping for is that myMap2 will have some type wherein I can safely do something like myMap2.retrieve(x) and have it compile and return "e". But myMap2.retrieve(y) should not even compile.
It's possible with Shapeless if your keys have different types:
import shapeless._
object _1
object _2
object _3
object _4
//-------------Map definition starts here----------------
class StaticMap1[K, V]
trait StaticMap1Like {
type M[T] <: StaticMap1[T, String]
private def add[T]: M[T] = (new StaticMap1[T, String] {}).asInstanceOf[M[T]]
implicit val __1 = add[_1.type] //add key to StaticMap1
implicit val __2 = add[_2.type] //add key to StaticMap1
}
object StaticMap1 extends StaticMap1Like
val hm = HMap[StaticMap1](_1 -> "a", _2 -> "b") //add values
//-------------Map definition ends here-----------------
scala> hm.get(_1)
res0: Option[String] = Some(a)
scala> hm.get(_2)
res1: Option[String] = Some(b)
scala> hm.get(_3) //compile-time error
<console>:18: error: could not find implicit value for parameter ev: BiMapIS[shapeless.nat._3,V]
hm.get(_3)
^
And key insertion:
//----Adding element _3 -> "c" starts here--------------
class StaticMap2[K, V] extends StaticMap1[K, V]
trait StaticMap2Like extends StaticMap1Like {
type M[T] <: StaticMap2[T, String]
private def add[T] = new StaticMap2[T, String] {}.asInstanceOf[M[T]]
implicit val __3 = add[_3.type] //add key to StaticMap2
}
object StaticMap2 extends StaticMap2Like
val hm2 = hm.asInstanceOf[HMap[StaticMap2]] + (_3 -> "c")
//----Adding element ends here---------------------------
scala> hm2.get(_3)
res6: Option[String] = Some(c)
scala> hm2.get(_2)
res7: Option[String] = Some(b)
scala> hm2.get(_1)
res8: Option[String] = Some(a)
scala> hm2.get(_4)
<console>:21: error: could not find implicit value for parameter ev: StaticMap2[_4.type,V]
hm2.get(_4)
^
scala> hm.get(_3) //old `hm` still working
<console>:17: error: could not find implicit value for parameter ev: StaticMap1[_3.type,V]
hm.get(_3)
^
But:
don't use Shapeless native nat here - it won't work as it can't distinguish nat._1 from nat._2 (at least for my version of Shapeless)
adding element to а Map also wouldn't be so convinient, as user is gonna have to add an implicit for every new key
Each time you use myMap2.get(y) you will get an Option of String for any y if it is an Int.
In case you try to use any other type, you will have a compilation exception.
e.g.: myMap2.get("y") will throw a type mismatch.
So you can't compile if the key type is not the right one.