programming-languages - 按返回类型重载函数?

标签 programming-languages language-design overloading function-calls

为什么更主流的静态类型语言不支持返回类型的函数/方法重载?我想不出有什么能做的。这似乎并不比通过参数类型支持重载有用或合理。怎么人气这么低?

最佳答案

与其他人所说的相反,按返回类型重载 可能和 由一些现代语言完成。通常的反对意见是在代码中

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

你分不清是哪个 func()正在被调用。这可以通过以下几种方式解决:
  • 有一个可预测的方法来确定在这种情况下调用哪个函数。
  • 每当这种情况发生时,就是编译时错误。但是,具有允许程序员消除歧义的语法,例如int main() { (string)func(); } .
  • 不要有副作用。如果您没有副作用并且从不使用函数的返回值,那么编译器可以首先避免调用该函数。

  • 我经常使用的两种语言( ab )按返回类型使用重载: Perl Haskell .让我描述一下他们是做什么的。

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

    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 打印大小。)此外,每个运算符都可以强制上下文,例如另外+强制标量上下文。 man perlfunc中的每一个条目记录这个。例如,这里是 glob EXPR 条目的一部分:

    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

    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例如出于这个原因。

    不仅仅是内置程序有这种行为。任何用户都可以使用 wantarray 定义这样的函数,它允许您区分列表、标量和空上下文。因此,例如,如果您在 void 上下文中被调用,您可以决定什么都不做。

    现在,您可能会提示这不是真正的返回值重载,因为您只有一个函数,该函数被告知调用它的上下文,然后根据该信息执行操作。然而,这显然是等价的(类似于 Perl 不允许通常的字面重载,但函数只能检查其参数)。而且,它很好地解决了本回复开头提到的模棱两可的情况。 Perl 不会提示它不知道调用哪个方法;它只是调用它。它所要做的就是弄清楚函数在什么上下文中被调用,这总是可能的:

    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 运算符。这对本次讨论来说并不重要。)

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

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

    此代码从标准输入读取浮点数,并打印其平方根。但这有什么令人惊讶的呢?嗯,类型readLnreadLn :: Read a => IO a .这意味着对于可以是 Read 的任何类型(正式地,每个类型都是 Read 类型类的实例),readLn可以阅读。 Haskell 怎么知道我想读取浮点数?嗯,类型sqrtsqrt :: Floating a => a -> a ,这实质上意味着 sqrt只能接受浮点数作为输入,所以 Haskell 推断出我想要什么。

    当 Haskell 无法推断出我想要什么时会发生什么?嗯,有几种可能性。如果我根本不使用返回值,Haskell 一开始就不会调用该函数。但是,如果我确实使用了返回值,那么 Haskell 会提示它无法推断类型:

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

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

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

    无论如何,整个讨论意味着通过返回值进行重载是可能的并且已经完成,这回答了您的部分问题。

    你问题的另一部分是为什么更多的语言不这样做。我会让其他人回答这个问题。但是,有一些评论:主要的原因可能是这里的混淆机会确实比按参数类型重载要大。您还可以查看各个语言的基本原理:

    Ada :“看起来最简单的重载解析规则是使用所有内容——尽可能广泛的上下文中的所有信息——来解析重载引用。这条规则可能很简单,但没有帮助。它需要人类读者扫描任意大的文本片段,并进行任意复杂的推理(例如上面的(g))。我们认为更好的规则是明确人类读者或编译器必须执行的任务,并使该任务成为对人类读者来说尽可能自然。”

    C++(Bjarne Stroustrup 的“C++ 编程语言”的第 7.4.1 小节):“在重载解析中不考虑返回类型。原因是保持单个运算符或函数调用的解析与上下文无关。考虑:

    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 中没有隐式转换。)

    Java ( Java Language Specification 9.4.1 ):“继承的方法之一必须是所有其他继承方法的返回类型可替换的,否则会发生编译时错误。” (是的,我知道这并没有给出一个基本原理。我确定这个基本原理是由 Gosling 在“Java 编程语言”中给出的。也许有人有副本?我敢打赌这本质上是“最小惊喜原则”。 ) 然而,关于 Java 的有趣事实:JVM 允许通过返回值进行重载!例如,这用于 Scala , 并可访问 directly through Java以及玩弄内部结构。

    附注。最后要注意的是,实际上可以通过一个技巧在 C++ 中通过返回值来重载。见证:

    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
    }
    

    关于programming-languages - 按返回类型重载函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/442026/

    相关文章:

    functional-programming - 在 Lisp 中创建命名本地列表

    C++ 对重载函数的模糊调用

    c++ - 为什么 clang 将字符串文字作为指针而不是数组?

    c# - 访问重写的方法

    Javascript:为什么是 Object.keys(someobject),而不是 someobject.keys?

    c++ - 为什么 std::unique_lock 不是从 std::lock_guard 派生的

    java - 什么是便携性? java如何比其他语言更便携?

    programming-languages - 关于Go语言的几个问题

    f# - F# 中的非类型化/类型化代码引用与宏卫生之间是否存在关系?

    c# - 在语法上是否需要不同的打开和关闭定界符?