Skip to content

Instantly share code, notes, and snippets.

@abreslav
Created March 20, 2014 17:28
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save abreslav/9669252 to your computer and use it in GitHub Desktop.
Save abreslav/9669252 to your computer and use it in GitHub Desktop.
// Why do we need variance
// Invariant class List, just like in Java
class List<T> { /* normal list */ }
fun printContents(list: List<Any>) {
for (item in list) {
println(item)
}
}
fun test() {
val list: List<String> = listOf("a", "b")
printContents(list) // ERROR: List<String> is not a subtype of List<Any>
}
@Eliah-Lakhin
Copy link

I see the issue here in attempt of printing custom object:

println(item)

In fact having superobject have no sense since it violates the idea of static typing.

@Eliah-Lakhin
Copy link

The code could be fixed in various ways. As an option we can cast each object to String explicitly, and then print it using printContents(list: List<String>).

@abreslav
Copy link
Author

Well, if you mean that we can abandon the JVM entirely and write in Haskell, then yes, we can, but nobody will follow us. Otherwise we have to live with

  • Nominal subtyping/Inheritance
  • Single root class

And these entail the need for variance

@abreslav
Copy link
Author

If this example doesn't make the issue clear, consider this:

fun feed(animals: List<Animal>) {
  for (animal in animals) animal.feed()
}

fun test() {
  val list = listOf(Dog("one"), Dog("two"))
  feed(list) // Same error
}

Casting each object on each call site is not an option: too much code. Casting is against static typing in general.

A more clever example:

fun render(s: Something, f: (Something) -> CharSequence) {
   ...
   f(s)
   ...
}

fun test() {
   fun emptyString(s: Something): String = ""

   render(s, ::emptyString) // same problem
} 

@Eliah-Lakhin
Copy link

Of course I didn't mean that, since Haskell is of strongly "immutable state" class of languages, whereas your language is not. And I understand that immutability is not the silver bullet.

Nevertheless, I think that pulling all of the base principals of JVM is not the right approach too. It is actually up to your goals, but Scala clearly shows us that people are not happy living with mixed Java and Scala codebase. Sooner or later they are trying to move completely to one of the languages and it's environment(usually Scala atm) and use libraries and tools written entirely in this language. Therefore my point of view is that persisting of interoperability between Java and your language should not be raised to the level of highest design principal.

Regarding your argument. I agree with your point that casting agnostics idea of static typing. That's why I think that the problem described in both of your examples is fundamentally broken. In the real programming systems class inheritance could be(and I think that it should be) completely avoided. And people around usually try to avoid it whenever it possible replacing inheritance with object composition, and another techniques that are easier to understand and maintain.

So returning to your second example. If I was a team leader the question I would ask to developer is why did you put "feed" method to the superclass? If it is unique to the Dog class you should have put it to the Dog class. Otherwise, it should be somekind of a static class, or some form of mixin.

@abreslav
Copy link
Author

Well, I'd like to see a collections library in a language without inheritance :)

Also, about the animals example: polymorphism is about having different implementations of feed() in Dog and Cat, and that can not be put to a static class. The fact that this particular example does not mention cats might have mislead you, but there are cats too.

@Eliah-Lakhin
Copy link

I would like to hear your opinion what issues do you see in implementation of collections library without inheritance?

@Eliah-Lakhin
Copy link

Returning to the example with Dogs and Cats. Virtual methods could be easily replaced with anonymous functions. Both have the same goal: customization of behaviour. But having different ways to reach the same thing makes syntax inconsistent, and difficult to understand for people as well as for machines. I hope you will not negate that syntax simplicity is an advantage.

@Eliah-Lakhin
Copy link

polymorphism is about having different implementations

I think it is important to mention that term "polymorphism" may have slightly different meaning depending on context. I criticize subtyping polymorphism, not the parametric polymorphism of course.

@llogiq
Copy link

llogiq commented Mar 21, 2014

We can have variance in Java, too. In your case, we don't need to know anything about the type of the list's elements, just that they can be printed (which in Java, applies to any Object):

void printContents(list: List<?>) {
    for (Object item in list) {
        System.out.println(item)
    }
}

In general, for any function that takes a list of elements that belong to some class or interface X, we'd define the argument as List<? extends X>. In the animal example, we'd have a feedAll(Collection<? extends Animal>) function. This will assure that we can feed it (assuming the implementation obeys the Liskov Substitution Principle), and the compiler will balk on trying to feed a list of anchovies.

The reverse case (contravariance) often comes up with callbacks, e.g. in the standard library, there are a few methods that take a Comparator (which is in a sense a callback). So not to require a Comparator of a specific type, they will usually ask for a Comparator<? super X>.

I'd imagine the reasoning within Kotlin is about the same as in Java?

@abreslav
Copy link
Author

I think it is important to mention that term "polymorphism" may have slightly different meaning depending on context. I criticize subtyping polymorphism, not the parametric polymorphism of course.

What you say is far too theoretical, I'm afraid.

Only parametric polymorphism is Haskell minus irrelevant piculiarities (laziness, immutable data etc). And that's too hard for people to grasp. And to be practical this kind of language would need type classes, that are even harder, and then structural subtyping for record that is impossible to implement efficiently over JVM.

I'm closing the case here. Too many keystrokes. If you want to discuss further, let's talk over skype (andrey.breslav).

@Eliah-Lakhin
Copy link

@llogiq I agree with your point. Actually even the current implementation of Java is covering 95% of all practical use cases.

@Eliah-Lakhin
Copy link

Ok. Andrey, thank you very much for participation and for your time. It was very interesting and informative discussion. I hope we will have a chance to continue it later.

I'm following your project with interest. Albeit we have different points of view in some controversial problems, I must admit JetBrains making the best products in its market. And there is no doubt, Kotlin is one of them.

P.S. I'm going to add you on my Skype contact list, so we will be in touch.

@abreslav
Copy link
Author

Just for the record. On the matter of variance in Java (which is called use-site variance): https://prezi.com/lnw_oiv1gs-j/java-8-vs-kotlin/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment