当我使用alias
定义哈希时,我收到了一个有趣的错误:
alias MyHash = Hash(Symbol, String | Int32)
hash = {:one => 2}.as(MyHash)
如果我运行此输出不符合我的预期:
Error in ffile2.cr:2: can't cast Hash(Symbol, Int32) to Hash(Symbol, Int32 | String)
hash = {:one => 2}.as(MyHash)
^
你不能吗?为什么不?是不是定义统一类型的是什么?
请注意,如果我将类型放在方法签名中,一切都很好:
def foo(h : Hash(Symbol, String | Int32) )
puts h[:bar]?.nil?
end
foo( {:one => 2} )
更新:这有效,但似乎有点愚蠢:
alias MyHash = Hash(Symbol, String | Int32)
hash = {:one => 2.as(String | Int32)}.as(MyHash)
答案 0 :(得分:4)
这与别名无关。如果使用别名类型替换原始示例中的别名,它也将失败。
.as
无法将Hash(Symbol, Int32)
神奇地转换为Hash(Symbol, String | Int32)
。这些是不同的类型,行为不同。这可能一开始并不明显,因为在检索条目时,两种类型都返回类型为String | Int32
的值。存储条目时,他们的行为不一样:Hash(Symbol, String | Int32)
可以获得String | Int32
类型的值,Hash(Symbol, Int32)
则不能。
这方面的语言设计术语是协方差和逆变。
为避免必须在文字中指定预期值类型,您还可以使用通用转换,例如:
literal = {:one => 2}
mapped = literal.map do |key, value|
{key, value.as(String | Int32)}
end.to_h
typeof(mapped) # => Hash(Symbol, Int32 | String)
这将采用匹配类型的任何哈希并将其转换为Hash(Symbol, Int32 | String)
。
方法参数中的类型不是"罚款"他们只是表现不同。只要您不对它们做任何错误,它们就会匹配未指定的类型。尝试将值设置为字符串,它仍然会失败:
def foo(h : Hash(Symbol, String | Int32) )
puts h[:bar] = "bar" # error: no overload matches 'Hash(Symbol, Int32)#[]=' with types Symbol, String
end
这些不同的语义显然不是很直观,但它是正在进行的工作的当前状态。有一个问题也会更详细地解释这是什么:https://github.com/crystal-lang/crystal/issues/3803