这是另一个答案,因为虽然我理解函数子类型规则的意义,但我想弄清楚为什么任何其他参数/结果子类型组合都没有。
子类型规则是:
意味着如果满足顶部子类型条件,则底部成立。
在函数类型定义中,函数参数是逆变的,因为我们颠倒了T1 和S1 之间的子类型关系。函数结果是协变的,因为它们保留了T2 和S2 之间的子类型关系。
既然定义不存在,为什么会有这样的规则? Aaron Fi 的回答中说得很清楚,我还找到了定义here(搜索标题“Function Types”):
另一种观点是允许一种类型的函数是安全的
S1 → S2 用于需要另一种类型 T1 → T2 的上下文中
只要在此上下文中可能传递给函数的任何参数都不会让它感到惊讶(T1 <: S1),并且它返回的任何结果都不会让上下文感到惊讶(S2 <: T2)。
再一次,这对我来说是有道理的,但我想看看为什么没有其他的打字规则组合是有意义的。为此,我查看了一个简单的高阶函数和一些示例记录类型。
对于以下所有示例,让:
S1 := {x, y}
T1 := {x, y, z}
T2 := {a}
S2 := {a, b}
具有逆变参数类型和协变返回类型的示例
让:
-
f1 有类型 S1 → S2 ⟹ {x, y} → {a, b}
-
f2 有类型 T1 → T2 ⟹ {x, y, z} → {a}
现在假设type(f1) <: type(f2)。我们从上面的规则中知道这一点,但让我们假装我们不知道,看看为什么它是有意义的。
我们运行map( f2 : {x, y, z} → {a}, L : [ {x, y, z} ] ) : [ {a} ]
如果我们将f2 替换为f1,我们会得到:
map( f1 : {x, y} → {a, b}, L : [ {x, y, z} ] ) : [ {a, b} ]
这很好,因为:
- 无论
f1 函数对其参数做什么,它都可以忽略额外的z 记录字段并且没有问题。
- 无论运行
map 的上下文如何处理结果,它都可以忽略
额外的b 记录字段并没有问题。
结论:
{x, y} → {a, b} ⟹ {x, y, z} → {a} ✔
协变参数类型和协变返回类型的示例
让:
-
f1 有类型 T1 → S2 ⟹ {x, y, z} → {a, b}
-
f2 有类型 S1 → T2 ⟹ {x, y} → {a}
假设type(f1) <: type(f2)
我们运行map( f2 : {x, y} → {a}, L : [ {x, y} ] ) : [ {a} ]
如果我们将f2 替换为f1,我们会得到:
map( f1 : {x, y, z} → {a, b}, L : [ {x, y} ] ) : [ {a, b} ]
我们可能会在这里遇到问题,因为f1 期望并且可能对z 记录字段进行操作,而这样的字段在列表L 的任何记录中都不存在。 ⚡
逆变参数类型和逆变返回类型的示例
让:
-
f1 有类型 S1 → T2 ⟹ {x, y} → {a}
-
f2 有类型 T1 → S2 ⟹ {x, y, z} → {a, b}
假设type(f1) <: type(f2)
我们运行map( f2 : {x, y, z} → {a, b}, L : [ {x, y, z} ] ) : [ {a, b} ]
如果我们将f2 替换为f1,我们会得到:
map( f1 : {x, y} → {a}, L : [ {x, y, z} ] ) : [ {a} ]
当传递给f1 时,我们可以忽略z 记录字段,但是如果调用map 的上下文需要带有b 字段的记录列表,我们将遇到错误。 ⚡
协变参数类型和逆变返回的示例
查看上面的示例,了解可能出错的两个地方。
结论
这是一个非常冗长而冗长的答案,但我不得不记下这些内容以弄清楚为什么其他参数和返回参数子类型无效。既然我已经把它写下来了,我想为什么不把它贴在这里。