3

In the end, I want to have a case class Swap so that Swap(a, b) == Swap(b, a).

I thought I could use a Set of two elements, and it quite does the job :

scala> case class Swap(val s:Set[Int])
defined class Swap

scala> Swap(Set(2, 1)) == Swap(Set(1, 2))
res0: Boolean = true

But this allows for any number of elements, and I would like to limit my elements to two. I found the class Set.Set2, which is the default implementation for an immutable Set with two elements, but it doesn't work the way I tried, or variations of it :

scala> val a = Set(2, 1)
a: scala.collection.immutable.Set[Int] = Set(2, 1)

scala> a.getClass
res3: Class[_ <: scala.collection.immutable.Set[Int]] = class scala.collection.immutable.Set$Set2

scala> case class Swap(val s:Set.Set2[Int])
defined class Swap

scala> val swp = Swap(a)
<console>:10: error: type mismatch;
 found   : scala.collection.immutable.Set[Int]
 required: Set.Set2[Int]
       val swp = Swap(a)
                      ^

So my questions are :

  • is there a way to use Set2 as I try ?
  • is there a better way to implement my case class Swap ? I read that one shouldn't override equals in a case class, though it was my first idea.

3 Answers3

3

This is a generic implementation -

import scala.collection.immutable.Set.Set2

def set2[T](a: T, b: T): Set2[T] = Set(a, b).asInstanceOf[Set2[T]]

case class Swap[T](s: Set2[T])

Swap(set2(1,2))  == Swap(set2(2,1)) //true

The reason that your solution didn't work is because of the signature

Set(elems: A*): Set

In case of 2 elements the concrete type will be Set2 but the compiler doesn't know that so you have to cast it to Set2

Maxim
  • 7,268
  • 1
  • 32
  • 44
  • 1
    Thank you, this is the solution I used. I also added a second contructor `def this(t1: T, t2: T) = this(Set(t1, t2).asInstanceOf[Set2[T]])` for convenience, which allowed me to construct swaps with `new Swap(t1, t2)`. Finally I used pattern matching this way : `case Swap(s:Set2[T]) => { val t1 = s.head val t2 = s.last ... }` – Guillaume Deshors Feb 06 '15 at 13:00
1

You can always hide the implementation details of Swap, in this case you actually should.

You could implement it using Set or you could implement it as:

 // invariant a <= b
 class Swap private (val a: Int, val b: Int)

 object Swap {
   def apply(a: Int, b: Int): Swap =
     if (a <= b) new Swap(a, b) else new Swap(b, a)
 }

Unfortunately you have to use class here and reimplement equals hashCode etc yourself, as we cannot get rid of scalac auto-generated apply: related SO Q/A

And make all functions on Swap maintain that invariant.

Then equals comparion is essentially this.a == other.a && this.b == other.b, we don't need to care about swapping anymore.

Community
  • 1
  • 1
phadej
  • 11,947
  • 41
  • 78
  • I would add the override for equals in order to get the result @Guillaume expects. Also, it would be very useful to define some extractor, that way the equality will also work nice on pattern matching – Logain Feb 06 '15 at 09:04
  • The point of keeping invariant, and not-allowing constructing invalid objects makes structural equality behave as we want. – phadej Feb 06 '15 at 09:25
  • It doesn't seem to compile : error: constructor Swap in class Swap cannot be accessed in object Swap. And most of all, this is not a case class anymore, and therefore doesn't really answer the question. But thank you anyway ! – Guillaume Deshors Feb 06 '15 at 09:52
  • @phadej while what you say is true, since you leverage on the elements being Ints, but they could be of any generic type (as per the question asked, where the Ints were merely examples), the part `a <= b would't work` – Logain Feb 06 '15 at 10:02
  • The OP uses Int as example, so I use it too. AFAIK set uses `hashCode` to order the elements, so can be used here - the change is minimal. – phadej Feb 06 '15 at 10:06
  • @GuillaumeDeshors create `Swap` object with `Swap(1,2)` not with `new Swap(1,2)`. *case classes* are normal classes, for which `scalac` generates bunch of useful stuff - no more no less. In this case, we don't really like default `apply` though. – phadej Feb 06 '15 at 10:07
1

The problem is that you don't know statically that a is a Set2 - as far as the compiler is concerned you called Set(as: A*) and got back some kind of Set.

You could use shapeless sized collections to enforce a statically known collection size.

lmm
  • 17,386
  • 3
  • 26
  • 37
  • Yes I called `Set(as: A*)` because I couldn't find a way to construct directly a `Set2`... – Guillaume Deshors Feb 06 '15 at 09:56
  • Yeah, the constructor is `private[collections]`. You could invoke it by reflection, or perhaps more usefully you could pattern-match: `a match { case s2: Set2 => Swap(s2) }`. But this is not entirely safe; if `a` isn't a `Set2` it will throw a `MatchError` at runtime. – lmm Feb 06 '15 at 09:58