从函数返回多个值的优雅方法

时间:2009-02-05 00:39:49

标签: python function language-agnostic language-design syntactic-sugar

在大多数主流编程语言中,从函数中返回多个值是一件非常尴尬的事情。

典型的解决方案是创建结构或普通旧数据并返回它,或者通过引用或指针传递至少一些参数归还他们。

使用引用/指针非常尴尬,因为它依赖于副作用,意味着你还有另一个参数要传递。

类/结构解决方案也是恕我直言,因为你最终得到了一百万个小类/结构,它们只用于从函数返回值,产生不必要的混乱和冗长。

此外,很多时候总是需要一个返回值,其余的只在某些情况下由调用者使用。这些解决方案都不允许调用者忽略不需要的返回类型。

我所知道的一种优雅处理多个返回值的语言是Python。对于那些不熟悉的人,它使用元组解包:

a, b = foo(c)  # a and b are regular variables.
myTuple = foo(c)  # myTuple is a tuple of (a, b)

有没有人对此问题有任何其他好的解决方案?除了Python之外,现有主流语言中的两种成语以及您在非主流语言中看到的语言级解决方案都是受欢迎的。

14 个答案:

答案 0 :(得分:24)

几乎所有受ML影响的功能语言(大部分都是这样)也有很好的元组支持,这使得这类事情变得微不足道。

对于C ++,我喜欢boost :: tuple plus boost :: tie(如果你有的话,还是std :: tr1)

typedef boost::tuple<double,double,double> XYZ;

XYZ foo();

double x,y,z;
boost::tie(x,y,z) = foo();

或一个不那么做作的例子

MyMultimap::iterator lower,upper;
boost::tie(lower,upper) = some_map.equal_range(key);

答案 1 :(得分:9)

一些语言,特别是Lisp和JavaScript,具有称为解构赋值或解构绑定的功能。这基本上是对类固醇进行元组拆包:而不是仅限于元组,列表或生成器等序列,您可以在赋值语句中解包更复杂的对象结构。有关详细信息,请参阅here for the Lisp versionhere for the (rather more readable) JavaScript version

除此之外,我一般不知道处理多个返回值的语言功能很多。但是,多个返回值的一些特定用法通常可以被其他语言功能替换。例如,如果其中一个值是错误代码,则可以更好地替换为异常。

虽然创建新类来保存多个返回值感觉就像杂乱一样,但是将这些值一起返回的事实通常表明,一旦创建了类,您的代码总体上会更好。特别是,处理相同数据的其他函数可以移动到新类,这可以使您的代码更容易理解。这不是普遍的,但值得考虑。 (Cpeterso关于数据块的答案更详细地表达了这一点。)

答案 2 :(得分:7)

PHP示例:

function my_funct() {
    $x = "hello";
    $y = "world";
    return array($x, $y);
}

然后,当跑:

list($x, $y) = my_funct();
echo $x.' '.$y; // "hello world"

答案 3 :(得分:5)

如果函数返回多个值,那么您可能正在见证"Data Clump" code smell。通常数据块是原始值,没有人认为它会变成对象,但是当你开始寻找行为进入新对象时会发生有趣的事情。

编写微小的帮助程序类可能是额外的输入,但它提供了清晰的名称和强类型检查。维护代码的开发人员会很感激。有用的小班经常长大,可以在其他代码中使用。

此外,如果函数返回多个值,那么它可能会做太多工作。该函数可以重构为两个(或更多)小函数吗?

答案 4 :(得分:4)

似乎没有人提到过Perl。

sub myfunc {
  return 1, 2;
}
my($val1, $val2) = myfunc();

答案 5 :(得分:4)

关于Java,请参阅Bruce Eckel的Thinking in Java以获得一个很好的解决方案(第621页)。

实质上,您可以定义与以下内容等效的类:

public class Pair<T,U> {
    public final T left;
    public final U right;
    public Pair (T t, U u) { left = t; right = u; }
}

然后,您可以将此作为函数的返回类型,并使用适当的类型参数:

public Pair<String,Integer> getAnswer() {
    return new Pair<String,Integer>("the universe", 42);
}

调用该函数后:

Pair<String,Integer> myPair = getAnswer();

您可以参考myPair.leftmyPair.right访问组成值。

还有其他语法糖选项,但以上是关键点。

答案 6 :(得分:3)

我认为python是最自然的方式,当我不得不在php中做同样的事情时,唯一的做法是将返回包装成数组。它确实有类似的拆包。

答案 7 :(得分:2)

即使你忽略了精彩的新的解构赋值Moss Collum mentioned,JavaScript也能很好地返回多个结果。

function give7and5() {
  return {x:7,y:5};
}

a=give7and5();
console.log(a.x,a.y);

7  5

答案 8 :(得分:1)

通常在php中我会使用引用的输入:

public function Validates(&$errors=array()) {
   if($failstest) {
      $errors[] = "It failed";
      return false;
   } else {
      return true;
   }
}

这给了我想要的错误消息而不会污染bool返回,所以我仍然可以这样做:

if($this->Validates()) ...

答案 9 :(得分:1)

Lua和CLU(麻省理工学院的Barbara Liskov小组)都有多个所有函数的返回值 - 它始终是默认值。我相信Lua的设计师受到了CLU的启发。我喜欢让多个值成为调用和返回的默认值;我认为这是一种非常优雅的思考方式。在Lua和CLU中,这种机制完全独立于元组和/或记录。

可移植汇编语言C--支持其本机调用约定的多个返回值,但不支持C调用约定。其目的是使编译器编写器能够在各个硬件寄存器中返回多个值,而不必将值放在内存中的clump中并返回指向该clump的指针(或者在C中,让调用者将指针传递给空丛。)

与任何机制一样,可以滥用多个返回值,但我认为让函数返回多个值比使结构包含多个值更不合理。

答案 10 :(得分:0)

C++中,您可以将容器传递给函数,以便函数可以填充它:

void getValues(std::vector<int>* result){
  result->push_back(2);
  result->push_back(6);
  result->push_back(73);
}

你也可以让函数返回一个智能指针(使用shared_ptr)到vector

boost::shared_ptr< std::vector<int> > getValues(){
  boost::shared_ptr< std::vector<int> > vec(new std::vector<int>(3));
  (*vec)[0] = 2;
  (*vec)[1] = 6;
  (*vec)[2] = 73;
  return vec; 
}

答案 11 :(得分:0)

C#

public struct tStruct
{
    int x;
    int y;
    string text;
    public tStruct(int nx, int ny, string stext)
    {
         this.x = nx;
         this.y = ny;
         this.text = stext;
    }
}

public tStruct YourFunction()
{
    return new tStruct(50, 100, "hello world");
}

public void YourPrintFunction(string sMessage)
{
    Console.WriteLine(sMessage);
}
你在Lua打电话

MyVar = YourFunction();
YourPrintfFunction(MyVar.x);
YourPrintfFunction(MyVar.y);
YourPrintfFunction(MyVar.text);

输出: 50 100 你好世界

答案 12 :(得分:0)

顺便说一句,Scala可以返回多个值,如下所示(从解释器会话中粘贴):

scala> def return2 = (1,2)
return2: (Int, Int)

scala> val (a1,a2) = return2
a1: Int = 1
a2: Int = 2

这是使用pattern matching for assignment的特殊情况。

答案 13 :(得分:0)

这是我的Ruby想法,它返回一个特殊的值,它看起来像一个标量但实际上有隐藏的属性:

https://gist.github.com/2305169