scala 3: value as a class parameter and valueof - scala

I am trying to specify a value as a class parameter:
import scala.collection.mutable
class MyMap[K,V,D <: V] {
private val impl = mutable.Map[K, V]()
def apply(k:K):V = impl.getOrElse(k, valueOf[D])
// ^^^^^^^^^^ error
def put(k:K,v:V):Unit = impl += k -> v
}
val m = MyMap[Int, Int, 0]()
m(100) // must return 0
For some reason, the above does not work. Instead, what works is the below code with this illogical unusedName:
import scala.collection.mutable
class MyMap[K,V,D <: V] {
private val impl = mutable.Map[K, V]()
def apply(k:K)(implicit unusedName:ValueOf[D]):V = impl.getOrElse(k, valueOf[D])
def put(k:K,v:V):Unit = impl += k -> v
}
val m = MyMap[Int, Int, 0]()
m(100) // must return 0
I tried to pass the implicit agrument explicitly, which in theory should be possible, but it did not work as I would expect:
scala> m(2)(0)
-- [E007] Type Mismatch Error: ----
1 |m(2)(0)
| ^
| Found: (0 : Int)
| Required: ValueOf[(0 : Int)]
scala> m(2)(ValueOf[0])
-- Error: --------------------------
1 |m(2)(ValueOf[0])
| ^^^^^^^^^^
| missing arguments for constructor ValueOf in class ValueOf
scala> m(2)(ValueOf[0](0))
val res7: Int = 0
Questions:
How does all this ValueOf/valueOf magic work?
Why didn't just valueOf[D] work? (It would be only logical)
UPDATE
the code referenced in the comments is:
import scala.collection.mutable
final class MyMap[K, V] private (default: V) {
private val impl = mutable.Map.empty[K, V]
def apply(k: K): V =
impl.getOrElse(k, default)
def put(k: K,v: V): Unit = {
impl += k -> v
}
}
object MyMap {
def empty[K, V, D <: V](using ValueOf[D]): MyMap[K, V] =
new MyMap(default = valueOf[D])
}
val m = MyMap.empty[Int, Int, 0]
m(100)
Code fragment 2:
import scala.collection.mutable
final class MyMap[K, V, D <: V](using ValueOf[D]) {
private val default = valueOf[D]
private val impl = mutable.Map.empty[K, V]
def apply(k: K): V =
impl.getOrElse(k, default)
def put(k: K,v: V): Unit = {
impl += k -> v
}
}
val m = MyMap[Int, Int, 0]()
m(100)

How does all this ValueOf/valueOf magic work?
ValueOf[T] is the proposition that there exists one distinguished value of type T.
The witness instances for all reasonable types T are generated by the compiler automatically.
For each v: ValueOf[T], the member access v.value gives the distinguished value of type T.
The method valueOf[T] is just unpacking the witnesses ValueOf[T] into the values of type T.
It could be somewhat instructive to quickly re-build this mechanism from scratch:
case class MyValueOf[T](value: T)
// For ValueOf, these proofs are generated automatically by the compiler;
// Here, we have to do it ourselves
given MyValueOf[0] = MyValueOf(0)
def myValueOf[A](using mvo: MyValueOf[A]): A = mvo.value
println(summon[MyValueOf[0]]) // MyValueOf(0), the proof
println(myValueOf[0]) // 0, the value
Why didn't just valueOf[D] work? (It would be only logical)
Because, as the signature says, the method valueOf[D] requires a ValueOf[D] as implicit argument. The instances of ValueOf[D] can be generated when the D is known. It can not be generated for arbitrary generic type D (if that were the case, then your code would work for a Map[Int, Int, Nothing], which is clearly absurd).
Regarding your REPL experiments:
m(2)(0) clearly shouldn't work, because 0 is of type Int, not ValueOf[Int].
ValueOf[0] is interpreted as an incomplete constructor invocation. If you meant the type instead of the value, you would have to use summon to materialize the value for the type, i.e. m(2)(summon[ValueOf[0]])
ValueOf[0](0) is basically the same as what the compiler would generate anyway when asked to summon[ValueOf[0]].
Regarding your Map-wrapper:
You probably want to pass the default value once, when you create the map:
import scala.collection.mutable
class MyMap[K, V, D <: V](using defaultValueOf: ValueOf[D]) {
private val impl = mutable.Map[K, V]()
def apply(k: K): V = impl.getOrElse(k, valueOf[D])
def put(k: K, v: V): Unit = impl += k -> v
}
val m = MyMap[Int, Int, 0]()
println(m(100)) // prints '0'
The wrapper itself does not seem to be very helpful, because there is already a method withDefault defined on all mutable maps.

