函数是否按返回类型重载? [英] Function overloading by return type?

查看:93
本文介绍了函数是否按返回类型重载?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

为什么更多主流的静态类型语言不支持返回类型的函数/方法重载?我想不出有什么办法.似乎没有比按参数类型支持重载有用或合理的了.怎么这么不那么受欢迎呢?

Why don't more mainstream statically typed languages support function/method overloading by return type? I can't think of any that do. It seems no less useful or reasonable than supporting overload by parameter type. How come it's so much less popular?

推荐答案

与其他人的说法相反,返回类型可能是超载,而是由某些现代人完成的语言.通常的反对意见是在类似

Contrary to what others are saying, overloading by return type is possible and is done by some modern languages. The usual objection is that in code like

int func();
string func();
int main() { func(); }

您无法确定正在调用哪个func().可以通过以下几种方法解决此问题:

you can't tell which func() is being called. This can be resolved in a few ways:

  1. 具有一种可预测的方法来确定在这种情况下调用哪个函数.
  2. 无论何时发生这种情况,都是编译时错误.但是,请使用允许程序员消除歧义的语法,例如int main() { (string)func(); }.
  3. 没有副作用.如果您没有副作用,并且从不使用函数的返回值,则编译器可以避免首先调用该函数.

我经常使用的两种语言( ab )按返回类型使用重载: Perl Haskell .让我描述一下他们的工作.

Two of the languages I regularly (ab)use overload by return type: Perl and Haskell. Let me describe what they do.

Perl 中,标量 list 上下文(和其他上下文)之间有根本的区别(但我们会假装有两个) . Perl中的每个内置函数都可以根据调用它的 context 来做不同的事情.例如,join运算符强制列表上下文(在连接的事物上),而​​scalar运算符强制标量上下文,因此进行比较:

