如何在不收到此错误消息的情况下将哈希值转换为别名类型?

时间:2018-03-17 16:03:33

标签: casting crystal-lang

当我使用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)

1 个答案:

答案 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