In Scala we use mix-in like this:
class C extends A with B
I understand this declaration as C is a subclass of A with B. Is this true? Or C is just subclass of both A and B(I don't think it's possible on JVM which doesn't support multi-inheritance)?
If A with B is a type, why doesn't this line work?
classOf[A with B]
Another reason why I consider A with B a type is the fact that it can be used in pattern match:
val c = new C
val u = c match { case a: A with B => 1 } // 1
Scala supports multiple inheritance via traits. Any class can extend 0 or 1 class, but can also "mix in" any number of traits. (There is a bit of compiler magic that rearranges things behind the scenes to conform to the JVM's limitations) The syntax is along the lines of
class MyClass extends [ClassOrTrait] with [Trait] with [AnotherTrait] with ...
So your class C definition is more like
class ((C extends A) with B) than like class (C extends (A with B))
A with B is a type, and can be used as a type alias, but the classOf method wants a class:
scala> type AB = A with B
defined type alias AB
scala> classOf[AB]
<console>:11: error: class type required but A with B found
classOf[AB]
^
vs
scala> class AB extends A with B
defined class AB
scala> classOf[AB]
res12: Class[AB] = class AB
I agree with #Dylan. A with B is only a type definition. However for it to work with classOf[T] , it needs to have a Java class or interface generated by Scala.
scala> trait A
defined trait A
scala> trait B
defined trait B
scala> trait AB extends A with B
defined trait AB
scala> class C extends A with B
defined class C
scala> type TypeAB = A with B
defined type alias TypeAB
scala> println(classOf[A])
interface $line3.$read$$iw$$iw$A
scala> println(classOf[B])
interface $line4.$read$$iw$$iw$B
scala> println(classOf[AB] )
interface $line5.$read$$iw$$iw$AB
scala> println(classOf[C])
class $line6.$read$$iw$$iw$C
scala> println(TypeAB)
<console>:8: error: not found: value TypeAB
println(TypeAB)
^
scala> classOf[TypeAB]
<console>:11: error: class type required but A with B found
classOf[TypeAB]
Also it is interesting that Scala does manage to match the with in case constructs.
Related
I'm playing with Scala's type system with the following example:
scala> sealed trait A
defined trait A
scala> case class A1(n: Int) extends A
defined class A1
scala> case class A2(n: Int) extends A
defined class A2
scala> case class MyCase[+P <: A](a: A, y: String)
defined class MyCase
scala> val l = List(MyCase[A1](A1(1), "A1"), MyCase[A2](A2(2), "A2"))
Now, when I do the following:
scala> l.head.asInstanceOf[MyCase[A2]]
res2: MyCase[A2] = MyCase(A1(1),A1)
How can a MyCase[A1] instance be assigned to a MyCase[A2] reference? I mean MyCase[A1] and MyCase[A2] are at the same level in the object hierarchy!
As I understand you're wondering why you didn't get an error here in runtime, when you did l.head.asInstanceOf[MyCase[A2]]. So the answer is simple - type erasure. For JVM both types are equal as it ignores generic parameter - it simply doesn't know about them as they exist on compiler level only, so only compiler can give you an exception:
scala> val mcA2: MyCase[A2] = l.head
<console>:15: error: type mismatch;
found : MyCase[Product with Serializable with A]
required: MyCase[A2]
val mcA2: MyCase[A2] = l.head
^
However, asInstanceOf ignores this check. So when you do
case class MyCase[+P <: A](a: P) //P instead of A
scala> val k = MyCase[A1](A1(0)).asInstanceOf[MyCase[A2]]
k: MyCase[A2] = MyCase(A1(0))
scala> val a: A2 = k.a
java.lang.ClassCastException: A1 cannot be cast to A2
... 33 elided
But in your example - you never really used P in runtime (and it's impossible), so you never gonna get ClassCastException. Actually, under these conditions (no real instances), you can do type-level copy like here, but it safer to use traits for that.
I ran into some code that looks pretty much like this:
object SomeOddThing extends App {
val c: C = new C
val a: A = c
val b: B = c
// these will all print None
println(a.x)
println(b.x)
println(c.x)
}
abstract class A {
def x: Option[String] = None
}
abstract class B extends A {
override def x: Option[String] = Some("xyz")
}
class C extends B {
// x now has type None.type instead of Option[String]
override def x = None
}
trait T {
this: B =>
override def x = Some("ttt")
}
// this won't compile with the following error
// error: overriding method x in class C of type => None.type;
// method x in trait T of type => Some[String] has incompatible type
// class D extends C with T {}
// ^
class D extends C with T {}
This looks like a bug to me. Either the type of C.x should be inferred correctly or the class shouldn't compile at all.
The spec 4.6.4 only promises to infer a conforming type for the result type of the overriding method.
See https://issues.scala-lang.org/browse/SI-7212 and be thankful you didn't throw.
Maybe it will change:
https://groups.google.com/forum/#!topic/scala-internals/6vemF4hOA9A
Update:
Covariant result types are natural. It's not grotesque that it infers what it would normally do. And that you can't widen the type again. It's maybe suboptimal, as that other issue puts it. (Speaking to my comment that it's an improvement rather than a bug per se.)
My comment on the ML:
This falls under "always annotate interface methods, including
interface for extension by subclass."
There are lots of cases where using an inferred type for an API method commits you to a type you had rather avoided. This is also true for the interface you present to subclasses.
For this problem, we're asking the compiler to retain the last explicitly ascribed type, which is reasonable, unless it isn't. Maybe we mean don't infer singleton types, or Nothing, or something like that. /thinking-aloud
scala> trait A ; trait B extends A ; trait C extends B
defined trait A
defined trait B
defined trait C
scala> trait X { def f: A }
defined trait X
scala> trait Y extends X { def f: B }
defined trait Y
scala> trait Z extends Y { def f: C }
defined trait Z
scala> trait X { def f: A = new A {} }
defined trait X
scala> trait Y extends X { override def f: B = new B {} }
defined trait Y
scala> trait Z extends Y { override def f: C = new C {} }
defined trait Z
scala> trait ZZ extends Z { override def f: A = new C {} }
<console>:13: error: overriding method f in trait Z of type => C;
method f has incompatible type
trait ZZ extends Z { override def f: A = new C {} }
^
To the question, what does it mean for None.type to conform to Option?
scala> implicitly[None.type <:< Option[_]]
res0: <:<[None.type,Option[_]] = <function1>
to show that None.type, the singleton type of the None object, is in fact an Option.
Usually, one says that the compiler avoids inferring singleton types, even when you kind of want it to.
Programming in Scala says, "Usually such types are too specific to be useful."
As far as I've learned, traits in Scala are similar to interfaces in Java except methods are allowed to have an implementation. Also, in contrast to Scala classes, you can't pass them arguments for construction.
So far, so good. But why am I allowed to instantiate them? Really, I don't see a good reason to allow this.
You don't really instantiate them. As you drew a parallel with Java, let's go further into it. You are able in Java to build a Anonymous class out of an abstract class or of an Interface. It is almost the same in Scala:
scala> trait A
defined trait A
scala> new A {}
res0: A = $anon$1#5736ab79
Note that the curly braces are mandatory when you create an object from a trait. For example, yon cannot do:
scala> new A
<console>:9: error: trait A is abstract; cannot be instantiated
new A
^
While it would works perfectly for a class:
scala> class B
defined class B
scala> new B
res2: B = B#213526b0
Of course if some elements in your trait are not implemented, you need to implement them when you create the object:
scala> trait C {def foo: Int}
defined trait C
scala> new C {}
<console>:9: error: object creation impossible, since method foo in trait C of type => Int is not defined
new C {}
^
scala> new C {def foo = 42}
res4: C = $anon$1#744957c7
Why can I say that a type field has the type of a class with another class mixed into it (when only traits can be mixed in a class) ?
Example:
scala> class A
defined class A
scala> class B extends A
defined class B
Mixing in B to A is not allowed:
scala> new A with B
<console>:10: error: class B needs to be a trait to be mixed in
new A with B
^
But this is possible:
scala> class E {type T = A with B}
defined class E
scala> new E
res1: E = E#1f2bc83
There is a difference between the mixin instantiation and the compound type definition.
First of all the type A with B exists and is exactly the type B, alas it is perfectly legal in scala to write
val x: A with B = new B
as is
val y: Any with AnyRef with A with B = new B
as it describes exactly the same type.
You are just introducing restrictions in the type of the value you can assign to a variable of that type.
These restrictions of course always hold in that case.
Furthermore you have to keep in mind that Scala does not necessarily need a type to be inhabited - i.e. the bottom type Nothing may not be instantiated at all.
But as Nothing is a subtype of every type that can be expressed in Scala it is even valid to write an expression like
def foo: AnyRef with AnyVal = sys.error("IMPOSSIBRU!")
Nothing is a subtype of AnyRef with AnyVal by definition thus that declaration typechecks.
This is called a compound type and has nothing to do with traits. It allows you to express that a type is a subtype of several other types.
For more information where they can occur see the Scala tag info in section "type handling".
Given the following code:
class A {
class B
type C <: B
trait D
}
class E extends A {
type C = B
}
class F extends E {
override type C = B with D
}
Why does the Scala IDE's presentation compiler within the Eclipse Indigo IDE complain with the error message overriding type C in class E, which equals F.this.B; type C has incompatible type?
After all class "B" is only "amended" with trait "D" and thus the two type definitions are of the same base type, which is "B". Hence compatible type definitions.
The code below works. I consider the rules for type assignment similiar to variable assignment, such as:
class Foo
trait Bar
val a: Foo = new Foo
val fooWithBar: Foo = new Foo with Bar
Is my understanding wrong?
They are not compatible, type C might be used in a contravariant position
class E extends A {
type C = B
def f(c: C)
}
class F extends E {
override type C = B with D
def f(c: ???)
}
Complement
given e: E, you are allowed to call e.f(new B). What if e was val e = new F ?