Подтвердить что ты не робот

Элегантные способы возврата нескольких значений из функции

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

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

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

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

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

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

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

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

4b9b3361

Ответ 1

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

Для С++ мне нравится 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);

Ответ 2

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

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

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

Ответ 3

Пример PHP:

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

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

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

Ответ 4

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

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

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

Ответ 5

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

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

Ответ 6

Что касается Java, см. Bruce Eckel Мышление в Java для хорошего решения (стр. 621 ff).

В сущности, вы можете определить класс, эквивалентный следующему:

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 для доступа к составным значениям.

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

Ответ 7

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

Ответ 8

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

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

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

7  5

Ответ 9

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

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

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

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

Ответ 10

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

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

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

Ответ 11

В 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; 
}

Ответ 12

С#

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 привет мир

Пол

Ответ 13

Кстати, Scala может возвращать несколько значений следующим образом (вставлено из сеанса интерпретатора):

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

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

Это особый случай использования соответствия шаблонов для назначения.

Ответ 14

Вот моя идея Ruby вернуть специальное значение, которое выглядит и действует как скаляр, но на самом деле имеет скрытые атрибуты:

https://gist.github.com/2305169