I tried implicit conversions in the following example:
val m: Map[Int, Int] = Map(10 -> "asd") //fine
val mm: Map[Int, Int] = Map("asd" -> 20) //type mismatch; found: (String, Int)
//required: (Int, Int)
implicit def stringToInt(str: String): Int = 10
Why can't we apply implicit conversions to map keys? Is there a way to work around this?
It doesn't work because you're using -> which is an (inline) operator:
implicit final class ArrowAssoc[A](self : A) extends scala.AnyVal {
#scala.inline
def ->[B](y : B) : scala.Tuple2[A, B] = { /* compiled code */ }
def →[B](y : B) : scala.Tuple2[A, B] = { /* compiled code */ }
}
You can see that by the time B is evaluated, A is already "fixed". Let's just say that you can only (implicitly) convert the right hand side of a tuple when using -> operator:
implicit def stringToInt(str: String): Int = 10
implicit def intToStr(str: Int): String = "a"
val a: Map[Int, Int] = Map(10 -> "asd") //fine
val b: Map[Int, Int] = Map("asd" -> 20) // error! cant change left side
val c: Map[String, String] = Map("asd" -> 20) // fine
val d: Map[String, String] = Map(10 -> "asd") // error! cant change left side
Because of similar compiler quirks related to using operator ->, #Jorg's solution works in one direction, but not the other:
implicit def tupleIntifier(t: (String, Int)) = (10, 10)
implicit def tupleIntifier2(t: (Int, String)) = (10, 10)
val a: Map[Int, Int] = Map("asd" -> 20) // uses tupleIntifier
val b: Map[Int, Int] = Map(10 -> "asd") // fails!!
However, if you avoid using -> operator altogether and simply use (key, value) syntax, it will work:
val a: Map[Int, Int] = Map((10, "asd"))
val b: Map[Int, Int] = Map(("asd", 20))
implicit def stringToInt(str: String): Int = 15
println(a) // prints Map(10 -> 15)
println(b) // prints Map(15 -> 20)
Please, look at the error message you are getting:
error: type mismatch;
found : (String, Int)
required: (Int, Int)
val mm: Map[Int, Int] = Map("asd" -> 20)
^
The error message is not about String instead of Int, it is about (String, Int) instead of (Int, Int). So, you are simply converting the wrong thing:
implicit def tupleIntifier[T](t: (String, T)) = (10, t._2)
val mm: Map[Int, Int] = Map("asd" -> 20)
//=> mm: Map[Int,Int] = Map(10 -> 20)
Voila! It works.
If you were to add such a general implicit conversion, you would lose the type-safety that Scala is enforcing, because any String would become an Int as needed, anywhere, without intervention from the programmer.
In reality, when you want to create that map from other data, you probably already know the data types of that other data. So if the keys are known to be integers, convert them to Int and use them like that. Otherwise, use strings.
Your example is highly artificial. Which concrete problem are you trying to solve?
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 am trying to convert implicitly a List[(Int, String)] to List[(IntWrap, String)], which is giving an error of TypeMismatch.
I tried few other conversion which works which are
List[Int] to List[IntWrap] and Tuple2[Int, String] to Tuple2[IntWrap, String] which works.
case class IntWrap(a : Int)
implicit def int2IntWrap(x: Int) = IntWrap(x)
implicit def int2IntWrapForTuple2(tuple: Tuple2[Int, String]) = (IntWrap(tuple._1), tuple._2)
//Defined few methods
def foo(t : (IntWrap, String)) = println(t._2)
def foo_list(t: List[IntWrap]) = println(t.size)
def foo_list_tuple(t : List[(IntWrap, String)]) = println(t.size)
foo(3 -> "hello") //this works
foo_list(List(1, 2, 3)) //this works
val l : List[(IntWrap, String)] = List(3 -> "hello")
foo_list_tuple(l) //this works
val l1 = List(3 -> "hello")
foo_list_tuple(l1) //this one doesn't work
//error: type mismatch; found: List[(Int, String)] required: List[(IntWrap, String)]
How can I make this work?
Try defining implicit conversion like so
implicit def listOfTupleToListOfIntWrapTuple(l: List[(Int, String)]): List[(IntWrap, String)] =
l.map(tuple => (IntWrap(tuple._1), tuple._2))
I want to sum the Map("one" -> 2, "two" -> 3, "three" -> 4)'s values 2+3+4, of course I can use the following methods:
Map("one" -> 2, "two" -> 3, "three" -> 4).foldLeft(0)(_ + _._2)
Map("one" -> 2, "two" -> 3, "three" -> 4).values.sum()
I found Map has a another more direct API sum: def sum: A, but, I don't search any example about this API, How to use it?
For sum[B >: (A, B)](implicit num: Numeric[B]): B, as you see, it needs the implicit parameter and the parameter type is Numeric[B] type.
Scala Numeric is trait that defined a series of math operation.
http://www.scala-lang.org/api/current/scala/math/Numeric.html
for your case, the type is Map[String, Int], so you need to implement the implicit for Numeric[(String, Int)] for sum method, like:
trait MyTupleNumeric extends Numeric[(String, Int)] {
def plus(x: (String, Int), y: (String, Int)) = ("", x._2 + y._2)
override def minus(x: (String, Int), y: (String, Int)): (String, Int) = ("", x._2 - x._2)
override def times(x: (String, Int), y: (String, Int)): (String, Int) = ("", x._2 * y._2)
override def negate(x: (String, Int)): (String, Int) = ("", -x._2)
override def fromInt(x: Int): (String, Int) = ("", x)
override def toInt(x: (String, Int)): Int = x._2
override def toLong(x: (String, Int)): Long = x._2.toLong
override def toFloat(x: (String, Int)): Float = x._2.toFloat
override def toDouble(x: (String, Int)): Double = x._2.toDouble
override def compare(x: (String, Int), y: (String, Int)): Int = x._2 - y._2
}
implicit object MyTupleNumericImplicit extends MyTupleNumeric
val f = implicitly[Numeric[(String, Int)]] // implicitly is used find implicits base on the type
println(f.plus(("one", 2), ("two", 3)))
val r = Map("one" -> 2, "two" -> 3, "three" -> 4)
println(r.sum._2)
println(r.sum(MyTupleNumericImplicit))
As the above code, we have implement own Numeric type with (String, Int), and implements methods.
And we implicit this into our scope, so we can use implicitly to get the function and call.
And the sum method also could find the implicit parameter for Numeric[(String, Int)]
The Map.sum method doesn't do what you want -- your functions look good as they are.
The reason why Map has a sum method is that all TraversableOnce classes have a sum method, which only works if the collection type is numeric. However, a Map[K, V] is a TraversableOnce[(K, V)], so this approach won't work (the key-value tuple is not a numeric type).
Can someone maybe explain to me why I get a compiler error below and the best way to do this type of conversion
Thanks
Des
case class A[T](i: Int, x: T)
val set: Set[A[_]] = Set(A(1, 'x'), A(2, 3))
val map: Map[Int, A[_]] = set.map(a => a.i -> a)
type mismatch; found : scala.collection.immutable.Set[(Int, A[_$19]) forSome { type _$19 }] required: Map[Int,A[_]]
There are a couple of things here, first I suppose that A is a case class (otherwise you would need to use the new keyword), second your map returns a Set of tuples (not a Map), third you are returning a A[_] in the map, but a.x returns an Any, not an A:
scala> case class A[T](i: Int, x: T)
defined class A
scala> val set: Set[A[_]] = Set(A(1, 'x'), A(2, 3))
set: Set[A[_]] = Set(A(1,x), A(2,3))
To match the type signature you can use toMap and change Map[Int, A[_]] to Map[Int, _]
scala> val map: Map[Int, _] = set.map(a => a.i -> a.x).toMap
map: Map[Int, _] = Map(1 -> x, 2 -> 3)
If you want to keep the original signature (Map[Int, A[_]]) you need to return an A in the tuple:
scala> val map: Map[Int, A[_]] = set.map(a => a.i -> a).toMap
map: Map[Int,A[_]] = Map(1 -> A(1,x), 2 -> A(2,3))
Is it possible to implement in Scala an implicit conversion for the group of parameters (without defining them as some class member) like
implicit def triple2One (x :Int, s :String, d :Double) = x // just as an example
So that I would be able to call it in the code like
val x :Int = (1, "test", 2.0)
It is possible:
scala> implicit def iFromISD(isd: (Int, String, Double)): Int = isd._1
iFromISD: (isd: (Int, String, Double))Int
scala> val x: Int = (1, "two", 3.0)
x: Int = 1
Naturally, there has to be a type annotation on the resulting val to drive the search for and application of the implicit conversion.
Addendum
It occurs to me there's another way that doesn't involve dubious implicit conversions:
scala> val (y, _, _) = (1, "two", 3.0)
y: Int = 1