1

I have a case class that looks like this:

case class SomeType[T](x: T)

I now want to provide an empty initialization method via a companion object like this:

object SomeType {
  def empty = SomeType(???)
}

How can I instantiate a generic type? I obviously do not want to instantiate it to null or Nill or Any! Is there a better way?

1 Answer 1

3

The issue with your question as stated is that SomeType[T] has a value x of type T. What value should x take for empty? The only sensible answer is that it should be Nothing but the type Nothing is uninhabited. That is, you cannot do this:

def empty[A]: SomeType[A] = SomeType(???)

And you cannot make x a by-name parameter (i.e. => T) because you have a case class (and it would be dumb, even if you could, because someone could do this):

val st: SomeType[Int] = SomeType.empty //would require T to be covariant
val i = st.x //throws Exception

Let's take a step back and start without x:

sealed trait SomeType[T]
object SomeType {
  def empty[A]: SomeType[A]          = new SomeType[A] {}
  def nonEmpty[A](a: A); SomeType[A] = new SomeType[A] {}
} 

If you are modelling something a bit like Option it's worth thinking about what the fundamental combinator it should have is. Here's one way of encoding Option for example:

sealed trait MyOption[T] {

  def fold[B](n: => B, s: T => B): B //fundamental combinator

  //functionality
  import MyOption._
  final def isEmpty                                      = fold(true, _ => false)
  final def flatMap[B](f: A => MyOption[B]): MyOption[B] = fold(empty, f)
  final def map[B](f: A => B): MyOption[B]               = fold(empty, f andThen nonEmpty)
}

object MyOption {
  //These are effectively the constructors
  def empty[A]: MyOption[A] = new MyOption[A] {
    override final def fold[B](n: => B, s: A => B): B = n 
  }
  def nonEmpty[A](a: A): MyOption[A] = new MyOption[A] {
    override final def fold[B](n: => B, s: A => B): B = s(a) 
  }
} 

You could make it covariant and make empty a val:

sealed trait MyOption[+T] {
  def fold[B](n: => B, s: T => B): B
}
object MyOption {
  val empty: MyOption[Nothing] = new MyOption[Nothing] {
    override final def fold[B](n: => B, s: Nothing => B): B = n 
  }
  def nonEmpty[A](a: A): MyOption[A] = new MyOption[A] {
    override final def fold[B](n: => B, s: A => B): B = s(a) 
  }
} 

Or as Yuval says below, make empty a singleton:

object MyOption {
  object empty extends MyOption[Nothing] {
    override final def fold[B](n: => B, s: Nothing => B): B = n 
  }
  def nonEmpty[A](a: A): MyOption[A] = new MyOption[A] {
    override final def fold[B](n: => B, s: A => B): B = s(a) 
  }
} 

I generally prefer not to do this because I would rather use the combinators than an explicit pattern-match, which is less amenable to refactoring

Sign up to request clarification or add additional context in comments.

1 Comment

What do you think about creating a sealed trait hierarchy with a case object representing the empty case?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.