Возврат нескольких значений
Apr. 21st, 2017 08:41 amИнтересно и немного забавно наблюдать, как новые языки программирования разворачиваются от старой кондовой концепции "функция может возвращать только одно значение".
C сохраняет подход с единственным значением. Где нужно возвращать более одного значения - начинается передача по указателю (примеры на каждом шагу).
В C++ уже можно за счёт tuple делать возврат с распаковкой в набор переменных, хотя реализация этого на уровне ABI скорее всего идёт через структуру в памяти, а не регистры. Ну и передача по ссылке, конечно же, как косвенный метод (с затратами памяти).
Pascal - та же передача по ссылке.
Forth изначально не имел этой проблемы (хотя держание всего на стеке - само по себе источник тормозов).
В Java формально одно значение (возврат объекта, пока нет value types, слишком дорог, хотя JIT это может соптимизировать). В результате возникают ситуации типа возврата массивов, или битовых трюков (которые работают только потому, что нету типов целых беззнаковых; заметим, что в этой доке везде пишут -1-x, а не ~x - почему?)
В C# вначале сделали out-параметры (и, я считаю, требование писать out и ref в списке параметров фактического вызова - гениальное и чрезвычайно важное требование; Паскаль до этого не дошёл), теперь наконец добавили кортежи.
Fortran за счёт требования передачи по ссылке или входного-выходного копирования всех параметров решал это изначально (считаем, что аналог ref из C#) и даже чрезмерно (вспомним тему модификации константы).
Динамические языки (типа Python, JS, Erlang) возвращают по-прежнему одно значение, но за счёт того, что оно может быть кортежом (списком...), получается произвольное количество (за счёт затрат на распаковку) - не решение, но лёгкий обход проблемы.
Go, Swift, Rust - возврат нескольких параметров сделан изначально.
Но на уровне скомпилированного кода приходится ориентироваться на монстров компиляции C, так LLVM сделал возможность возврата нескольких параметров, а GCC - нет. Он тормозит всех. В результате в свежайших разработках типа RISC-V ABI имеем только одно возвращаемое значение (может растягиваться на два регистра, сути это не меняет). Не могу нагуглить, что мешает GCCʼшникам.
C сохраняет подход с единственным значением. Где нужно возвращать более одного значения - начинается передача по указателю (примеры на каждом шагу).
В C++ уже можно за счёт tuple делать возврат с распаковкой в набор переменных, хотя реализация этого на уровне ABI скорее всего идёт через структуру в памяти, а не регистры. Ну и передача по ссылке, конечно же, как косвенный метод (с затратами памяти).
Pascal - та же передача по ссылке.
Forth изначально не имел этой проблемы (хотя держание всего на стеке - само по себе источник тормозов).
В Java формально одно значение (возврат объекта, пока нет value types, слишком дорог, хотя JIT это может соптимизировать). В результате возникают ситуации типа возврата массивов, или битовых трюков (которые работают только потому, что нету типов целых беззнаковых; заметим, что в этой доке везде пишут -1-x, а не ~x - почему?)
В C# вначале сделали out-параметры (и, я считаю, требование писать out и ref в списке параметров фактического вызова - гениальное и чрезвычайно важное требование; Паскаль до этого не дошёл), теперь наконец добавили кортежи.
Fortran за счёт требования передачи по ссылке или входного-выходного копирования всех параметров решал это изначально (считаем, что аналог ref из C#) и даже чрезмерно (вспомним тему модификации константы).
Динамические языки (типа Python, JS, Erlang) возвращают по-прежнему одно значение, но за счёт того, что оно может быть кортежом (списком...), получается произвольное количество (за счёт затрат на распаковку) - не решение, но лёгкий обход проблемы.
Go, Swift, Rust - возврат нескольких параметров сделан изначально.
Но на уровне скомпилированного кода приходится ориентироваться на монстров компиляции C, так LLVM сделал возможность возврата нескольких параметров, а GCC - нет. Он тормозит всех. В результате в свежайших разработках типа RISC-V ABI имеем только одно возвращаемое значение (может растягиваться на два регистра, сути это не меняет). Не могу нагуглить, что мешает GCCʼшникам.
no subject
Date: 2017-04-21 06:25 am (UTC)no subject
Date: 2017-04-21 07:09 am (UTC)На уровне ABI это видно по тому, что не используются несколько регистров для возврата - вместо этого появляется скрытый параметр - адрес структуры, которую надо заполнить.
no subject
Date: 2017-07-03 04:18 pm (UTC)no subject
Date: 2017-07-03 04:27 pm (UTC)Если функция реально возвращает какие-то значения, почему бы это не писать явно?
Вон тот же open(), возврат - код ошибки и дескриптор.
no subject
Date: 2017-07-03 05:34 pm (UTC)Функция (говоря в общем) никогда не создает "новую" память. Количество димов в компьютере не меняется по вызову функции. Поэтому возвращать что-то, что мы обязаны не профукать на следующем шагу считаю плохим тоном. Попыткой натянуть бизнеслогику на алгебраические выражения. Читай - доказательное программирование. Отсюда и отношение к функциям скорее как к функторам.
Как пример. Мы возвращаем std::pair в котором first это, скажем, errno, а second некий объект. Насколько валиден second если first=EAGAIN? Есть ли необходимость удалить/закрыть second в этом случае? Нужно ли вызвать эту функцию еще раз? Нам необходимо переинициализировать параметры для нее?
no subject
Date: 2017-07-09 02:17 pm (UTC)> Поэтому возвращать что-то, что мы обязаны не профукать на следующем шагу считаю плохим тоном. Попыткой натянуть бизнеслогику на алгебраические выражения. Читай - доказательное программирование.
Эээ?
> Насколько валиден second если first=EAGAIN?
А зачем она его вообще возвращает? Она его создала бы, если бы не было ошибки? Тогда - невалиден. И вообще должен быть пустым.
> Есть ли необходимость удалить/закрыть second в этом случае? Нужно ли вызвать эту функцию еще раз? Нам необходимо переинициализировать параметры для нее?
Понимаешь ли, эти вопросы и сейчас встают в полный рост. Только сейчас какой-нибудь accept() передаёт ошибку неявно.