How to create object/singleton of generic type in Scala?

11.5k views Asked by At

In the code shown below, how can I convert EmptyTree to object (Singleton) ?

trait Tree[T] {
    def contains(num: T): Boolean
    def inc( num: T ): Tree[T]
  }


class EmptyTree[T <% Ordered[T] ] extends Tree[T] {
    def contains(num:T):Boolean = false
    def inc(num:T):Tree[T] = {
        new DataTree(num, new EmptyTree, new EmptyTree)
    }
    override def toString = "."
}

class DataTree[T <% Ordered[T] ](val x:T, val left:Tree[T], val right:Tree[T]) extends Tree[T] {

    def contains(num:T):Boolean = {
        if( num < x ) left.contains(x)
        else if ( num > x ) right.contains(x)
        else true
    }
    def inc(num:T):Tree[T] = {
        if(num < x ) new DataTree(x, left.inc(num), right)
        else if ( num > x ) new DataTree(x, left, right.inc(num))
        else this
    }
    override def toString = "{" + left + x + right + "}"
}


val t = new DataTree(20, new EmptyTree[Int], new EmptyTree[Int])
                                                //> t  : greeting.Test.DataTree[Int] = {.20.}
val p = t.inc(10)                               //> p  : greeting.Test.Tree[Int] = {{.10.}20.}
val a = p.inc(30)                               //> a  : greeting.Test.Tree[Int] = {{.10.}20{.30.}}
val s = a.inc(5)                                //> s  : greeting.Test.Tree[Int] = {{{.5.}10.}20{.30.}}
val m = s.inc(11)                               //> m  : greeting.Test.Tree[Int] = {{{.5.}10{.11.}}20{.30.}}
3

There are 3 answers

3
Odomontois On BEST ANSWER

Let me detalize Alexey's answer. Here is full implementation with some code style improvements:

First define your trait with aknowledgment of its covariance:

 trait Tree[+T] {
    def contains[U >: T : Ordering](num: U): Boolean

    def inc[U >: T : Ordering](num: U): Tree[U]
  }

Next define your subtype-of-all-trees object

  case object EmptyTree extends Tree[Nothing] {
    def contains[U >: Nothing : Ordering](num: U): Boolean = false
    def inc[U >: Nothing : Ordering](num: U): Tree[U] =
      DataTree(num, EmptyTree, EmptyTree)
    override def toString = "."
  }

Now change your general case implementation:

  case class DataTree[T: Ordering](x: T, left: Tree[T], right: Tree[T]) extends Tree[T] {
    import Ordering.Implicits._
    def contains[U >: T : Ordering](num: U): Boolean = 
      if (num < x) left.contains(x)
      else if (num > x) right.contains(x)
      else true

    def inc[U >: T : Ordering](num: U): Tree[U] = 
      if (num < x) DataTree(x, left.inc(num), right)
      else if (num > x) DataTree(x, left, right.inc(num))
      else this

    override def toString = "{" + left + x + right + "}"
  }

You could be a little bit frustrated since I replaced your Ordered with Ordering, but you should know that view bounds are deprecated

4
yǝsʞǝla On

You have to fix the generic argument because that's the only time you can provide it:

scala> trait A[T]
defined trait A

scala> object B extends A[Int]
defined object B

Obviously you want to reuse EmptyTree for all types of T, so instead of defining A[SOMETYPE] for each type just use bottom type Nothing:

scala> object B extends A[Nothing]
defined object B

This object can be used with any tree.

That's exactly how Option[T] is implemented in Scala. Here is how None is defined:

case object None extends Option[Nothing]
1
Ivan Klass On

If keeping generics, also there is an option to add empty factory - like it's done for Map and Vector. Off course, with such an implementation it will not be a unique instance object for every creation, but when using inc method, it will not produce new objects, it will just reference itself.

object DataTree {
  def empty[T <% Ordered[T]] = new Tree[T] {
      def contains(num: T):Boolean = false
      def inc(num: T): Tree[T] = {
        new DataTree(num, this, this)
      }
      override def toString = "."
  }
}

So you can instantiate it as following:

val t = new DataTree(20, DataTree.empty[Int], DataTree.empty[Int])