In Perl, there is a fundamental distinction between scalar and list context (and others, but we'll pretend there are two). Every built-in function in Perl can do different things depending on the context in which it is called. For example, the join operator forces list context (on the thing being joined) while the scalar operator forces scalar context, so compare:

print join " ", localtime(); # printed "58 11 2 14 0 109 3 13 0" for me right now
print scalar localtime(); # printed "Wed Jan 14 02:12:44 2009" for me right now.

Perl中的每个运算符都在标量上下文中执行某些操作,而在列表上下文中执行某些操作,它们可能会有所不同,如图所示. (这不仅适用于像localtime这样的随机运算符.如果在列表上下文中使用数组@a,它将返回该数组,而在标量上下文中,它将返回元素的数量.因此例如,print @a打印删除元素,而print 0+@a打印尺寸.)此外,每个操作员都可以 force 上下文,例如加法+强制标量上下文. man perlfunc中的每个条目都对此进行了记录.例如,这是glob EXPR条目的一部分:

Every operator in Perl does something in scalar context and something in list context, and they may be different, as illustrated. (This isn't just for random operators like localtime. If you use an array @a in list context, it returns the array, while in scalar context, it returns the number of elements. So for example print @a prints out the elements, while print 0+@a prints the size.) Furthermore, every operator can force a context, e.g. addition + forces scalar context. Every entry in man perlfunc documents this. For example, here is part of the entry for glob EXPR:

在列表上下文中,返回一个(可能是 空)上的文件名扩展列表 EXPR的值,例如标准 Unix shell /bin/csh可以.在 标量上下文,全局遍历 这样的文件名扩展,返回 列表用完后,undef.

In list context, returns a (possibly empty) list of filename expansions on the value of EXPR such as the standard Unix shell /bin/csh would do. In scalar context, glob iterates through such filename expansions, returning undef when the list is exhausted.

现在,列表和标量上下文之间有什么关系?好吧,man perlfunc

Now, what's the relation between list and scalar context? Well, man perlfunc says

请记住以下重要规则: 没有规则与 列表中表达式的行为 标量行为的上下文 上下文,反之亦然.它可能会做 完全不同的两件事.每个 运算符和功能决定哪个 最有价值的一种 适合以标量返回 语境.一些操作员返回 清单的长度 在列表上下文中返回.一些 运算符返回中的第一个值 列表.一些操作员返回 列表中的最后一个值.一些 操作员返回成功的计数 操作.一般来说,他们会做什么 您想要的,除非您想要保持一致性.

Remember the following important rule: There is no rule that relates the behavior of an expression in list context to its behavior in scalar context, or vice versa. It might do two totally different things. Each operator and function decides which sort of value it would be most appropriate to return in scalar context. Some operators return the length of the list that would have been returned in list context. Some operators return the first value in the list. Some operators return the last value in the list. Some operators return a count of successful operations. In general, they do what you want, unless you want consistency.

因此,拥有一个函数并不是一件简单的事情,然后在最后进行简单的转换.实际上,由于这个原因,我选择了localtime示例.

so it's not a simple matter of having a single function, and then you do simple conversion at the end. In fact, I chose the localtime example for that reason.

具有此行为的不仅仅是内置组件.任何用户都可以使用wantarray定义此类函数,该函数可让您区分列表,标量和空上下文.因此,例如,如果您在空白上下文中被调用,则可以决定不执行任何操作.

It's not just the built-ins that have this behavior. Any user can define such a function using wantarray, which allows you to distinguish between list, scalar, and void context. So, for example, you can decide to do nothing if you're being called in void context.

现在,您可能会抱怨返回值不是 true 重载,因为您只有一个函数,该函数会在调用上下文的情况下被告知然后对这些信息进行操作.但是,这显然是等效的(类似于Perl不允许字面上通常进行的重载,但是函数只能检查其参数).此外,它很好地解决了此响应开始时提到的模棱两可的情况. Perl不会抱怨它不知道该调用哪种方法.它只是称之为.它要做的就是找出调用该函数的上下文,这始终是可能的:

Now, you may complain that this isn't true overloading by return value because you only have one function, which is told the context it's called in and then acts on that information. However, this is clearly equivalent (and analogous to how Perl doesn't allow usual overloading literally, but a function can just examine its arguments). Moreover, it nicely resolves the ambiguous situation mentioned at the beginning of this response. Perl doesn't complain that it doesn't know which method to call; it just calls it. All it has to do is figure out what context the function was called in, which is always possible:

sub func {
    if( not defined wantarray ) {
        print "void\n";
    } elsif( wantarray ) {
        print "list\n";
    } else {
        print "scalar\n";
    }
}

func(); # prints "void"
() = func(); # prints "list"
0+func(); # prints "scalar"

(注意:当我指的是函数时,我有时可能会说Perl运算符.这对本次讨论并不重要.)

(Note: I may sometimes say Perl operator when I mean function. This is not crucial to this discussion.)

Haskell 采用另一种方法,即没有副作用.它还具有强大的类型系统,因此您可以编写如下代码:

Haskell takes the other approach, namely to not have side effects. It also has a strong type system, and so you can write code like the following:

main = do n <- readLn
          print (sqrt n) -- note that this is aligned below the n, if you care to run this

此代码从标准输入中读取浮点数,并打印其平方根.但是,这有什么令人惊讶的呢?好吧,readLn的类型是readLn :: Read a => IO a.这意味着对于任何可能为Read的类型(从形式上讲,每个类型都是Read类型类的实例),readLn都可以读取它. Haskell如何知道我想读取浮点数?好吧,sqrt的类型是sqrt :: Floating a => a -> a,这实际上意味着sqrt只能接受浮点数作为输入,因此Haskell推断了我想要的内容.

This code reads a floating point number from standard input, and prints its square root. But what is surprising about this? Well, the type of readLn is readLn :: Read a => IO a. What this means is that for any type that can be Read (formally, every type that is an instance of the Read type class), readLn can read it. How did Haskell know that I wanted to read a floating point number? Well, the type of sqrt is sqrt :: Floating a => a -> a, which essentially means that sqrt can only accept floating point numbers as inputs, and so Haskell inferred what I wanted.

Haskell无法推断我想要什么会发生什么?好吧,有几种可能性.如果我根本不使用返回值,Haskell根本不会首先调用该函数.但是,如果我 do 使用返回值,则Haskell将抱怨它无法推断类型:

What happens when Haskell can't infer what I want? Well, there a few possibilities. If I don't use the return value at all, Haskell simply won't call the function in the first place. However, if I do use the return value, then Haskell will complain that it can't infer the type:

main = do n <- readLn
          print n
-- this program results in a compile-time error "Unresolved top-level overloading"

我可以通过指定所需的类型来解决歧义:

I can resolve the ambiguity by specifying the type I want:

main = do n <- readLn
          print (n::Int)
-- this compiles (and does what I want)

无论如何,整个讨论的意思是返回值的重载是可能的并且已经完成,这回答了您的部分问题.

Anyway, what this whole discussion means is that overloading by return value is possible and is done, which answers part of your question.

问题的另一部分是为什么更多的语言不这样做.我让别人回答.但是,有几点评论:原则上的原因可能是,与参数类型的重载相比,此处混淆的机会确实更大.您还可以查看各种语言的基本原理:

The other part of your question is why more languages don't do it. I'll let others answer that. However, a few comments: the principle reason is probably that the opportunity for confusion is truly greater here than in overloading by argument type. You can also look at rationales from individual languages:

Ada :似乎最简单的重载解决方案规则是使用所有内容-来自尽可能广泛的上下文中的所有信息-来解析重载引用.此规则可能很简单,但无济于事,它需要人工阅读器来扫描任意大块的文本,并做出任意复杂的推论(例如上面的(g)).我们认为,更好的规则是明确规定人类读者或编译器必须执行的任务,并使该任务对人类读者而言自然而然.可能."

Ada: "It might appear that the simplest overload resolution rule is to use everything - all information from as wide a context as possible - to resolve the overloaded reference. This rule may be simple, but it is not helpful. It requires the human reader to scan arbitrarily large pieces of text, and to make arbitrarily complex inferences (such as (g) above). We believe that a better rule is one that makes explicit the task a human reader or a compiler must perform, and that makes this task as natural for the human reader as possible."

C ++(Bjarne Stroustrup的"C ++编程语言"的7.4.1小节):在重载解析中不考虑返回类型.原因是保持独立于运算符或函数调用的解析独立于上下文.请考虑:

C++ (subsection 7.4.1of Bjarne Stroustrup's "The C++ Programming Language"): "Return types are not considered in overload resolution. The reason is to keep resolution for an individual operator or function call context-independent. Consider:

float sqrt(float);
double sqrt(double);

void f(double da, float fla)
{
    float fl = sqrt(da);     // call sqrt(double)
    double d = sqrt(da); // call sqrt(double)
    fl = sqrt(fla);            // call sqrt(float)
    d = sqrt(fla);             // call sqrt(float)
}

如果考虑到返回类型,将不再可能单独查看sqrt()的调用并确定调用了哪个函数."(注意,为了进行比较,在Haskell中没有隐式转换.)

If the return type were taken into account, it would no longer be possible to look at a call of sqrt() in isolation and determine which function was called." (Note, for comparison, that in Haskell there are no implicit conversions.)

Java( Java语言规范9.4.1 ):其中一个继承的方法必须对于所有其他继承的方法都可以用返回类型替换,否则会发生编译时错误." (是的,我知道这并没有给出基本原理.我确定基本原理是Gosling在"Java编程语言"中给出的.也许有人抄袭了吗?我敢打赌,它本质上是最不令人惊讶的原则". )但是,关于Java的一个有趣的事实是:JVM 允许通过返回值进行重载!例如,在 Scala中使用此方法,并且可以访问

Java (Java Language Specification 9.4.1): "One of the inherited methods must be return-type-substitutable for every other inherited method, or else a compile-time error occurs." (Yes, I know this doesn't give a rationale. I'm sure the rationale is given by Gosling in "the Java Programming Language". Maybe someone has a copy? I bet it's the "principle of least surprise" in essence.) However, fun fact about Java: the JVM allows overloading by return value! This is used, for example, in Scala, and can be accessed directly through Java as well by playing around with internals.

PS.最后一点,实际上可以通过技巧在C ++中通过返回值重载.证人:

PS. As a final note, it is actually possible to overload by return value in C++ with a trick. Witness:

struct func {
    operator string() { return "1";}
    operator int() { return 2; }
};

int main( ) {
    int x    = func(); // calls int version
    string y = func(); // calls string version
    double d = func(); // calls int version
    cout << func() << endl; // calls int version
    func(); // calls neither
}

这篇关于函数是否按返回类型重载?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