-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Covariance and contravariance in generic types #1297
Comments
So, its a good thing to allow more programs to succeed the type checker if they would be able to run without type restrictions. That's the half of the whole point. (The other half is not make the type system catch all program that would fail in runtime) With this in mind, allowing But, as we discussed IRL. This will affect the method lookup / is_a? results. Crystal, due to how class/methods/macros are expanded does not follow some rules as other languages. Compare: So, allowing covariance/contravariance, at least in the first stage, might be "only" about method type restriction. I'm a bit uncertain about the following steps. |
Wouldn't something like protocols be an alternative solution?
|
I worked mostly with Java and Scala so I am used to using types and relying on them. I can understand that when you come from dynamic languages the approach can be different. (That's why type inference is a great thing) For me, however, having types and inheritance implies that you can rely on them. Java handled variance in generics by making them invariant. Say you have a function that takes as an argument function from type A to type B
More interesting is the question: what I can pass to
Currently in Crystal it's not possible and due to covariance on input you can actually pass So my point is that Crystal already has covariance and this does not work in every case as a person with typed language background would expect it to. So I would suggest approach similar to Scala if it's possible. |
+1 for |
I was thinkg of using So
corresponds to covariance
And
corresponds to contravariance
I think |
Sure, that works too. |
I support @asterite! There is little point in Crystal becoming a "Scala with Ruby syntax" ;-). Somehow, most programmers think about only reading, when they think of covariance. And then get tripped by it in endless ways. In my limited experience, variance is one of the darkest corners of type systems. If you haven't already seen them, I highly recommend that you watch:
I request you all to consider a combination of:
Such a combination can address a vast majority of scenarios, without introducing covariance and contravariance. Remember that a departure from invariance is a one way ticket. It taints the language and a lot of the standard library irrevocably! Edit: Correct Phillips' spelling. |
@js-ojus As I already pointed out Crystal is not invariant ATM. |
@whorbowicz Yes, I realise that Crystal currently has some covariance. The question that I was trying to surface was: should Crystal embrace invariance or continue down the path to full covariance/contravariance? We have to remember that covariance is unsound, unless everything is contravariant on the input side and covariant on the output side. Else, we leak, and become unsound. This is one of the biggest reasons why delegates in C# are contravariant in their input types. And, we only need a glance at StackOverflow to see thousands of C# programmers who got tripped by the above. Also, there is nothing wrong with invariance and existential types. It has been proved formally to be sound (e.g. Igarashi and Viroli). Torgersen, Ernst and Hansen's paper "Wild FJ" is a good read, as well. On the second point, I did not mean to imply that everything that Paul Phillips said was correct :-). Nonetheless, I am curious about what you say. Could you point to the specific example of Edit: Formatting. |
@js-ojus I'll give you the details, but let's move this subject to some private channel (eg. G+) - we are drifting away from the topic. :) |
For notation the |
Closed in favor of #2665 |
(spawned from #1294)
Motivating example:
The questions are:
There are true, not a lot to discuss here.
This is a bit more difficult to answer. The thing is, you read from an enumerable, you don't put things inside it. So this question should be true, because
Child.new.is_a?(Base)
.Now,
Child.new.is_a?(Base)
, but if the above istrue
, say we replaceArray(Child).new
withexp
, we get:Good, it's an
Array(Base)
so lets put some elements into it:Oops, but
exp
wasArray(Child)
, we can't put aBase
inside it. So maybe the answer tois_a?
should be false here.So, we have two cases of Child vs. Base in generic types, and in one case we'd like the answer to be true and in another one we'd like it to be false.
The "solution" is to mark the T in Enumerable as covariant. In C# they do it like
IEnumerable<out T>
. There's alsoin
. I think in Scala they use+
and-
. And, as far as I know, these are difficult to grasp. So, we'll go from an untyped language (Ruby) to a language with some type annotations (generic types, captured blocks), to a language with covariance/contravariance type annotations. I'm not sure I like that idea, but we'll see.The way it works right now is that
Array(Child).is_a?(Array(Base))
will give false, but the callarray_base(child_ary)
succeeds. And, as long as you only read from the array, everything will compile fine. So, in a way, this is safe, but maybe not very intuitive.We should decide what to do with this.
The text was updated successfully, but these errors were encountered: