Types 混淆函数子类型

Types 混淆函数子类型,types,programming-languages,type-theory,Types,Programming Languages,Type Theory,我正在上一门编程语言的课程,关于“什么时候一个函数是另一个函数的子类型”的答案对我来说是非常违反直觉的 澄清:假设我们有以下类型关系: bool<int<real boolbool)?难道不是相反吗 我希望子类型函数的标准是:如果f2可以接受f1可以接受的任何参数,并且f1只返回f2返回的值,则f1是f2的子类型。很明显,f1可以接受一些值,但f2不能 下面是函数子类型的规则: 参数类型必须是反变量,返回类型必须是协变量。 Co variant==为results参数的类型保留“A

我正在上一门编程语言的课程,关于“什么时候一个函数是另一个函数的子类型”的答案对我来说是非常违反直觉的

澄清:假设我们有以下类型关系:

bool<int<real
boolbool
)?难道不是相反吗


我希望子类型函数的标准是:如果f2可以接受f1可以接受的任何参数,并且f1只返回f2返回的值,则f1是f2的子类型。很明显,f1可以接受一些值,但f2不能

下面是函数子类型的规则:

参数类型必须是反变量,返回类型必须是协变量。

Co variant==为results参数的类型保留“A是B的子类型”层次结构

Contra variant==反转参数的类型层次结构(“与之相反”)

因此,在您的示例中:

f1:  int  -> bool
f2:  bool -> bool
我们可以得出结论,f2是f1的一个亚型。为什么?因为(1)只查看这两个函数的参数类型,我们看到“bool是int的子类型”的类型层次结构实际上是共变量。它保留int和bool之间的类型层次结构。(2) 仅查看这两个函数的结果类型,我们可以看到相反的方差是成立的

换言之(我对这个问题的简单英语思考):

反变参数:“我的调用者可以传递比我需要的更多的信息,但这没关系,因为我只会使用我需要使用的信息。” co-variant返回值:“我可以返回比调用者需要的更多的内容,但没关系,他/她只会使用他们需要的内容,而忽略其余的内容”

让我们看另一个例子,使用所有内容都是整数的结构:

f1:  {x,y,z} -> {x,y}
f2:  {x,y}   -> {x,y,z}
所以在这里,我们再次断言f2是f1的一个子类型(它是)。查看这两个函数的参数类型(并使用 看看这两个函数的返回类型,如果f2{x,y,z}?答案再次是肯定的。(参见上述逻辑)

第三种方法是假设f2
这是另一个答案,因为虽然我理解函数子类型规则是如何有意义的,但我想了解为什么任何其他参数/结果子类型组合都没有意义


子类型规则为:

这意味着,如果满足顶部子类型条件,则底部为真

在函数类型定义中,函数参数是逆变的,因为我们颠倒了
T1
S1
之间的子类型关系。函数结果是协变的,因为它们保留了
T2
S2
之间的子类型关系

既然没有定义,为什么规则是这样的?Aaron Fi的回答中已经很好地说明了这一点,我还找到了定义(搜索标题“函数类型”):

另一种观点是允许一种类型的函数是安全的
S1→ S2
用于另一种类型的T1→ 应为T2
只要在此上下文中可能传递给函数的参数没有一个会让它感到意外(
T1),问题就得到了回答,但我想在这里给出一个简单的示例(关于参数类型,它是非直观的)

以下代码将失败,因为您只能将字符串传递给
myFuncB
, 我们正在传递数字和布尔值

typedef FuncTypeA = Object Function(Object obj); // (Object) => Object
typedef FuncTypeB = String Function(String name); // (String) => String

void process(FuncTypeA myFunc) {
   myFunc("Bob").toString(); // Ok.
   myFunc(123).toString(); // Fail.
   myFunc(true).toString(); // Fail.
}

FuncTypeB myFuncB = (String name) => name.toUpperCase();

process(myFuncB);
但是,下面的代码将起作用,因为现在您可以将任何类型的对象传递给
myFuncB
, 我们只传递字符串

typedef FuncTypeA = Object Function(String name); // (String) => Object
typedef FuncTypeB = String Function(Object obj); // (Object) => String

void process(FuncTypeA myFuncA) {
   myFunc("Bob").toString(); // Ok.
   myFunc("Alice").toString(); // Ok.
}

FuncTypeB myFuncB = (Object obj) => obj.toString();

process(myFuncB);

我一直在努力寻找我自己对这个问题的答案,因为我觉得仅仅接受替代规则并不直观。因此,我的尝试如下:

根据定义:函数
f1:A1=>B1
f2:A2=>B2
的超类型,如果
f2
可以在需要
f1
的地方使用

现在看下图,想象水从上到下流过漏斗
f1
f2

我们意识到,如果我们想用另一个漏斗
f2
替换漏斗
f1
,那么:

  • 输入直径
    A2
    不得小于
    A1
  • 输出直径
    B2
    需要不大于
    B1
同样的推理也适用于函数:为了使
f2
能够替换
f1
,然后:


  • f2
    的输入集
    A2
    需要涵盖
    f1
    的所有可能输入,即
    A1
    f2:A2=>B2

    请进一步澄清您的问题。格式没有我预期的格式。修复了。抱歉,我的解释比我希望的要长。我仍然有点困惑。如果{x,y,z}是{x,y}的子类型,为什么bool是int的子类型?在第一种情况下{x,y,z}比{x,y}大,但在另一个例子中bool比int小。因为你可以将int转换成bool。(无论如何,在大多数编程语言中。例如,将零视为false,所有其他整数值视为true)对我来说,这个问题的答案似乎和很多信息相冲突。子类型化意味着逆变方法参数和协变返回类型,不是吗?换句话说,通用->特定函数将是特定->通用函数的子类型(假设特定是通用函数的子类型)。此答案不正确。此语句是反向的:“参数类型必须是协变的,返回类型必须是逆变的。”答案由@dbmii提供
    {x, y} → {a, b} ⟹ {x, y, z} → {a} ✔
    
    map( f1 : {x, y, z} → {a, b}, L : [ {x, y} ] ) : [ {a, b} ]
    
    map( f1 : {x, y} → {a}, L : [ {x, y, z} ] ) : [ {a} ]
    
    typedef FuncTypeA = Object Function(Object obj); // (Object) => Object
    typedef FuncTypeB = String Function(String name); // (String) => String
    
    void process(FuncTypeA myFunc) {
       myFunc("Bob").toString(); // Ok.
       myFunc(123).toString(); // Fail.
       myFunc(true).toString(); // Fail.
    }
    
    FuncTypeB myFuncB = (String name) => name.toUpperCase();
    
    process(myFuncB);
    
    typedef FuncTypeA = Object Function(String name); // (String) => Object
    typedef FuncTypeB = String Function(Object obj); // (Object) => String
    
    void process(FuncTypeA myFuncA) {
       myFunc("Bob").toString(); // Ok.
       myFunc("Alice").toString(); // Ok.
    }
    
    FuncTypeB myFuncB = (Object obj) => obj.toString();
    
    process(myFuncB);