2

I'm learning Scala by working the exercises from the book "Scala for the Impatient". One question asks:

Given a mutable Pair[S, T] class, use a type constraint to define a swap method that can be called if the type parameters are the same.

My code:

class Pair[T, S](var first: T, var second: S) {
  def swap(implicit ev: T =:= S) {
    val temp = first
    first = second // doesn't compile
    second = temp
  }
}

The code above fails to compile with the complaint that first and second are different types. Well, I just nicely told the compiler that they're not. How can I tell it to shut up?

Ashkan Kh. Nazary
  • 21,844
  • 13
  • 44
  • 68
Abhijit Sarkar
  • 21,927
  • 20
  • 110
  • 219

1 Answers1

8

You've just told the compiler that types passed to your class as T and S should be equal - you just require an evidence of their equality, which can be used to infer actual T and S correctly when you pass actual types (but not inside generic class itself). It doesn't mean that T and S are interchangable. Btw, it doesn't change anything but you did a mistake by defining new S and T, should be:

  class Pair[T, S](var first: T, var second: S) {
    def swap(implicit ev: T =:= S) { //without [S, T] - they are unrelated to original ones, it's whole new names
      val temp = first
      first = second // doesn't compile
      second = temp
    }
  }

However it's still doesn't compile. Why? Just think of it:

 def getBoth(implicit ev: T =:= S) = List(first, second)

So what is return type compiler should infer? List[T] or List[S]. the only it can do is List[Any]. So having same and different types simulteniously has no sense. If you want different names - just use type S = T no evidence will be needed.

And what is the real case of having S and T different in constructor and same in some method. It's simply logically incorrect (when talking about abstract types - not concrete substitutions).

Btw, =:= is not the compiler feature - there is no special processing for that in compiler. The whole implementation is it inside scala Predef:

@implicitNotFound(msg = "Cannot prove that ${From} =:= ${To}.")
  sealed abstract class =:=[From, To] extends (From => To) with Serializable
  private[this] final val singleton_=:= = new =:=[Any,Any] { def apply(x: Any): Any = x }
  object =:= {
     implicit def tpEquals[A]: A =:= A = singleton_=:=.asInstanceOf[A =:= A]
  }

So it's just tpEquals[A] implicit which takes type A and gives you A =:= A - when you require T =:= U it's trying to make such conversion which is possible only for equal types. And the process of checking implicit itself actually happens only when you pass actual T and U, not when you defining them.

About your particular problem:

class Pair[T, S](var first: T, var second: S) {
    def swap(implicit ev: T =:= S) { 
      val temp = first
      first = second.asInstanceOf[T] 
      second = temp.asInstanceOf[S]
    }
}

scala> new Pair(5,6)
res9: Pair[Int,Int] = Pair@6bfc12c4

scala> res9.swap

scala> res9.first
res11: Int = 6

Or just (as @m-z and @Imm suggested):

class Pair[T, S](var first: T, var second: S) {
    def swap(implicit ev: T =:= S, ev2: S =:= T) {
      val temp = first
      first = second 
      second = temp
    }
}

T =:= S extends T => S and this implicitly added function (even as an object) is interpreted as implicit conversion from T to S in Scala, so it works like both types are equal, which is pretty cool.

