Test.scala:18:48: type mismatch;
[error] found : cents.type (with underlying type Int)
[error] required: shapeless.tag.Tagged[Test.MoneyTag] with Int
[error] def cash2(cents: Int): Money = tag[MoneyTag](cents)
[error] ^
[error] one error found
This type is different from Tagged[MoneyTag] with Int, which the compiler is asking for. Not sure what’s going on, I would probably ask on Shapeless’ Gitter to see if anyone can explain.
Thanks for the link to an alternative. Looks neat.
What I don’t understand is that the call to add100 is accepted, so cash has to produce a subtype of Money, whatever that type is. If so, why can’t it have return type Money? Whatever it’s doing, shapeless.tag doesn’t seem quite ready for prime time. I was just looking for an alternative to value classes. I’ll have a look at your newtype setup.
Agreed, the implementation in Shapeless seems to have been thrown in there after a successful Stack Overflow answer showed off the neat trick. Anyway, I learned today about https://github.com/estatico/scala-newtype , which might also work for you (I haven’t tried it).
I remember Martin Odersky saying he wanted Dotty to have intersection types (A & B) because one of the problems with A with B is that it’s not commutative. We can demonstrate this:
scala> trait A { def x = 1 }
defined trait A
scala> trait B { def x = 2 }
defined trait B
scala> object AwithB extends A with B { override def x = super.x }
defined object AwithB
scala> AwithB.x
res4: Int = 2
scala> object BwithA extends B with A { override def x = super.x }
defined object BwithA
scala> BwithA.x
res5: Int = 1
If the implementation is different, the type is ipso facto different:
trait A { def x = 1 }
trait B { def x = 2 }
class AwithB extends A with B { override def x = super.x }
class BwithA extends B with A { override def x = super.x }
val aWithB: AwithB = new BwithA // error: type mismatch
val bWithA: BwithA = new AwithB // error: type mismatch
You are merely demonstrating that a class that extends A with B is not the same type as a class that extends B with A. Two classes are always different types, even if they extend the exact same type. Therefore, this does not demonstrate that A with B is distinct from B with A.
What I meant to say is that in terms of type inference and type checking, there is no difference between A with B and B with A. Each is assignable to the other. You cannot write a method that accepts one but not the other as an argument. A type error cannot be caused by the difference between the two, unless the compiler has a bug.
But you are also right by pointing out that at the same time, instances of A with B are distinct from instances of B with A.
I guess the correct way of talking about this is that A with B and B with A are the same reference type, but distinct object types.
aWithB and bWithA are distinct (and incompatible) types, but I still think A with B and B with A are the same type. I don’t think you can find a value that would have type A with B but not B with A (and vice-versa).
trait A
trait B
type AB = A with B
type BA = B with A
def ab: AB = null
def ba: BA = null
def f(x: AB) = ()
def g(x: BA) = ()
f(ba)
g(ab)
val x = new A with B
val y = new B with A
f(x)
f(y)
g(x)
g(y)
I’m not an expert in this subject, but don’t they linearize differently? Yes, A with B and B with A are very, very similar, but wouldn’t they behave differently in terms of diamond inheritance? I’m actually a bit surprised there aren’t type errors here…
scala> trait A ; trait B
defined trait A
defined trait B
scala> typeOf[A with B]
res1: $r.intp.global.Type = A with B
scala> typeOf[B with A]
res2: $r.intp.global.Type = B with A
scala> res1 <:< res2
res3: Boolean = true
scala> res2 <:< res1
res4: Boolean = true
scala> res2 =:= res1
res5: Boolean = false
scala> res1 =:= res2 // fool me twice, shame on me
res6: Boolean = false