Created
November 9, 2014 20:45
-
-
Save asterite/2247449862b60396a00b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class Array(T) | |
| def map(&block : T -> U) | |
| ary = [] of U | |
| each { |e| ary << yield e } | |
| ary | |
| end | |
| end | |
| class Foo | |
| end | |
| class Bar < Foo | |
| end | |
| class Baz < Foo | |
| end | |
| a = [Bar.new, Baz.new] # :: Array(Foo+) | |
| a << Foo.new # ok | |
| a << Baz.new # ok | |
| b = a.map { Bar.new } # :: Array(Bar) | |
| b << Foo.new # error: no overload matches 'Array(Bar)#<<' with types Foo | |
| # El problema es que el U de map se infiere a Bar, el tipo más específico. | |
| # Si queremos que b quede de tipo Array(Foo) deberíamos poder especificar ese U. | |
| # Pero... ese U se infiere del tipo del bloque! | |
| # | |
| # En otros lenguajes uno podría pensar que map es map<U>(&block : T -> U) | |
| # y entonces poder hacer: | |
| # | |
| # a.map<Foo> { Bar.new } | |
| # | |
| # Eso no está mal pero es agregarle más cosas al lenguaje. | |
| # | |
| # Una cosa que podemos hacer es castear el tipo del bloque a Foo, así | |
| # el tipo inferido es Foo: | |
| c = a.map { Bar.new as Foo } | |
| # El problema es que antes de Crystal 0.5.3 el `as` funcionaba siempre de la siguiente manera: | |
| # se filtra el tipo del valor (Bar) al tipo destino (Foo). Si filtramos Bar con Foo nos | |
| # queda... Bar! Sería como "restringime Bar a los tipos que heredan de Foo". Para Bar | |
| # el resultado es Bar. Entonces c queda de tipo Array(Bar), no cambió nada. | |
| # | |
| # En Crystal 0.5.3 si al filtrar un tipo A por B el resultado es A entonces el tipo | |
| # resultante en realidad cambia y es B+: | |
| d = a.map { Bar.new as Foo } # :: Array(Foo+) | |
| d << Foo.new # ok | |
| # Un ejemplo similar que no involucra herencia: | |
| a = [1, 2, 3] | |
| b = a.map { |x| x + 1 as Int32 | Float64 } # :: Array(Int32 | Float64) en Crystal 0.5.3, Array(Int32) en Crystal 0.5.2 | |
| b << 1.5 # ok en Crystal 0.5.3, error en Crystal 0.5.2 | |
| # De esa manera uno convive con la inferencia de tipos (si map infiere el tipo del bloque, ajusto el tipo del | |
| # bloque a lo que yo quiero) en lugar de agregarle más features al lenguaje ;-) |
Author
@bcardiff sí, tu definición es la más correcta en realidad: "es para ayudar un poco al type inference" :-)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
El "problema" es que no tenés varianza al usar un bloque.
Si esperás un tipo
S2 -> T2y te pasanS1 -> T1cuándo se válido usar el bloque sin problema?O dicho de otra forma, cuándo
S1 -> T1 <: S2 -> T2.Eso debería cumplirse cuando
S2 <: S1yT1 <: T2.Si tuvieses eso en el lenguaje podrias hacer algo como (cambiando la interaz, con lo cual quizás no te guste)... Pero de esa forma un bloque que
-> Barbien podría usarse como-> Foopor dónde lo tenés que usar.Pero entiendo que el
aslo usas para ayudar un poco al type inference.Está bien como quedó.