Por que múltiplos valores de retorno não são comuns?

7

Gostaria de fazer uma pergunta sobre vários valores de retorno, por que esse construto não é preferível em linguagens de programação (dificuldades conceituais e / ou técnicas). Eu ouvi algo sobre quadros de pilha e como eles reservam memória para valor de retorno e valores de retorno variáveis ​​podem tornar isso problemático, mas se alguém pudesse explicar isso melhor, isso seria muito apreciado.

Em linguagens conctanetative (como FORTH), ter vários valores de retorno de uma função é uma coisa comum e muito útil, e imagino que algo parecido com isto em linguagens do tipo java / c também seria útil (um exemplo muito básico):

x, y = multRet(5);
multret(int x) {
    return x+1;
    return x+2;
    exit;
}

Para esclarecer: não estou perguntando como retornar vários valores (essa é uma pergunta conhecida com respostas conhecidas), mas quero obter um esclarecimento sobre por que essa prática não é comum em linguagens de programação.

artemonster
fonte
4
Uma explicação possível é que, se você precisar retornar vários valores, sempre poderá fazer isso retornando uma estrutura ou lista. Por que complicar o idioma e o compilador?
David Richerby
4
Eu não acho que a pergunta seja respondida. Primeiro, isso é apenas uma questão de gosto. Segundo, acho que a premissa de que é "incomum" é falsa. Há um grande número de idiomas que suportam tuplas de retorno, e existem desde a década de 1970, e a prática de oferecer suporte a tuplas de retorno parece estar ganhando popularidade, pois todas as linguagens de programação de sistemas populares recentemente projetadas (Swift, Go, Rust) suportam tuplas de retorno .
Wandering Logic
2
Eu concordo com @DavidRicherby; esse recurso seria redundante e teria semântica potencialmente difícil / obscura (o que acontece com i=0; while (true) { return i; i+=1 }?). Observe que algumas linguagens funcionais oferecem tuplas, uma alternativa melhor.
Raphael
11
@WanderingLogic Uma tupla ainda é um valor, retornado por uma returninstrução. Ao retornar funções / lambdas, você pode ter razão.
Raphael
11
Não concordo que esta questão seja baseada em opiniões: os comentários até agora indicam que existem respostas técnicas. Se você acha que a parte "por que" da pergunta é muito subjetiva, ela pode ser facilmente editada para "Quais são as vantagens e desvantagens de permitir vários valores de retorno?"
precisa saber é o seguinte

Respostas:

7

As funções são tradicionalmente consideradas como retornando um único valor - em matemática. É claro que vários valores formam uma tupla, mas muitas vezes em matemática estamos lidando com funções com valor real. Suspeito que essa origem de funções seja a razão pela qual vários valores de retorno não são tão comuns. Dito isso, é fácil imitá-los de várias maneiras (retornando uma tupla ou tendo vários parâmetros de saída, ou seja, passados ​​por referência), e algumas linguagens modernas comuns (como Python) suportam nativamente essa construção.

Yuval Filmus
fonte
4

Ter vários valores de retorno é preferível . Na falta deles, o design é ruim, puro e simples. No entanto, devemos esclarecer o equívoco de que tal coisa existe no sentido em que você está pensando.

Em linguagens baseadas no cálculo lambda digitado (as linguagens de programação progenitoras), por exemplo, família ML, Haskell, uma função (abstração lambda) aceita apenas um único argumento e "retorna" (mais formalmente, avalia para) um único resultado. Então, como podemos criar uma função que parece receber ou retornar vários valores?

A resposta é que podemos "colar" valores usando tipos de produtos . Se A e B são os dois tipos, o tipo de produtoUMA×B contém todas as tuplas do formulário (uma,b), Onde uma:UMA e b:B.

Assim, para criar uma função multiplyque multiplica dois números inteiros, atribuímos o tipo a ela multiply: int * int -> int. Ele aceita um único argumento que passa a ser uma tupla. Da mesma forma, uma função splitque divide uma lista em duas teria tipo split: a list -> a list * a list.

Talvez 30 anos atrás, a implementação era uma preocupação válida ao incluir esse recurso. De acordo com a convenção de chamada C cedl (e é exatamente isso, uma convenção - que ninguém a ordenou), o valor de retorno é armazenado em um registro especial, geralmente EAX. Certamente, isso é extremamente rápido de ler e escrever, então a ideia tem algum mérito.

