Reason behind designing parametric types as invariant

Thank you for illuminating and detailed response. How about the proposal:

  1. add P{=:T} syntax to mean the current meaning of P{T}
  2. make P{T} a sugar to P{<:T}

This way we have nice consistent syntax P{<:T}, P{=:T}, P{>:T} to talk about covariance, invariance and contravariance of T respectively.
With this we can make P{T} to be a sugar for one of those 3 options. Which one? I argue that it must be P{<:T}. The immediate objection here is that it is not much harder to write P{<:T} with the current syntax and that is a fair point. However, imagine a typical MATLAB/Python/R user coming to Julia. It is fair to assume that he is not familiar with the notion of subtype variance. Once he learns about parametric types and type hierarchies he naturally conceives of the notion of subtype covariance (since it is an amalgamation of those two ideas) and tries to write something like f(v::Vector{Real}) while what he intended was f(v::Vector{<:Real}). And here we wreck the train for him by explicitly teaching that Vector{Real} contrary to his expectations is invariant (indeed there is even warning about it in the documentation). In doing so we introduce notion of subtype variance making the first Julia experience more complicated than is necessary.

This is exactly what happened to me on my first encounter with Julia. As a result of this confusion I asked the this question and you can see @dpsanders trying to explain type invariance to me there.

In summary, giving P{T} the meaning of P{<:T} has the following advantages:

  • follows principle of least surprise
  • makes the Julia’s learning curve less steep
  • when one writes P{T} with abstract T, he almost never means P{=:T} and means P{<:T} instead
  • compiler can generate efficient machine code for f(v::Vector{<:Real}), while the currently f(v::Vector{Real}) results in inefficient code.
2 Likes