Assume, we have:
class B
class A extends B
trait T
Then it holds:
val a: A with T = new A with T
a.isInstanceOf[B] // result is true !
Is it right to say, the isInstanceOf method checks, if there is at least one type (not all types) which matches the right hand side in a subtype relationship?
At first look, I thought a value with type A with T can not be a subtype of B, because A and T are not both subtypes of B. But it is A or T is a subtype of B -- is that right ?
isInstanceOf looks if there is a corresponding entry in the inheritance chain. The chain of A with T includes A, B and T, so a.isInstanceOf[B] must be true.
edit:
Actually the generated byte code calls javas instanceof, so it would be a instanceof B in java. A little more complex call like a.isInstanceOf[A with T] would be (a instanceof A) && (a instanceof T).
At first look, I thought a value with type A with T can not be a
subtype of B
There's are two misconceptions here. First, that the static type of an instance has any bearing on the result of isInstanceOf: there is none. To be clear, when doing a.isInstanceOf[B], the fact that a is of type A with T is not relevant.
The method isInstanceOf is implemented at the bytecode level by the JVM. It looks at the class information every instance carries, and checks whether B one of the classes (the class of the instance itself and its ancestors), or one of the implemented interfaces. That's the "is-a" relationship: "a is a B".
Technically, isInstanceOf is part of Java's reflection, where it is known as instanceof.
The second misconception is the inheritance can somehow remove a parent type. That never happens: inheritance only adds types, never removes them. The type A with T is an A, a B, a T, an AnyVal and an Any. So even if isInstanceOf did look at the type A with T, it would still return true.
Related
How are primitive types in Scala objects if we do not use the word "new" to instantiate the instances of those primitives? Programming in Scala by Martin Odersky described the reasoning as some enforcing by a "trick" that makes these value classes to be defined abstract and final, which did not quite make sense to me because how are we able to make an instance of these classes if its abstract? If that same primitive literal is to be stored somewhere let's say into a variable will that make the variable an object?
I assume that you use scala 2.13 with implementation of literal types. For this explanation you can think of type and class as synonyms, but in reality they are different concepts.
To put it all together it worth to treat each primitive type as a set of subtypes each of which representing type of one single literal value.
So literal 1 is a value and type at the same time (instance 1 of type 1), and it is subtype of value class Int.
Let's prove that 1 is subtype of Int by using 'implicitly':
implicitly[1 <:< Int] // compiles
The same but using val:
val one:1 = 1
implicitly[one.type <:< Int] // compiles
So one is kind of an instance (object) of type 1 (and instance of type Int at the same time because because Int is supertype of 1). You can use this value the same way as any other objects (pass it to function or assign to other vals etc).
val one:1 = 1
val oneMore: 1 = one
val oneMoreGeneric: Int = one
val oneNew:1 = 1
We can assume that all these vals contain the same instance of one single object because from practical perspective it doesn't actually matter if this is the same object or not.
Technically it's not an object at all, because primitives came form java (JVM) world where primitives are not objects. They are different kind of entities.
Scala language is trying to unify these two concepts into one (everything is classes), so developers don't have to think too much about differences.
But here are still some differences in a backstage. Each value class is a subtype of AnyVal, but the rest of the classes are subtype of AnyRef (regular class).
implicitly[1 <:< AnyVal] //compiles
implicitly[Int <:< AnyVal] // compiles
trait AnyTraint
implicitly[AnyTraint <:< AnyVal] // fails to compail
implicitly[AnyTraint <:< AnyRef] // compiles
And in addition, because of its non-class nature in the JVM, you can't extend value classes as regular class or use new to create an instance (because scala compiler emulates new by itself). That's why from perspective of extending value classes you should think about them as final and from perspective of creating instances manually you should think of them as abstract. But form most of the other perspectives it's like any other regular class.
So scala compiler can kind of extend Int by 1,2,3 .. types and create instances of them for vals, but developers can't do it manually.
I have read this article on covariance/contravariance: http://julien.richard-foy.fr/blog/2013/02/21/be-friend-with-covariance-and-contravariance/
The examples are very clear. However, I am struggling to understand the conclusions drawn at the end:
If you look at the definitions of Run[+A] and Vet[-A] you may notice
that the type Aappears only in the return type of methods of Run[+A]
and only in the parameters of methods of Vet[-A]. More generally a
type that produces values of type A can be made covariant on A (as you
did with Run[+A]), and a type that consumes values of type A can be
made contravariant on A (as you did with Vet[-A]).
From the above paragraph you can deduce that types that only have
getters can be covariant (in other words, immutable data types can be
covariant, and that’s the case for most of the data types of Scala’s
standard library), but mutable data types are necessarily invariant
(they have getters and setters, so they both produce and consume
values).
Producers: If something produces type A, I can imagine some reference variable of type A being set to an object of type A or any subtypes of A, but not supertypes, so it's appropriate that it can be covariant.
Consumers: If something consumes type A, I guess that means type A may be used as parameters in methods. I'm not clear what relationship this has to covariance or contravariance.
It seems from the examples that specifying a type as covariant/contravariant affects how it can be consumed by other functions but not sure how it affects the classes themselves.
It seems from the examples that specifying a type as covariant/contravariant affects how it can be consumed by other functions but not sure how it affects the classes themselves.
It is right that the article focused on the consequences of variance for users of a class, not for implementers of a class.
The article shows that covariant and contravariant types give more freedom to users (because a function that accepts a Run[Mammal] effectively accepts a Run[Giraffe] or a Run[Zebra]). For implementors, the perspective is dual: covariant and contravariant types give them more constraints.
These constraints are that covariant types can not occur in contravariant positions and vice versa.
Consider for instance this Producer type definition:
trait Producer[+A] {
def produce(): A
}
The type parameter A is covariant. Therefore we can only use it in covariant positions (such as a method return type), but we can not use it in contravariant position (such as a method parameter):
trait Producer[+A] {
def produce(): A
def consume(a: A): Unit // (does not compile because A is in contravariant position)
}
Why is it illegal to do so? What could go wrong if this code compiled? Well, consider the following scenario. First, get some Producer[Zebra]:
val zebraProducer: Producer[Zebra] = …
Then upcast it to a Producer[Mammal] (which is legal, because we claimed the type parameter to be covariant):
val mammalProducer: Producer[Mammal] = zebraProducer
Finally, feed it with a Giraffe (which is legal too because the consume method a Producer[Mammal] accepts a Mammal, and a Giraffe is a Mammal):
mammalProducer.consume(new Giraffe)
However, if you remember well, the mammalProducer was actually a zebraProducer, so its consume implementation actually only accepts a Zebra, not a Giraffe! So, in practice, if it was allowed to use covariant types in contravariant positions (like I did with the consume method), the type system would be unsound. We can construct a similar scenario (leading to an absurdity) if we pretend that a class with a contravariant type parameter can also have a method where it is in covariant position (see at the end for the code).
(Note that several programming languages, e.g. Java or TypeScript, have such unsound type systems.)
In practice, in Scala if we want to use a covariant type parameter in contravariant position, we have to use the following trick:
trait Producer[+A] {
def produce(): A
def consume[B >: A](b: B): Unit
}
In that case, a Producer[Zebra] would not expect to get an actual Zebra passed in the consume method (but any value of a type B, lower-bounded by Zebra), so it would be legal to pass a Giraffe, which is a Mammal, which is a super-type of Zebra.
Appendix: similar scenario for contravariance
Consider the following class Consumer[-A], which has a contravariant type parameter A:
trait Consumer[-A] {
def consume(a: A): Unit
}
Suppose that the type system allowed us to define a method where A is in covariant position:
trait Consumer[-A] {
def consume(a: A): Unit
def produce(): A // (does not actually compile because A is in covariant position)
}
Now we can get an instance of Consumer[Mammal], upcast it to Consumer[Zebra] (because of contravariance) and call the produce method to get a Zebra:
val mammalConsumer: Consumer[Mammal] = …
val zebraConsumer: Consumer[Zebra] = mammalConsumer // legal, because we claimed `A` to be contravariant
val zebra: Zebra = zebraConsumer.produce()
However, our zebraConsumer is actually mammalConsumer, whose method produce can return any Mammal, not just Zebras. So, at the end, zebra might be initialized to some Mammal that is not a Zebra! In order to avoid such absurdities, the type system forbids us to define the produce method in the Consumer class.
These days, I am reading the book "programming in scala". There is one sentence in the book on Page 246, Chapter11, first paragraph:
For example, just as Any is a superclass of every other class, Nothing
is a subclass of every other class.
I understand the first part because every class inherits Any in a direct or indirect way. But I can't understand the latter part of the sentence.
This is class Nothing definition:
abstract final class Nothing extends Any
Conceptually, Nothing is something that is harder to grasp than Any, which we're familiar with from Java and most other object oriented programming. Nothing is Scalas bottom type. The definition from Wikipedia states:
In subtyping systems, the bottom type is the subtype of all types.
(However, the converse is not true—a subtype of all types is not
necessarily the bottom type.) It is used to represent the return type
of a function that does not return a value: for instance, one which
loops forever, signals an exception, or exits.
I think the easiest way to reason about it is to think of it as a way of describing something that never returns. You can't construct an instance of a Nothing type yourself. One particular use of Nothing in Scala is to be able to reason about covariant parametric types:
In Scala, the bottom type is denoted as Nothing. Besides its use for
functions that just throw exceptions or otherwise don't return
normally, it's also used for covariant parameterized types. For
example, Scala's List is a covariant type constructor, so
List[Nothing] is a subtype of List[A] for all types A. So Scala's Nil,
the object for marking the end of a list of any type, belongs to the
type List[Nothing].
Being a subtype of is not the same as being a subclass of. From Subtyping:
Subtyping should not be confused with the notion of (class or object)
inheritance from object-oriented languages; subtyping is a relation
between types (interfaces in object-oriented parlance) whereas
inheritance is a relation between implementations stemming from a
language feature that allows new objects to be created from existing
ones. In a number of object-oriented languages, subtyping is called
interface inheritance.
This is also denoted in the Scala Specification Section §3.5.2 (Conformance) as part of the <: (is subtype of relation), both for value types and type constructors:
For every value type T, scala.Nothing <: T <: scala.Any.
For every type constructor T (with any number of type parameters), scala.Nothing <: T <: scala.Any.
One familiar aspect of subtyping is variance of generic types, where - denotes contravariance and + denotes covariance.
I'm trying to use a covariant type parameter inside a trait to construct a case-class like so:
trait MyTrait[+T] {
private case class MyClass(c: T)
}
compiler says:
error: covariant type T occurs in contravariant position in type T of value c
I then tried the following but it also didn't work:
trait MyTrait[+T] {
private case class MyClass[U <: T](c: U)
}
the error this time is:
error: covariant type T occurs in contravariant position in type >: Nothing <: T of type U
Could somebody explain why the T is in a covariant position here and suggest a solution for this problem?
Thx!
This is a fundamental feature of object-oriented programming that doesn't get as much attention as it deserves.
Suppose you have a collection C[+T]. What the +T means is that if U <: T, then C[U] <: C[T]. Fair enough. But what does it mean to be a subclass? It means that every method should work that worked on the original class. So, suppose you have a method m(t: T). This says you can take any t and do something with it. But C[U] can only do things with U, which might not be all of T! So you have immediately contradicted your claim that C[U] is a subclass of C[T]. It's not. There are things you can do with a C[T] that you can't do with a C[U].
Now, how do you get around this?
One option is to make the class invariant (drop the +). Another option is that if you take a method parameter, to allow any superclass as well: m[S >: T](s: S). Now if T changes to U, it's no big deal: a superclass of T is also a superclass of U, and the method will work. (However, you then have to change your method to be able to handle such things.)
With a case class, it's even harder to get it right unless you make it invariant. I recommend doing that, and pushing the generics and variance elsewhere. But I'd need to see more details to be sure that this would work for your use case.
Almost there. Here:
scala> trait MyTrait[+T] {
| private case class MyClass[U >: T](c: U)
| }
defined trait MyTrait
Which means MyClass[Any] is valid for all T. That is at the root of why one cannot use T in that position, but demonstrating it requires more code than I'm in the mood for at the moment. :-)
One can say a type parameter T must have a specific supertype S_1:
class Test[T <: S_1]
Is there a way to say, that a type parameter must have at least one supertype of multiple supertype alternatives ?
Something like (pseudocode) :
class Test[T <: S_1 || S_2]
Or: Is this not possible, because such a construction makes no sense and would be a hint of a design mistake in the code ?
Short answer: The intuitive solution is to make S_1 and S_2 share a common trait that represents the set of abilities you require for your type parameter T. Use that trait as the upper bound for T.
More possibilities:
If S_1 and S_2 are unrelated in nature and your requirement for the type T is that it has certain members (that both S_1 and S_2 happen to implement), you can use a structural type to formulate that (the concept behind is called duck typing).
If for some reason you really require T to be a subclass of S_1 or S_2, and you can't change those types, you can use implicits to convert both of these to a newly introduced internal type S_1_or_2, which you can then use as an upper bound for your T.
Let me expand on Niklas second alternative. Implicit parameters can be used to prove something about the type, so this seems like just the thing. It would go like this:
class ThingIWantToProve[T]
object ThingIWantToProve {
// Here I define the proofs I need
implicit def s1IsProvable: ThingIWantToProve[S_1] = new ThingIWantToProve[S_1]
implicit def s2IsProvable: ThingIWantToProve[S_2] = new ThingIWantToProve[S_2]
}
class Test[T : ThingIWantToProve] // here I use a context bound