Related

Binding Existential types in Scala

This is my basic CMap which map classes (Class[T] for any T) to any type of value.
scala> type CMap = Map[Class[T] forSome{type T}, Any]
defined type alias CMap
scala> val cMap: CMap = Map(classOf[Int]->5, classOf[String]->"abc", classOf[Double]->"ddd")
cMap: CMap = Map(int -> 5, class java.lang.String -> abc, double -> ddd)
Now I want a "bound" CMap (call it CMapBind). Like CMap, it maps classes (any classes) to values (any values). But unlike the CMap, CMapBind has a type binding between the key and value, meaning I hope the following behavior:
val cMapBind: CMapBind = Map(classOf[Int]->5, classOf[String]-> "aa") // should compile
val cMapBind: CMapBind = Map(classOf[Int]->5, classOf[String]-> 0) // should fail compile
How do I implement CMapBind?
I know the following two won't work syntactically/logically.
scala> type CMapBind = Map[Class[T] forSome{type T}, T]
<console>:8: error: not found: type T
type CMapBind = Map[Class[T] forSome{type T}, T]
scala> type CMapBind = Map[Class[T], T] forSome{type T}
scala> val cMapBind: CMapBind = Map(classOf[Int]->5, classOf[String]->"str")
<console>:8: error: type mismatch;
found : scala.collection.immutable.Map[Class[_ >: String with Int],Any]
required: CMapBind
(which expands to) Map[Class[T],T] forSome { type T }
val cMapBind: CMapBind = Map(classOf[Int]->5, classOf[String]->"str")
Note that here I am using type constructor Class[T] as an example to illustrate the question. In my code I have my own types, e.g. trait Animal[S, T], class Dog extends Animal[Int, String].
Edit 1:
I should have mentioned that I am using immutable Map as an example, but what I really need is a mutable heterogeneous Map).
Let's try to implement mutable HMap. Some explanations about Scala type system is here by #MilesSabin: http://www.chuusai.com/2011/07/16/fundeps-in-scala/
The idea is to statically check the constructor (here you will see, that it's arity depends on your hands, so it is possible to generate it or smth else), and the insert method. By the way, same way immutable HMap implemented in shapeless.
import scala.collection.mutable.Map
class HMapBuilder[R[_, _]] { // constructor arity is two
def apply[K1, V1](e1: (K1, V1))(implicit ev1: R[K1, V1]) =
new HMap[R](Map(e1))
def apply[K1, V1, K2, V2](e1: (K1, V1), e2: (K2, V2))
(implicit ev1: R[K1, V1], ev2: R[K2, V2]) =
new HMap[R](Map(e1, e2))
}
So it is a constructor of our map. Evidences will statically check types of the inserting data. Next, lets wrap the default scala collection:
class HMap[R[_, _]](underlying : Map[Any, Any] = Map.empty) {
def get[K, V](k : K)(implicit ev : R[K, V]) : Option[V] =
underlying.get(k).asInstanceOf[Option[V]]
def +=[K, V](kv : (K, V))(implicit ev : R[K, V]) : HMap[R] = {
underlying += kv
this
}
def -=[K](k : K) : HMap[R] = {
underlying -= k
this
}
override def toString = underlying.toString
}
Finally wrapping HMapBuilder, to make a pleasant constructor:
object HMap {
def apply[R[_, _]] = new HMapBuilder[R]
def empty[R[_, _]] = new HMap[R]
def empty[R[_, _]](underlying : Map[Any, Any]) =
new HMap[R](underlying)
}
In result, the usage is similar to shapeless HMap:
class Mapping[K, V]
implicit def mappingFromClass[A] = new Mapping[Class[A], A]
val hm = HMap[Mapping](classOf[Int] -> 5) // ok
hm += (classOf[String] -> "string") // ok
hm += (classOf[Boolean] -> false) // ok
hm += (classOf[Double] -> "sss") // compile fail
Works as expected. I implemented only insert and remove functions, other functions is possible to define same way.

Safely copying fields between case classes of different types

Assuming you have case classes like the following
case class Test1(a:String,b:Int,c:Char)
case class Test2(a:String,b:Int)
And you instantiate the classes with the following variables
val test1 = Test1("first",2,'3')
val test2 = Test2("1st",20)
Is there a way to use the .copy method (or otherwise), to apply the variables inside Test2 to Test1, something like
val test3 = test1.copy(test2) //Note this isn't valid scala code
// Result should be ("1st",20,'3')
If this isn't possible in pure scala, how would it be done in Shapeless 1/2 (current code is in Shapeless 1, however we are planning to upgrade to Shapeless 2 at some point in time)
In shapeless 2.0.0 this can be done with like so,
scala> import shapeless._
import shapeless._
scala> case class Test1(a: String, b: Int, c: Char)
defined class Test1
scala> case class Test2(a: String, b: Int)
defined class Test2
scala> val test1 = Test1("first", 2, '3')
test1: Test1 = Test1(first,2,3)
scala> val test2 = Test2("1st", 20)
test2: Test2 = Test2(1st,20)
scala> val test1Gen = Generic[Test1]
test1Gen: ... = $1$$1#6aac621f
scala> val test2Gen = Generic[Test2]
test2Gen: ... = $1$$1#5aa4954c
scala> val test3 = test1Gen.from(test2Gen.to(test2) :+ test1.c)
test3: Test1 = Test1(1st,20,3)
Note that this makes assumptions about the order of fields in each of the case classes rather than making use of field label information. This could be error prone where you had multiple fields of the same type: the types might line up, but the latent semantics might be altered.
We can fix that by using shapeless's LabelledGeneric. LabelledGeneric maps case class values to shapeless extensible records which, as well as capturing the types of the field values, also encodes the field names in the type by way of the singleton type of the corresponding Scala Symbol. With a little bit of additional infrastructure (which I'll be adding to shapeless 2.1.0 shortly) this allows us to map between case classes safely with minimal boilerplate,
import shapeless._, record._, syntax.singleton._, ops.hlist.Remove
/**
* This will be in shapeless 2.1.0 ...
*
* Permute the elements of the supplied `HList` of type `L` into the same order
* as the elements of the `HList` of type `M`.
*/
trait Align[L <: HList, M <: HList] extends (L => M) {
def apply(l: L): M
}
object Align {
def apply[L <: HList, M <: HList]
(implicit alm: Align[L, M]): Align[L, M] = alm
implicit val hnilAlign: Align[HNil, HNil] = new Align[HNil, HNil] {
def apply(l: HNil): HNil = l
}
implicit def hlistAlign[L <: HList, MH, MT <: HList, R <: HList]
(implicit
select: Remove.Aux[L, MH, (MH, R)],
alignTail: Align[R, MT]): Align[L, MH :: MT] = new Align[L, MH :: MT] {
def apply(l: L): MH :: MT = {
val (h, t) = l.removeElem[MH]
h :: alignTail(t)
}
}
}
/**
* This, or something like it, will be in shapeless 2.1.0 ...
*
* Utility trait intended for inferring a field type from a sample value and
* unpacking it into its key and value types.
*/
trait Field {
type K
type V
type F = FieldType[K, V]
}
object Field {
def apply[K0, V0](sample: FieldType[K0, V0]) =
new Field { type K = K0; type V = V0 }
}
object OldWineNewBottle {
case class From(s1: String, s2: String)
case class To(s2: String, i: Int, s1: String)
val from = From("foo", "bar")
val fromGen = LabelledGeneric[From]
val toGen = LabelledGeneric[To]
// Define the type of the i field by example
val iField = Field('i ->> 0)
val align = Align[iField.F :: fromGen.Repr, toGen.Repr]
// extend the fields of From with a field for 'i', permute into
// the correct order for To and create a new instance ...
val to = toGen.from(align('i ->> 23 :: fromGen.to(from)))
assert(to == To("bar", 23, "foo"))
}

Magnet pattern with repeated parameters (varargs)

Is it possible to use the magnet pattern with varargs:
object Values {
implicit def fromInt (x : Int ) = Values()
implicit def fromInts(xs: Int*) = Values()
}
case class Values()
object Foo {
def bar(values: Values) {}
}
Foo.bar(0)
Foo.bar(1,2,3) // ! "error: too many arguments for method bar: (values: Values)Unit"
?
As already mentioned by gourlaysama, turning the varargs into a single Product will do the trick, syntactically speaking:
implicit def fromInts(t: Product) = Values()
This allows the following call to compile fine:
Foo.bar(1,2,3)
This is because the compiler autmatically lifts the 3 arguments into a Tuple3[Int, Int, Int]. This will work with any number of arguments up to an arity of 22. Now the problem is how to make it type safe. As it is Product.productIterator is the only way to get back our argument list inside the method body, but it returns an Iterator[Any]. We don't have any guarantee that the method will be called only with Ints. This should come as no surprise as we actually never even mentioned in the signature that we wanted only Ints.
OK, so the key difference between an unconstrained Product and a vararg list is that in the latter case each element is of the same type. We can encode this using a type class:
abstract sealed class IsVarArgsOf[P, E]
object IsVarArgsOf {
implicit def Tuple2[E]: IsVarArgsOf[(E, E), E] = null
implicit def Tuple3[E]: IsVarArgsOf[(E, E, E), E] = null
implicit def Tuple4[E]: IsVarArgsOf[(E, E, E, E), E] = null
implicit def Tuple5[E]: IsVarArgsOf[(E, E, E, E, E), E] = null
implicit def Tuple6[E]: IsVarArgsOf[(E, E, E, E, E), E] = null
// ... and so on... yes this is verbose, but can be done once for all
}
implicit class RichProduct[P]( val product: P ) {
def args[E]( implicit evidence: P IsVarArgsOf E ): Iterator[E] = {
// NOTE: by construction, those casts are safe and cannot fail
product.asInstanceOf[Product].productIterator.asInstanceOf[Iterator[E]]
}
}
case class Values( xs: Seq[Int] )
object Values {
implicit def fromInt( x : Int ) = Values( Seq( x ) )
implicit def fromInts[P]( xs: P )( implicit evidence: P IsVarArgsOf Int ) = Values( xs.args.toSeq )
}
object Foo {
def bar(values: Values) {}
}
Foo.bar(0)
Foo.bar(1,2,3)
We have changed the method signature form
implicit def fromInts(t: Product)
to:
implicit def fromInts[P]( xs: P )( implicit evidence: P IsVarArgsOf Int )
Inside the method body, we use the new methodd args to get our arg list back.
Note that if we attempt to call bar with a a tuple that is not a tuple of Ints, we will get a compile error, which gets us our type safety back.
UPDATE: As pointed by 0__, my above solution does not play well with numeric widening. In other words, the following does not compile, although it would work if bar was simply taking 3 Int parameters:
Foo.bar(1:Short,2:Short,3:Short)
Foo.bar(1:Short,2:Byte,3:Int)
To fix this, all we need to do is to modify IsVarArgsOf so that all the implicits allow
the tuple elemts to be convertible to a common type, rather than all be of the same type:
abstract sealed class IsVarArgsOf[P, E]
object IsVarArgsOf {
implicit def Tuple2[E,X1<%E,X2<%E]: IsVarArgsOf[(X1, X2), E] = null
implicit def Tuple3[E,X1<%E,X2<%E,X3<%E]: IsVarArgsOf[(X1, X2, X3), E] = null
implicit def Tuple4[E,X1<%E,X2<%E,X3<%E,X4<%E]: IsVarArgsOf[(X1, X2, X3, X4), E] = null
// ... and so on ...
}
OK, actually I lied, we're not done yet. Because we are now accepting different types of elements (so long as they are convertible to a common type, we cannot just cast them to the expected type (this would lead to a runtime cast error) but instead we have to apply the implicit conversions. We can rework it like this:
abstract sealed class IsVarArgsOf[P, E] {
def args( p: P ): Iterator[E]
}; object IsVarArgsOf {
implicit def Tuple2[E,X1<%E,X2<%E] = new IsVarArgsOf[(X1, X2), E]{
def args( p: (X1, X2) ) = Iterator[E](p._1, p._2)
}
implicit def Tuple3[E,X1<%E,X2<%E,X3<%E] = new IsVarArgsOf[(X1, X2, X3), E]{
def args( p: (X1, X2, X3) ) = Iterator[E](p._1, p._2, p._3)
}
implicit def Tuple4[E,X1<%E,X2<%E,X3<%E,X4<%E] = new IsVarArgsOf[(X1, X2, X3, X4), E]{
def args( p: (X1, X2, X3, X4) ) = Iterator[E](p._1, p._2, p._3, p._4)
}
// ... and so on ...
}
implicit class RichProduct[P]( val product: P ) {
def args[E]( implicit isVarArg: P IsVarArgsOf E ): Iterator[E] = {
isVarArg.args( product )
}
}
This fixes the problem with numeric widening, and we still get a compile when mixing unrelated types:
scala> Foo.bar(1,2,"three")
<console>:22: error: too many arguments for method bar: (values: Values)Unit
Foo.bar(1,2,"three")
^
Edit:
The var-args implicit will never be picked because repeated parameters are not really first class citizens when it comes to types... they are only there when checking for applicability of a method to arguments.
So basically, when you call Foo.bar(1,2,3) it checks if bar is defined with variable arguments, and since it isn't, it isn't applicable to the arguments. And it can't go any further:
If you had called it with a single argument, it would have looked for an implicit conversion from the argument type to the expected type, but since you called with several arguments, there is an arity problem, there is no way it can convert multiple arguments to a single one with an implicit type conversion.
But: there is a solution using auto-tupling.
Foo.bar(1,2,3)
can be understood by the compiler as
Foo.bar((1,2,3))
which means an implicit like this one would work:
implicit def fromInts[T <: Product](t: T) = Values()
// or simply
implicit def fromInts(t: Product) = Values()
The problem with this is that the only way to get the arguments is via t.productIterator, which returns a Iterator[Any] and needs to be cast.
So you would lose type safety; this would compile (and fail at runtime when using it):
Foo.bar("1", "2", "3")
We can make this fully type-safe using Scala 2.10's implicit macros. The macro would just check that the argument is indeed a TupleX[Int, Int, ...] and only make itself available as an implicit conversion if it passes that check.
To make the example more useful, I changed Values to keep the Int arguments around:
case class Values(xs: Seq[Int])
object Values {
implicit def fromInt (x : Int ) = Values(Seq(x))
implicit def fromInts[T<: Product](t: T): Values = macro Macro.fromInts_impl[T]
}
With the macro implementation:
import scala.language.experimental.macros
import scala.reflect.macros.Context
object Macro {
def fromInts_impl[T <: Product: c.WeakTypeTag](c: Context)(t: c.Expr[T]) = {
import c.universe._
val tpe = weakTypeOf[T];
// abort if not a tuple
if (!tpe.typeSymbol.fullName.startsWith("scala.Tuple"))
c.abort(c.enclosingPosition, "Not a tuple!")
// extract type parameters
val TypeRef(_,_, tps) = tpe
// abort it not a tuple of ints
if (tps.exists(t => !(t =:= typeOf[Int])))
c.abort(c.enclosingPosition, "Only accept tuples of Int!")
// now, let's convert that tuple to a List[Any] and add a cast, with splice
val param = reify(t.splice.productIterator.toList.asInstanceOf[List[Int]])
// and return Values(param)
c.Expr(Apply(Select(Ident(newTermName("Values")), newTermName("apply")),
List(param.tree)))
}
}
And finally, defining Foo like this:
object Foo {
def bar(values: Values) { println(values) }
}
You get type-safe invocation with syntax exactly like repeated parameters:
scala> Foo.bar(1,2,3)
Values(List(1, 2, 3))
scala> Foo.bar("1","2","3")
<console>:13: error: too many arguments for method bar: (values: Values)Unit
Foo.bar("1","2","3")
^
scala> Foo.bar(1)
Values(List(1))
The spec only specifies the type of repeated parameters (varargs) from inside of a function:
The type of such a repeated parameter inside the method is then the sequence type scala.Seq[T ].
It does not cover the type anywhere else.
So I assume that the compiler internally - in a certain phase - cannot match the types.
From this observation (this does not compile => "double definition"):
object Values {
implicit def fromInt(x: Int) = Values()
implicit def fromInts(xs: Int*) = Values()
implicit def fromInts(xs: Seq[Int]) = Values()
}
it seems to be Seq[]. So the next try is to make it different:
object Values {
implicit def fromInt(x: Int) = Values()
implicit def fromInts(xs: Int*) = Values()
implicit def fromInts(xs: Seq[Int])(implicit d: DummyImplicit) = Values()
}
this compiles, but this does not solve the real problem.
The only workaround I found is to convert the varargs into a sequence explicitly:
def varargs(xs: Int*) = xs // return type is Seq[Int]
Foo.bar(varargs(1, 2, 3))
but this of course is not what we want.
Possibly related: An implicit conversion function has only one parameter. But from a logical (or the compiler's temporary) point of view, in case of varargs, it could be multiple as well.
As for the types, this might be of interest
Here is a solution which does use overloading (which I would prefer not to)
object Values {
implicit def fromInt (x : Int ) = Values()
implicit def fromInts(xs: Seq[Int]) = Values()
}
case class Values()
object Foo {
def bar(values: Values) { println("ok") }
def bar[A](values: A*)(implicit asSeq: Seq[A] => Values) { bar(values: Values) }
}
Foo.bar(0)
Foo.bar(1,2,3)

Adding a pairwise difference to generic collections - implicit resolution doesn't kick in

Ok, so I have this:
implicit final class RichIterableLike[A, Repr <: IterableLike[A, Repr]](val it: Repr)
extends AnyVal {
def pairDiff[To](implicit num: Numeric[A], cbf: CanBuildFrom[Repr, A, To]): To = {
val b = cbf(it)
val iter = it.iterator
if (iter.hasNext) {
var pred = iter.next()
while (iter.hasNext) {
import num.mkNumericOps
val succ = iter.next()
b += succ - pred
pred = succ
}
}
b.result()
}
}
This compiles, but doesn't kick in:
val stabs = IndexedSeq(1.0, 2.0, 3.0)
stabs.pairDiff
Gives: value pairDiff is not a member of IndexedSeq[Double]
Explicit conversion works:
new RichIterableLike[Double, IndexedSeq[Double]](stabs).pairDiff
... how to fix this?
EDIT
If I apply the approach of this answer, it works:
implicit final class RichIterableLike[A, CC[~] <: Iterable[~]](val it: CC[A])
extends AnyVal {
def pairDiff[To](implicit num: Numeric[A], cbf: CanBuildFrom[CC[A], A, To]): To = {
...
}
But the question remains, what is the crucial difference that makes the implicit lookup kick in in the latter case.
In order for the implicit lookup to work it needs a link between A and Repr (IterableLike demands that link). You pass it in as an argument, so that argument should be typed as Repr[A]. That means you need to modify your signature so it will look something like this:
RichIterableLike[A, Repr[X] <: IterableLike[X, Repr[X]]](val it: Repr[A])
With the above signature you say:
I have an object that accepts a type parameter, I will name that object Repr and when you pass it in I would like to capture the type parameter as well. I will name that type parameter A. As an extra condition I want the type of Repr to conform to the signature of IterableLike

How to set up implicit conversion to allow arithmetic between numeric types?

I'd like to implement a class C to store values of various numeric types, as well as boolean. Furthermore, I'd like to be able to operate on instances of this class, between types, converting where necessary Int --> Double and Boolean -> Int, i.e., to be able to add Boolean + Boolean, Int + Boolean, Boolean + Int, Int + Double, Double + Double etc., returning the smallest possible type (Int or Double) whenever possible.
So far I came up with this:
abstract class SemiGroup[A] { def add(x:A, y:A):A }
class C[A] (val n:A) (implicit val s:SemiGroup[A]) {
def +[T <% A](that:C[T]) = s.add(this.n, that.n)
}
object Test extends Application {
implicit object IntSemiGroup extends SemiGroup[Int] {
def add(x: Int, y: Int):Int = x + y
}
implicit object DoubleSemiGroup extends SemiGroup[Double] {
def add(x: Double, y: Double):Double = x + y
}
implicit object BooleanSemiGroup extends SemiGroup[Boolean] {
def add(x: Boolean, y: Boolean):Boolean = true;
}
implicit def bool2int(b:Boolean):Int = if(b) 1 else 0
val n = new C[Int](10)
val d = new C[Double](10.5)
val b = new C[Boolean](true)
println(d + n) // [1]
println(n + n) // [2]
println(n + b) // [3]
// println(n + d) [4] XXX - no implicit conversion of Double to Int exists
// println(b + n) [5] XXX - no implicit conversion of Int to Boolean exists
}
This works for some cases (1, 2, 3) but doesn't for (4, 5). The reason is that there is implicit widening of type from lower to higher, but not the other way. In a way, the method
def +[T <% A](that:C[T]) = s.add(this.n, that.n)
somehow needs to have a partner method that would look something like:
def +[T, A <% T](that:C[T]):T = that.s.add(this.n, that.n)
but that does not compile for two reasons, firstly that the compiler cannot convert this.n to type T (even though we specify view bound A <% T), and, secondly, that even if it were able to convert this.n, after type erasure the two + methods become ambiguous.
Sorry this is so long. Any help would be much appreciated! Otherwise it seems I have to write out all the operations between all the types explicitly. And it would get hairy if I had to add extra types (Complex is next on the menu...).
Maybe someone has another way to achieve all this altogether? Feels like there's something simple I'm overlooking.
Thanks in advance!
Okay then, Daniel!
I've restricted the solution to ignore Boolean, and only work with AnyVals that have a weak Least Upper Bound that has an instance of Numeric. These restrictions are arbitrary, you could remove them and encode your own weak conformance relationship between types -- the implementation of a2b and a2c could perform some conversion.
Its interesting to consider how implicit parameters can simulate inheritance (passing implicit parameters of type (Derived => Base) or Weak Conformance. They are really powerful, especially when the type inferencer helps you out.
First, we need a type class to represent the Weak Least Upper Bound of all pairs of types A and B that we are interested in.
sealed trait WeakConformance[A <: AnyVal, B <: AnyVal, C] {
implicit def aToC(a: A): C
implicit def bToC(b: B): C
}
object WeakConformance {
implicit def SameSame[T <: AnyVal]: WeakConformance[T, T, T] = new WeakConformance[T, T, T] {
implicit def aToC(a: T): T = a
implicit def bToC(b: T): T = b
}
implicit def IntDouble: WeakConformance[Int, Double, Double] = new WeakConformance[Int, Double, Double] {
implicit def aToC(a: Int) = a
implicit def bToC(b: Double) = b
}
implicit def DoubleInt: WeakConformance[Double, Int, Double] = new WeakConformance[Double, Int, Double] {
implicit def aToC(a: Double) = a
implicit def bToC(b: Int) = b
}
// More instances go here!
def unify[A <: AnyVal, B <: AnyVal, C](a: A, b: B)(implicit ev: WeakConformance[A, B, C]): (C, C) = {
import ev._
(a: C, b: C)
}
}
The method unify returns type C, which is figured out by the type inferencer based on availability of implicit values to provide as the implicit argument ev.
We can plug this into your wrapper class C as follows, also requiring a Numeric[WeakLub] so we can add the values.
case class C[A <: AnyVal](val value:A) {
import WeakConformance.unify
def +[B <: AnyVal, WeakLub <: AnyVal](that:C[B])(implicit wc: WeakConformance[A, B, WeakLub], num: Numeric[WeakLub]): C[WeakLub] = {
val w = unify(value, that.value) match { case (x, y) => num.plus(x, y)};
new C[WeakLub](w)
}
}
And finally, putting it all together:
object Test extends Application {
val n = new C[Int](10)
val d = new C[Double](10.5)
// The type ascriptions aren't necessary, they are just here to
// prove the static type is the Weak LUB of the two sides.
println(d + n: C[Double]) // C(20.5)
println(n + n: C[Int]) // C(20)
println(n + d: C[Double]) // C(20.5)
}
Test
There's a way to do that, but I'll leave it to retronym to explain it, since he wrote this solution. :-)