Элегантные способы вернуть несколько значений из функции - PullRequest
27 голосов
/ 05 февраля 2009

Похоже, что в большинстве основных языков программирования возвращение нескольких значений из функции является крайне неловкой вещью.

Типичные решения - создать struct или простые старые данные class и вернуть их или передать хотя бы некоторые параметры по ссылке или указателю вместо возврата их.

Использование ссылок / указателей довольно неловко, потому что оно основано на побочных эффектах и ​​означает, что вам нужно передать еще один параметр.

Решение класса / структуры также ИМХО довольно неловко, потому что в результате вы получаете миллион маленьких классов / структур, которые используются только для возврата значений из функций, создавая ненужный беспорядок и многословие.

Кроме того, во многих случаях всегда требуется одно возвращаемое значение, а остальные используются вызывающей стороной только в определенных обстоятельствах. Ни одно из этих решений не позволяет вызывающей стороне игнорировать ненужные типы возврата.

Единственный известный мне язык, который элегантно обрабатывает несколько возвращаемых значений, - это Python. Для тех из вас, кто незнаком, он использует распаковку кортежей:

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

Есть ли у кого-нибудь еще хорошие решения этой проблемы? Приветствуются обе идиомы, которые работают на существующих основных языках, кроме Python и решений на уровне языка, которые вы видели на неосновных языках.

Ответы [ 14 ]

24 голосов
/ 05 февраля 2009

Практически все функциональные языки под влиянием 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);
9 голосов
/ 05 февраля 2009

Некоторые языки, особенно Lisp и JavaScript, имеют функцию, называемую деструктурирующим присваиванием или деструктурирующим связыванием. По сути это распаковка кортежей на стероидах: вместо того, чтобы ограничиваться последовательностями, такими как кортежи, списки или генераторы, вы можете распаковать более сложные структуры объектов в операторе присваивания. Для получения дополнительной информации см. здесь для версии Lisp или здесь для (более читаемой) версии JavaScript .

Кроме этого, я не знаю многих языковых возможностей для работы с несколькими возвращаемыми значениями вообще. Однако существует несколько конкретных вариантов использования нескольких возвращаемых значений, которые часто могут быть заменены другими языковыми функциями. Например, если одно из значений является кодом ошибки, его лучше заменить на исключение.

При создании новых классов для хранения нескольких возвращаемых значений ощущается беспорядок, тот факт, что вы возвращаете эти значения вместе, часто является признаком того, что ваш код в целом будет лучше после создания класса. В частности, другие функции, работающие с теми же данными, могут затем перейти в новый класс, что может упростить отслеживание вашего кода. Это не всегда так, но стоит задуматься. (Ответ Cpeterso о скоплениях данных выражает это более подробно).

7 голосов
/ 05 февраля 2009

Пример PHP:

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

Затем при запуске:

list($x, $y) = my_funct();
echo $x.' '.$y; // "hello world"
5 голосов
/ 05 февраля 2009

Если функция возвращает несколько значений, это может быть признаком того, что запах «Data Clump» кода пахнет . Часто скопления данных являются примитивными значениями, которые никто не думает превратить в объект, но интересные вещи случаются, когда вы начинаете искать поведение для перемещения в новые объекты.

Написание крошечных вспомогательных классов может быть дополнительной типизацией, но она обеспечивает четкие имена и строгую проверку типов. Разработчики, поддерживающие ваш код, оценят это. А полезные маленькие классы часто вырастают для использования в другом коде.

Кроме того, если функция возвращает несколько значений, возможно, она выполняет слишком много работы. Может ли функция быть преобразована в две (или более) маленькие функции?

4 голосов
/ 05 февраля 2009

Что касается Java, см. Bruce Eckel's 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.left и myPair.right для доступа к составляющим значениям.

Существуют и другие синтаксические варианты сахара, но ключевым моментом является вышеуказанное.

4 голосов
/ 05 февраля 2009

Кажется, никто еще не упоминал Perl.

sub myfunc {
  return 1, 2;
}
my($val1, $val2) = myfunc();
3 голосов
/ 05 февраля 2009

я думаю, что python - самый естественный способ, когда мне нужно было сделать то же самое в php, единственное, что нужно было заключить в массив - это возвращать обратно. хотя он имеет аналогичную распаковку.

2 голосов
/ 05 февраля 2009

Даже если вы проигнорируете замечательное новое задание по деструктуризации Мосс Коллум упомянул , JavaScript довольно хорош при возвращении нескольких результатов.

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

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

7  5
1 голос
/ 05 февраля 2009

Оба Lua и CLU (по группе Барбары Лисков в MIT) имеют несколько возвращаемых значений для всех функций - это всегда по умолчанию. Я считаю, что дизайнеры Lua были вдохновлены CLU. Мне нравится иметь несколько значений по умолчанию для звонков и возвратов; Я думаю, что это очень элегантный способ думать о вещах. В Lua и CLU этот механизм полностью независим от кортежей и / или записей.

Переносимый язык ассемблера C - поддерживает несколько возвращаемых значений для своего собственного соглашения о вызовах, но не для соглашения о вызовах C. Там идея состоит в том, чтобы позволить автору компилятора возвращать несколько значений в отдельных аппаратных регистрах, вместо того, чтобы помещать значения в группу в памяти и возвращать указатель на эту группу (или, как в C, заставить вызывающего передать указатель на пустой комок).

Как и любой механизм, множественные возвращаемые значения могут быть использованы неправильно, но я не думаю, что более разумно иметь функцию, возвращающую несколько значений, чем структуру, содержащую несколько значений.

1 голос
/ 05 февраля 2009

Часто в php я использую ссылочный ввод:

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

Это дает мне сообщения об ошибках, которые я хочу, не загрязняя возврат bool, так что я все еще могу сделать:

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