{"action":"created","issue":{"url":"https://api.github.com/repos/manastech/crystal/issues/1297","labels_url":"https://api.github.com/repos/manastech/crystal/issues/1297/labels{/name}","comments_url":"https://api.github.com/repos/manastech/crystal/issues/1297/comments","events_url":"https://api.github.com/repos/manastech/crystal/issues/1297/events","html_url":"https://github.com/manastech/crystal/issues/1297","id":103533536,"number":1297,"title":"Covariance and contravariance in generic types","user":{"login":"asterite","id":209371,"avatar_url":"https://avatars.githubusercontent.com/u/209371?v=3","gravatar_id":"","url":"https://api.github.com/users/asterite","html_url":"https://github.com/asterite","followers_url":"https://api.github.com/users/asterite/followers","following_url":"https://api.github.com/users/asterite/following{/other_user}","gists_url":"https://api.github.com/users/asterite/gists{/gist_id}","starred_url":"https://api.github.com/users/asterite/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/asterite/subscriptions","organizations_url":"https://api.github.com/users/asterite/orgs","repos_url":"https://api.github.com/users/asterite/repos","events_url":"https://api.github.com/users/asterite/events{/privacy}","received_events_url":"https://api.github.com/users/asterite/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":1,"created_at":"2015-08-27T15:18:20Z","updated_at":"2015-08-27T15:55:46Z","closed_at":null,"body":"(spawned from #1294)\r\n\r\nMotivating example:\r\n\r\n```crystal\r\nclass Base\r\nend\r\n\r\nclass Child < Base\r\nend\r\n\r\ndef enumerable_base(x : Enumerable(Base))\r\nend\r\n\r\ndef array_base(x : Array(Base))\r\nend\r\n\r\nbase_ary = Array(Base).new\r\nchild_ary = Array(Child).new\r\n\r\n# Which of the following methods should give an error?\r\nenumerable_base(base_ary)\r\narray_base(base_ary)\r\nenumerable_base(child_ary)\r\narray_base(child_ary)\r\n```\r\n\r\nThe questions are:\r\n\r\n```crystal\r\n# 1.\r\nArray(Base).new.is_a?(Enumerable(Base))\r\nArray(Base).new.is_a?(Array(Base))         \r\n```\r\n\r\nThere are true, not a lot to discuss here.\r\n\r\n```crystal\r\n# 2.\r\nArray(Child).new.is_a?(Enumerable(Base))\r\n```\r\n\r\nThis 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)`.\r\n\r\n```crystal\r\n# 3.\r\nArray(Child).new.is_a?(Array(Base))\r\n```\r\n\r\nNow, `Child.new.is_a?(Base)`, but if the above is `true`, say we replace `Array(Child).new` with `exp`, we get: \r\n\r\n```crystal\r\nexp.is_a?(Array(Base)) #=> true\r\n```\r\n\r\nGood, it's an `Array(Base)` so lets put some elements into it:\r\n\r\n```crystal\r\nexp << Base.new # ERROR\r\n```\r\n\r\nOops, but `exp` was `Array(Child)`, we can't put a `Base` inside it. So maybe the answer to `is_a?` should be false here.\r\n\r\nSo, 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.\r\n \r\nThe \"solution\" is to mark the T in Enumerable as covariant. In C# they do it like `IEnumerable<out T>`. There's also `in`. 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.\r\n\r\nThe way it works right now is that `Array(Child).is_a?(Array(Base))` will give false, but the call `array_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.\r\n\r\nWe should decide what to do with this."},"comment":{"url":"https://api.github.com/repos/manastech/crystal/issues/comments/135476750","html_url":"https://github.com/manastech/crystal/issues/1297#issuecomment-135476750","issue_url":"https://api.github.com/repos/manastech/crystal/issues/1297","id":135476750,"user":{"login":"bcardiff","id":459923,"avatar_url":"https://avatars.githubusercontent.com/u/459923?v=3","gravatar_id":"","url":"https://api.github.com/users/bcardiff","html_url":"https://github.com/bcardiff","followers_url":"https://api.github.com/users/bcardiff/followers","following_url":"https://api.github.com/users/bcardiff/following{/other_user}","gists_url":"https://api.github.com/users/bcardiff/gists{/gist_id}","starred_url":"https://api.github.com/users/bcardiff/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bcardiff/subscriptions","organizations_url":"https://api.github.com/users/bcardiff/orgs","repos_url":"https://api.github.com/users/bcardiff/repos","events_url":"https://api.github.com/users/bcardiff/events{/privacy}","received_events_url":"https://api.github.com/users/bcardiff/received_events","type":"User","site_admin":false},"created_at":"2015-08-27T15:55:46Z","updated_at":"2015-08-27T15:55:46Z","body":"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)\r\n\r\nWith this in mind, allowing `Enumerable(T) <: Enumerable(S) when T <: S` is a good thing. Specifying per generic param if its <, = or > regarding subtyping rules is an easy way to express them.\r\n\r\nBut, as we discussed IRL. This will affect the method lookup / is_a? results.\r\n\r\nCrystal, due to how class/methods/macros are expanded does not follow some rules as other languages.\r\nThat is, type restriction plays in choosing the method but does not restrict which methods can be called. Sounds tricky.  \r\n\r\nCompare:\r\nhttp://play.crystal-lang.org/#/r/cyq\r\nhttp://play.crystal-lang.org/#/r/cyr\r\n\r\nSo, 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.\r\n\r\n"}}