3

I came from C++ world and new to Scala, and this behavior looks unusual.

class G1[A]( val a : A) {
  //val c:A = new A //This gives compile error
  def fcn1(b: A): Unit = {
    //val aobj = new A // This gives compile error
    println(a.getClass.getSimpleName)
    println(b.getClass.getSimpleName)
  }
}

def fcnWithTP[A](): Unit = {
  //val a = new A // This gives compile error
  //println(a.getClass.getSimpleName)
}

I am not able to crate a object using the type parameter in a class in a function body or a class body. I am only be able to use it in the function parameter.

What is the reason for this? Is this because of type erasure? At run time, the function does not know what the actual type A is, so it cannot create an object of that type?

What is the general rule for this? Does it that mean the type parameter cannot appear in function body or class definition at all? If they can actually appear, what are the examples?

szli
  • 36,893
  • 11
  • 32
  • 40
  • It looks like it's because the compiler can' t work out `A` is a class. This question looks relevant: http://stackoverflow.com/questions/6200253/scala-classof-for-type-parameter (it's using `classOf` rather than `new` but the principle is the same, I think) – The Archetypal Paul Apr 03 '16 at 17:23
  • 1
    And to answer your main point more generally, yes, type parameters can appear in function bodies, to declare return types etc when you need a type, but you're asking for more in your code: that the type be used as a class. – The Archetypal Paul Apr 03 '16 at 17:24

3 Answers3

6

Yes, you're right that this is because of erasure—you don't know anything about A at runtime that you haven't explicitly asserted about it as a constraint in the method signature.

Type erasure on the JVM is only partial, so you can do some horrible things in Scala like ask for the class of a value:

scala> List(1, 2, 3).getClass
res0: Class[_ <: List[Int]] = class scala.collection.immutable.$colon$colon

Once you get to generics, though, everything is erased, so for example you can't tell the following things apart:

scala> List(1, 2, 3).getClass == List("a", "b", "c").getClass
res1: Boolean = true

(In case it's not clear, I think type erasure is unambiguously a good thing, and that the only problem with type erasure on the JVM is that it's not more complete.)

You can write the following:

import scala.reflect.{ ClassTag, classTag }

class G1[A: ClassTag](val a: A) {
  val c: A = classTag[A].runtimeClass.newInstance().asInstanceOf[A]
}

And use it like this:

scala> val stringG1: G1[String] = new G1("foo")
stringG1: G1[String] = G1@33d71170

scala> stringG1.c
res2: String = ""

This is a really bad idea, though, since it will crash at runtime for many, many type parameters:

scala> class Foo(i: Int)
defined class Foo

scala> val fooG1: G1[Foo] = new G1(new Foo(0))
java.lang.InstantiationException: Foo
  at java.lang.Class.newInstance(Class.java:427)
  ... 43 elided
Caused by: java.lang.NoSuchMethodException: Foo.<init>()
  at java.lang.Class.getConstructor0(Class.java:3082)
  at java.lang.Class.newInstance(Class.java:412)
  ... 43 more

A better approach is to pass in the constructor:

class G1[A](val a: A)(empty: () => A) {
  val c: A = empty()
}

And a much better approach is to use a type class:

trait Empty[A] {
  def default: A
}

object Empty {
  def instance[A](a: => A): Empty[A] = new Empty[A] {
    def default: A = a
  }

  implicit val stringEmpty: Empty[String] = instance("")
  implicit val fooEmpty: Empty[Foo] = instance(new Foo(0))
}

class G1[A: Empty](val a: A) {
  val c: A = implicitly[Empty[A]].default
}

And then:

scala> val fooG1: G1[Foo] = new G1(new Foo(10101))
fooG1: G1[Foo] = G1@5a34b5bc

scala> fooG1.c
res0: Foo = Foo@571ccdd0

Here we're referring to A in the definition of G1, but we're only making reference to properties and operations that we've confirmed hold or are available at compile time.

Travis Brown
  • 138,631
  • 12
  • 375
  • 680
4

Generics are not the same thing as templates. In C++ Foo<Bar> and Foo<Bat> are two different classes, generated at compile time. In scala or java, Foo[T] is a single class that has with a type parameter. Consider this:

  class Foo(val bar)
  class Bar[T] {
    val foo = new T // if this was possible ... 
  }

  new Bar[Foo]

In C++, (an equivalent of) this would fail to compile, because there is no accessible constructor of Foo that takes no arguments. The compiler would know that when it tried to instantiate a template for Bar<Foo> class, and fail.

In scala, there is no separate class for Bar[Foo], so, at compilation time, the compiler doesn't know anything about T, other than that it is some type. It has no way of knowing whether calling a constructor (or any other method for that matter) is possible or sensible (you can't instantiate a trait for example, or an abstract class), so new T in that context has to fail: it simply does not make sense.

Roughly speaking, you can use type parameters in places where any type can be used (do declare a return type for example, or a variable), but when you are trying to do something that only works for some types, and not for others, you have to make your type param more specific. For example, this: def foo[T](t: T) = t.intValue does not work, but this: def foo[T <: Number](t: T) = t.intValue does.

Dima
  • 39,570
  • 6
  • 44
  • 70
1

Well the compiler does not know how to create an instance of type A. You need to either provide a factory function that returns instance of A, or use Manifest which creates instance of A from reflection.

With factory function:

class G1[A](val a:A)(f: () => A) {
  val c:A = f()
}

With Manifest:

class G1[A](val a: A)(implicit m: scala.reflect.Manifest[A]) {
  val c: A = m.erasure.newInstance.asInstanceOf[A]
}

When using type parameter, usually you will specify more details on the type A, unless you're implementing some sort of container for A that does not directly interact with A. If you need to interact with A, you need some specification on it. You can say A must be a subclass of B

class G1[A <: B](val a : A)

Now compiler would know A is a subclass of B so you can call all functions defined in B on a:A.

jiulongw
  • 355
  • 3
  • 5
  • It's worth noting that while `Manifest` isn't formally deprecated (there's been a deprecation line in the source since 2.10.0, but it's commented out), it is in the process of being replaced by `ClassTag`, and in general if `ClassTag` works (as it does here), there are good reasons to prefer it. – Travis Brown Apr 03 '16 at 19:30