Mas é muito limitante, como você aponta, e não há motivo para seguirmos essa regra hoje em dia. Por exemplo, poderíamos usar a seguinte política: para um pequeno número de valores retornados, use um conjunto de registros especificados (temos mais registros para uso agora do que nos anos 80). Para dados maiores, poderíamos retornar um ponteiro para um local na memória. Não sei o que é melhor; Eu não sou um escritor de compilador. Mas a convenção arcaica de chamada C não deve ter influência na semântica das linguagens modernas.

Gardenhead
fonte
1

A sintaxe não é necessária quando padrões simples fazem o trabalho melhor e a sintaxe proposta seria historicamente confusa para muitas pessoas. Isso significa que sua sintaxe é difícil de aprender e causará problemas quando as pessoas alternarem entre seu idioma e os outros.

O padrão simples que resolve esse problema é definir uma variável denominada "returnValue" no início do seu método e depois atribuir valores a ele. Se você precisar de vários valores, basta usar uma coleção de algum tipo e adicionar os valores. Em seguida, retorne esta variável. Problema resolvido.

Sua sintaxe significaria que, por padrão, você teria que lidar com coleções. Isso é frustrante para funções que retornam apenas 1 valor.

E se alguém fizer algo errado? Por exemplo.

x, y = multRet(5);
multret(int x) {
    return x+1;
    exit;
}

ou

x = multRet(5);
multret(int x) {
    return x+1;
    return x+1;
    exit;
}

O compilador deve pegar isso? E se você tiver um loop?

Na melhor das hipóteses, isso seria açúcar sintático. Algo que não adiciona novos recursos, mas apenas torna os padrões atuais mais confortáveis ​​e mais curtos no código.

Na pior das hipóteses, isso levaria a um erro horrível e horrível no código. Exceção de ponteiro nulo em massas.

Esperançosamente
fonte
11
O compilador pode capturar vários valores de retorno. É o que a linguagem de programação Go faz.
slebetman
1

É principalmente por razões sintáticas; no seu próprio exemplo, a primeira declaração de retorno retornaria um valor e retornaria e nunca alcançaria a segunda declaração de retorno.

Atualmente, existem alguns idiomas que permitem o retorno de uma tupla, que é zero, um ou mais valores de tipos arbitrários, agrupados em um único valor. Ainda assim, eles estão retornando um valor, apenas um valor um pouco mais complicado. Geralmente, esses idiomas também podem atribuir uma tupla a zero, uma ou mais variáveis ​​correspondentes, geralmente com uma maneira fácil de ignorar alguns elementos da tupla. Isso pode ser assim:

Atribua tupla a outra tupla:

x = multRet(5); // x is a variable of type tuple (int, int)
multret(int x) {
    return (x+1, x+2);
}

(x, y) = multRet(5); // x, y are variables of type int
multret(int x) {
    return (x+1, x+2);
}

(x, _) = multRet(5); // x is a variable of type int, second component ignored. 
multret(int x) {
    return (x+1, x+2);
}

Essa sintaxe não seria compatível com C, onde (x, y) é uma expressão de vírgula entre parênteses. Por outro lado, é muito mais fácil receber dois valores do que em C, onde você passa os dois valores usando ponteiros ou declara uma estrutura para esse fim, preenche e devolve. Usando ponteiros, o terceiro exemplo em que o chamador está interessado apenas em um valor é uma dor, portanto esse método de retornar tuplas é muito útil.

gnasher729
fonte
Go é um exemplo em que os valores retornados são valores separados em vez de uma tupla. No Go, você pode executar o segundo e o terceiro exemplos, mas o primeiro exemplo resultaria em erro em tempo de compilação: o compilador espera duas variáveis, mas obtém apenas uma. A sintaxe escolhido pelo Go é simplesmente return x, ye para aceitar os valores de retorno que você fariax,y = multiRet()
slebetman
Você quer dizer que Go não tem variáveis ​​do tipo tupla?
gnasher729
Como qualquer outro idioma C, o Go possui estruturas e, como a maioria das linguagens modernas, o Go possui um tipo de mapa (matriz de hash / associativo), para que você possa implementar tuplas como estruturas ou mapas. Uma coisa que Go tem e que não vejo com frequência são estruturas anônimas - estruturas sem nome.
slebetman
Bem, idiomas mais recentes como o Swift têm tuplas. Ter que implementar tuplas você mesmo tira a maior parte da vantagem.
gnasher729
No LISP é comum. Também podemos usar a ligação de valores múltiplos para atribuir cada valor de retorno a uma variável
Talk is Cheap Me mostre código