dk14
  • 22,206
  • 4
  • 51
  • 88
  • How'd you solve the given problem then? From the problem statement "Given a mutable Pair[S, T] class...", I'm guessing the author doesn't want me to define the implicit evidence on the class level but only on the method level. – Abhijit Sarkar Jun 08 '15 at 01:45
  • you already solved this - just do `asInstanceOf[S]`, `asInstanceOf[T]`. I've just meant that compiler cannot do it for you automatically in general case – dk14 Jun 08 '15 at 02:06
  • 1
    Why cast when you can call `ev(first)`, etc? – Michael Zajac Jun 08 '15 at 02:23
  • @m-z you can do it only for `second = ev(temp)`, but not for `first = second` (`=:=` extends `T => S` only), so just looks not so symmetric :) – dk14 Jun 08 '15 at 03:32
  • 2
    @dk14 you could resolve that by requiring both a `T =:= S` and an `S =:= T`. – lmm Jun 08 '15 at 08:28
  • 1
    "And what is the real case of having S and T different in constructor (!) and same in some method. It's simply logically incorrect." I don't think that's right. It basically means: Ok, compiler, let `Pair` have fields of any type, but if they happen to be equal, let it also have a method `swap`. As all of this is compile-time, wrong usage won't compile. It doesn't even break any OOP guarantees. As an example look at how `Seq.toMap` is defined: `toMap[T, U](implicit ev: <:<[A, (T, U)])`. Basically it means: `Seq` can hold items of any type, but if it holds pairs, it also has a method `toMap`. – Kolmar Jun 08 '15 at 09:30
  • @Kolmar Was talking from type inference perspective (inside method). I have no objections against what happens outside the method (types may be equal or not at calling point). Anyway `T` and `S` itself are always different. – dk14 Jun 08 '15 at 09:38
  • @dk14 BTW, with two evidences you don't need casts `ev2(second)`, `ev(temp)`. Those casts are done implicitly, so you can just write `first = second; second = temp` – Kolmar Jun 08 '15 at 10:06
  • @dk14 I'm sorry, I don't understand. "types may be equal or not at calling point" Not at calling point, but at Pair construction, right? "Anyway T and S itself are always different." Unless the Pair was created with them equal, e.g. `Pair[Int, Int]`. To check that, `ev: S =:= T` is supplied. You wrote: "And what is the real case of having S and T different in constructor (!) and same in some method." The case is, we want Pair to have additional methods (`swap`), if it was constructed with the same S and T. – Kolmar Jun 08 '15 at 10:21
  • @Kolmar I'm just talking about types `T` and `S` before substitution to `Int, Int` (in constructor or in method whatever - `T` and `S` are not known), there is no practical case to have same types but different names there. At calling point (in place [constructor or method] where substitution to `T` and `S` is actually occurs) we can talk about their actual equality - that's the place where "The case is, we want Pair to have additional methods (swap), if it was constructed with the same S and T" works. – dk14 Jun 08 '15 at 14:28
  • I don't understand why the compiler requires the 2nd implicit. T =:= S but not the inverse is simply not possible by the definition of equality. – Abhijit Sarkar Jun 12 '15 at 05:40
  • 1
    @Abhijit Sarkar As I said in first place, `=:=` is not a compiler feature, it's implicit-based trick. `T =:= S` and `S =:= T` is same from required-for-outside-world equality perspective, but not inside your method. So, you steel need to convert `S to T` (`first = second`) and `T to S`(`second = temp`) because **they are not equal types for the compiler**. So the fact that `T =:= S` is an instance of function `T => S` helps to do this transparent for you (and avoid `asInstanceOf`) so `T =:= S` also acts like an implicit conversion from `T` to `S` (which actually does `asInstanceOf` inside) – dk14 Jun 12 '15 at 05:48
  • I don't care how the feature is implemented in the language if it doesn't make sense. I saw in the Coursera videos Mr. Odersky claims to have modeled the language closely after theoretical mathematics and this violates pre school mathematics. Looks like this is one of those places where the language needs work. No praise where it isn't due. – Abhijit Sarkar Jun 15 '15 at 14:48
  • @Abhijit Sarkar again again again I'm saying, **this is not a feature of the language** and btw there is nothing wrong with non-commutative equality if you're looking to something advanced than pre-school mathematics – dk14 Jun 15 '15 at 14:52
  • @Abhijit Sarkar Talking about mathematics, in group theory non-commutative operations are pretty fine - magmas, semigroups, loops and quasigroups are non-commutative. So, you may probably claim that scala is less mathematically strong than let's say haskell (and I can agree with that - there is a lot of work to do), but arguing about features you didn't studied enough yet has not much sense. – dk14 Jun 15 '15 at 15:04
  • @dk14 I'm not arguing about a feature that I didn't study. I understood your explanation provided in the answer and it's a perfectly reasonable one (thus I accepted it too). I'm not interested in opening an irrelevant discussion here whether in any level of mathematics, equality is a non-commutative notion so I'll leave it there. I'm learning the language so unlike staunch Scala enthusiasts, I didn't develop a notion yet that all things in Scala are the best since sliced bread. – Abhijit Sarkar Jun 21 '15 at 05:57
  • @AbhijitSarkar Discussion about mathematics is absolutely relevant here as Scala is built in respect to abstract algebra (which includes theory of groups and cathegory theory), and it's one of its killer features, as it gives you an ability to proof your code (more-less) mathematically instead of operating some semi-religious OOP-patterns. You won't get much profit from scala without learning background. Science, b**** :). – dk14 Jun 21 '15 at 07:53
  • @AbhijitSarkar And Scala is not the best in this kind - Haskell is more advanced but much harder to learn and use in practice. Scala is just trying to "run with the hare and hunt with the hounds" – dk14 Jun 21 '15 at 07:55