Por que a maioria das linguagens de programação suporta apenas o retorno de um único valor de uma função? [fechadas]


118

Existe uma razão pela qual as funções na maioria das linguagens de programação (?) São projetadas para suportar qualquer número de parâmetros de entrada, mas apenas um valor de retorno?

Na maioria dos idiomas, é possível "contornar" essa limitação, por exemplo, usando parâmetros externos, retornando ponteiros ou definindo / retornando estruturas / classes. Mas parece estranho que as linguagens de programação não tenham sido projetadas para suportar múltiplos valores de retorno de uma maneira mais "natural".

Há uma explicação para isso?


40
porque você pode retornar uma matriz ...
Nathan campo de feno

6
Então, por que não apenas permitir um argumento? Eu suspeito que está ligado ao discurso. Pegue algumas idéias, junte-as a uma coisa e retorne para quem tiver a sorte / a infelicidade de ouvir. O retorno é quase como uma opinião. "isto" é o que você faz com "estes".
precisa

30
Eu acredito que a abordagem de python é bastante simples e elegante: quando você tem que devolver vários valores simplesmente retornar uma tupla: def f(): return (1,2,3)e então você pode usar para-desembalar tupla "split" da tupla: a,b,c = f() #a=1,b=2,c=3. Não há necessidade de criar uma matriz e extrair elementos manualmente, não há necessidade de definir nenhuma nova classe.
Bakuriu

7
Posso informar que o Matlab tem um número variável de valores de retorno. O número de argumentos de saída é determinado pela assinatura de chamada (por exemplo, [a, b] = f()vs. [a, b, c] = f()) e obtido fpor dentro nargout. Eu não sou um grande fã do Matlab, mas isso às vezes é bastante útil.
Gerrit # 03/07/13

5
Eu acho que se a maioria das linguagens de programação é projetada dessa maneira, é discutível. Na história das linguagens de programação, havia algumas linguagens muito populares criadas dessa maneira (como Pascal, C, C ++, Java, VB clássico), mas hoje também existem muitas outras linguagens, conquistando cada vez mais fãs que permitem retorno múltiplo valores.
Doc Brown

Respostas:


58

Algumas linguagens, como Python , suportam vários valores de retorno nativamente, enquanto algumas linguagens como C # os suportam por meio de suas bibliotecas base.

Mas, em geral, mesmo em idiomas que os suportam, vários valores de retorno não são usados ​​com frequência porque são desleixados:

  • É difícil nomear claramente as funções que retornam vários valores .
  • É fácil confundir a ordem dos valores de retorno

    (password, username) = GetUsernameAndPassword()  
    

    (Por esse mesmo motivo, muitas pessoas evitam ter muitos parâmetros para uma função; alguns chegam a dizer que uma função nunca deve ter dois parâmetros do mesmo tipo!)

  • Os idiomas OOP já têm uma alternativa melhor para vários valores de retorno: classes.
    Eles são mais tipificados, mantêm os valores de retorno agrupados como uma unidade lógica e mantêm os nomes de (propriedades de) os valores de retorno consistentes em todos os usos.

O local em que eles são bastante convenientes é em linguagens (como Python), onde vários valores de retorno de uma função podem ser usados ​​como vários parâmetros de entrada para outra. Mas, os casos de uso em que esse é um design melhor do que o uso de uma classe são muito pequenos.


50
É difícil dizer que retornar uma tupla está retornando várias coisas. Está retornando uma tupla . O código que você escreveu apenas o descompacta de maneira limpa, usando um pouco de açúcar sintático.

10
@ LEGO: Eu não vejo distinção - uma tupla é, por definição, vários valores. O que você consideraria "vários valores de retorno", se não isso?
BlueRaja - Danny Pflughoeft

19
É uma distinção muito vaga, mas considere uma Tupla vazia (). Isso é uma coisa ou zero? Pessoalmente, eu diria que é uma coisa. Eu posso atribuir x = ()muito bem, assim como eu posso atribuir x = randomTuple(). No último, se a tupla retornada estiver vazia ou não, ainda posso atribuir a tupla retornada x.

19
... Nunca afirmei que tuplas não pudessem ser usadas para outras coisas. Mas argumentar que "Python não suporta múltiplos valores de retorno, ele suporta tuplas" é simplesmente ser extremamente inútil pedante. Esta ainda é uma resposta correta.
BlueRaja - Danny Pflughoeft

14
Nem tuplas nem classes são "valores múltiplos".
Andres F.

54

Porque funções são construções matemáticas que executam um cálculo e retornam um resultado. De fato, muito do que está "oculto" de poucas linguagens de programação se concentra apenas em uma entrada e uma saída, com várias entradas sendo apenas um invólucro fino em torno da entrada - e quando uma única saída de valor não funciona, usando uma única estrutura coesa (ou tupla, ou Maybe) seja a saída (embora esse valor de retorno "único" seja composto por muitos valores).

Isso não mudou porque os programadores descobriram que os outparâmetros são construções estranhas que são úteis apenas em um conjunto limitado de cenários. Como em muitas outras coisas, o suporte não existe porque a necessidade / demanda não existe.


5
@FrustratedWithFormsDesigner - isso surgiu um pouco em uma pergunta recente . Posso contar com uma mão o número de vezes que desejei várias saídas em 20 anos.
Telastyn

61
Funções em matemática e funções na maioria das linguagens de programação são duas bestas muito diferentes.
Julio

17
@ Tdammers nos primeiros dias, eles eram muito parecidos em pensamentos. Fortran, pascal e similares foram fortemente influenciados pela matemática mais do que pela arquitetura da computação.

9
@ Tdammers - como assim? Quero dizer, para a maioria dos idiomas, tudo se resume ao cálculo lambda no final - uma entrada, uma saída, sem efeitos colaterais. Tudo o resto é uma simulação / hack em cima disso. As funções de programação podem não ser puras, no sentido em que várias entradas podem produzir a mesma saída, mas o espírito está lá.
Telastyn

16
@ SteveEvers: é lamentável que o nome "função" tenha assumido a programação imperativa, em vez do "procedimento" ou "rotina" mais apropriado. Na programação funcional, uma função se parece muito com funções matemáticas.
Tdammers

35

Em matemática, uma função "bem definida" é aquela em que há apenas 1 saída para uma determinada entrada (como uma observação lateral, você pode ter apenas funções de entrada únicas e ainda obter semântica várias entradas usando o curry ).

Para funções com vários valores (por exemplo, raiz quadrada de um número inteiro positivo, por exemplo), é suficiente retornar uma coleção ou sequência de valores.

Para os tipos de funções de que você está falando (funções que retornam vários valores, de tipos diferentes ), eu vejo isso um pouco diferente do que você parece: Eu vejo a necessidade / uso de parâmetros fora como uma solução alternativa para um melhor design ou um estrutura de dados mais útil. Por exemplo, eu preferiria que os *.TryParse(...)métodos retornassem uma Maybe<T>mônada em vez de usar um parâmetro out. Pense neste código em F #:

let s = "1"
match tryParse s with
| Some(i) -> // do whatever with i
| None -> // failed to parse

O suporte ao compilador / IDE / análise é muito bom para essas construções. Isso resolveria grande parte da "necessidade" de parâmetros. Para ser completamente honesto, não consigo pensar em nenhum outro método em que essa não seja a solução.

Para outros cenários - os que não me lembro - basta uma simples tupla.


1
Além disso, eu realmente gostaria de poder escrever em C #: var (value, success) = ParseInt("foo");qual seria o tipo de tempo de compilação verificado porque (int, bool) ParseInt(string s) { }foi declarado. Eu sei que isso pode ser feito com genéricos, mas ainda assim seria uma adição agradável ao idioma.
Careta do Desespero

10
@GrimaceofDespair: o que você realmente quer é destruir a sintaxe, não vários valores de retorno.
Domenic

2
@warren: Sim. Veja aqui e observar que tal solução não seria contínuo: en.wikipedia.org/wiki/Well-definition
Steven Evers

4
A noção matemática de bem-definição não tem nada a ver com o número de saídas que uma função retorna. Isso significa que as saídas sempre serão as mesmas se a entrada for a mesma. A rigor, as funções matemáticas retornam um valor, mas esse valor geralmente é uma tupla. Para um matemático, não há essencialmente nenhuma diferença entre isso e o retorno de vários valores. Argumentos de que funções de programação devem retornar apenas um valor, porque é isso que as funções matemáticas fazem não são muito convincentes.
precisa

1
@MichaelSiler (concordo com o seu comentário) Mas observe que o argumento é reversível: "argumentos de que as funções do programa podem retornar valores múltiplos porque podem retornar um único valor da tupla também não são muito atraentes" :)
Andres F.

23

Além do que já foi dito quando você olha para os paradigmas usados ​​na montagem quando uma função retorna, ela deixa um ponteiro para o objeto retornado em um registro específico. Se eles usassem registros variáveis ​​/ múltiplos, a função de chamada não saberia onde obter os valores retornados se essa função estivesse em uma biblioteca. Portanto, isso tornaria difícil o vínculo com as bibliotecas e, em vez de definir um número arbitrário de ponteiros retornáveis, eles foram incluídos. Idiomas de nível superior não têm a mesma desculpa.


Ah! Ponto de detalhe muito interessante. +1!
Steven Evers

Essa deve ser a resposta aceita. Geralmente, as pessoas pensam nas máquinas de destino quando constroem compiladores. Outra analogia é a razão pela qual temos int, float, char / string etc., porque é isso que é suportado pela máquina de destino. Mesmo que o destino não seja bare metal (por exemplo, jvm), você ainda deseja obter um desempenho decente ao não emular muito.
Imel96

26
... Você pode definir facilmente uma convenção de chamada para retornar vários valores de uma função, quase da mesma maneira que você pode definir uma convenção de chamada para passar vários valores para uma função. Esta não é uma resposta. -1
BlueRaja - Danny Pflughoeft

2
Seria interessante saber se os mecanismos de execução baseados em pilha (JVM, CLR) já consideraram / permitiram vários valores de retorno. Deve ser bastante fácil; o chamador só precisa exibir o número certo de valores, assim como empurra o número certo de argumentos!
Lorenzo Dematté

1
@ David não, o cdecl permite (teoricamente) um número ilimitado de parâmetros (é por isso que as funções varargs são possíveis) . Embora alguns C-compiladores pode limitá-lo a várias dezenas ou centenas de argumentos por função, que eu acho que é ainda mais do que razoável -_-
BlueRaja - Danny Pflughoeft

18

Muitos dos casos de uso em que você usaria vários valores de retorno no passado simplesmente não são mais necessários com os recursos da linguagem moderna. Deseja retornar um código de erro? Lance uma exceção ou retorne um Either<T, Throwable>. Deseja retornar um resultado opcional? Retornar um Option<T>. Deseja retornar um dos vários tipos? Retornar uma Either<T1, T2>união ou uma etiqueta.

E mesmo nos casos em que você realmente precisa retornar vários valores, os idiomas modernos geralmente oferecem suporte a tuplas ou algum tipo de estrutura de dados (lista, matriz, dicionário) ou objetos, bem como alguma forma de ligação destrutiva ou correspondência de padrões, o que compõe o empacotamento seus vários valores em um único valor e, em seguida, destruí-lo novamente em vários valores triviais.

Aqui estão alguns exemplos de idiomas que não oferecem suporte ao retorno de vários valores. Realmente não vejo como a adição de suporte a vários valores de retorno os tornaria significativamente mais expressivos para compensar o custo de um novo recurso de idioma.

Rubi

def foo; return 1, 2, 3 end

one, two, three = foo

one
# => 1

three
# => 3

Pitão

def foo(): return 1, 2, 3

one, two, three = foo()

one
# >>> 1

three
# >>> 3

Scala

def foo = (1, 2, 3)

val (one, two, three) = foo
// => one:   Int = 1
// => two:   Int = 2
// => three: Int = 3

Haskell

let foo = (1, 2, 3)

let (one, two, three) = foo

one
-- > 1

three
-- > 3

Perl6

sub foo { 1, 2, 3 }

my ($one, $two, $three) = foo

$one
# > 1

$three
# > 3

1
Eu acho que um aspecto é que, em algumas linguagens (como Matlab), uma função pode ser flexível em quantos valores ela retorna; veja meu comentário acima . Não gosto de muitos aspectos no Matlab, mas esse é um dos poucos (talvez o único) recurso que sinto falta ao migrar do Matlab para, por exemplo, o Python.
Gerrit

1
Mas e as linguagens dinâmicas como Python ou Ruby? Suponha que eu escreva algo como a sortfunção Matlab : sorted = sort(array)retorna apenas a matriz classificada, enquanto [sorted, indices] = sort(array)retorna ambos. A única maneira de pensar em Python seria passar uma flag ao sortlongo das linhas de sort(array, nout=2)or sort(array, indices=True).
Gerrit

2
@ MikeCellini Acho que não. Uma função pode dizer com quantos argumentos de saída a função é chamada ( [a, b, c] = func(some, thing)) e agir de acordo. Isso é útil, por exemplo, se o cálculo do primeiro argumento de saída for barato, mas o cálculo do segundo for caro. Não estou familiarizado com nenhum outro idioma em que o equivalente ao Matlabs nargoutesteja disponível em tempo de execução.
gerrit

1
@gerrit a solução correta em Python é escrever isto: sorted, _ = sort(array).
Rota das milhas

1
@MilesRout: E a sortfunção pode dizer que não precisa calcular os índices? Isso é legal, eu não sabia disso.
Jörg W Mittag 04/02

12

A verdadeira razão pela qual um único valor de retorno é tão popular são as expressões usadas em muitos idiomas. Em qualquer idioma em que você possa ter uma expressão como x + 1você já está pensando em termos de valores de retorno únicos, porque você avalia uma expressão em sua cabeça dividindo-a em pedaços e decidindo o valor de cada peça. Você olha xe decide que seu valor é 3 (por exemplo), e você olha para 1 e depois olha parax + 1e junte tudo para decidir que o valor do todo é 4. Cada parte sintática da expressão tem um valor, não qualquer outro número de valores; essa é a semântica natural das expressões que todos esperam. Mesmo quando uma função retorna um par de valores, ainda está realmente retornando um valor que está cumprindo dois valores, porque a idéia de uma função que retorna dois valores que não estão de alguma forma agrupados em uma única coleção é muito estranha.

As pessoas não querem lidar com a semântica alternativa que seria necessária para que as funções retornassem mais de um valor. Por exemplo, em uma linguagem baseada em pilha como Forth, você pode ter qualquer número de valores de retorno, porque cada função simplesmente modifica a parte superior da pilha, acionando entradas e pressionando as saídas à vontade. É por isso que Forth não tem o tipo de expressão que as línguas normais têm.

Perl é outra linguagem que às vezes pode agir como se as funções estivessem retornando vários valores, mesmo que seja considerado apenas o retorno de uma lista. A maneira como as listas "interpolam" no Perl nos fornece listas como as (1, foo(), 3)que podem ter 3 elementos, como a maioria das pessoas que não sabem que o Perl esperaria, mas poderiam facilmente ter apenas 2 elementos, 4 elementos ou qualquer número maior de elementos, dependendo foo(). As listas no Perl são achatadas para que uma lista sintática nem sempre tenha a semântica de uma lista; pode ser apenas um pedaço de uma lista maior.

Outra maneira de fazer com que as funções retornem vários valores seria ter uma semântica de expressão alternativa, em que qualquer expressão pode ter vários valores e cada valor representa uma possibilidade. Tome x + 1novamente, mas desta vez imagine que xtem dois valores {3, 4}, então os valores de x + 1seriam {4, 5} e os valores de x + xseriam {6, 8} ou talvez {6, 7, 8} , dependendo se uma avaliação tem permissão para usar vários valores para x. Uma linguagem como essa pode ser implementada usando backtracking, da mesma forma que o Prolog usa para fornecer várias respostas a uma consulta.

Em resumo, uma chamada de função é uma única unidade sintática e uma única unidade sintática tem um valor único na expressão semântica que todos conhecemos e amamos. Qualquer outra semântica o forçaria a maneiras estranhas de fazer coisas, como Perl, Prolog ou Forth.


9

Como sugerido nesta resposta , é uma questão de suporte de hardware, embora a tradição em design de linguagem também tenha um papel.

quando uma função retorna, deixa um ponteiro para o objeto retornado em um registro específico

Das três primeiras línguas, Fortran, Lisp e COBOL, a primeira utilizou um único valor de retorno conforme modelado em matemática. O segundo retornou um número arbitrário de parâmetros da mesma maneira que os recebeu: como uma lista (também se podia argumentar que apenas passava e retornava um único parâmetro: o endereço da lista). O terceiro retorna zero ou um valor.

Essas primeiras línguas influenciaram muito o design das línguas que as seguiram, embora a única que retornasse vários valores, Lisp, nunca tenha ganhado muita popularidade.

Quando o C chegou, apesar de influenciado pelas linguagens anteriores, ele enfatizou o uso eficiente de recursos de hardware, mantendo uma estreita associação entre o que a linguagem C fazia e o código da máquina que a implementava. Alguns de seus recursos mais antigos, como variáveis ​​"auto" vs "register", são resultado dessa filosofia de design.

Deve-se também salientar que a linguagem assembly era amplamente popular até os anos 80, quando finalmente começou a ser eliminada do desenvolvimento convencional. As pessoas que escreviam compiladores e criavam idiomas estavam familiarizadas com o assembly e, na maioria das vezes, mantinham o que funcionava melhor lá.

A maioria das línguas que divergiu dessa norma nunca encontrou muita popularidade e, portanto, nunca teve um papel forte influenciando as decisões dos designers de linguagem (que, é claro, foram inspirados pelo que sabiam).

Então, vamos examinar a linguagem assembly. Vejamos primeiro o 6502 , um microprocessador de 1975 que foi famoso pelos microcomputadores Apple II e VIC-20. Era muito fraco em comparação com o que era usado no mainframe e nos minicomputadores da época, embora poderoso comparado aos primeiros computadores de 20, 30 anos antes, no início das linguagens de programação.

Se você olhar para a descrição técnica, ela possui 5 registros e alguns sinalizadores de um bit. O único registro "completo" foi o contador de programas (PC) - esse registro aponta para a próxima instrução a ser executada. Os outros registram onde o acumulador (A), dois "índices" registram (X e Y) e um ponteiro de pilha (SP).

Chamar uma sub-rotina coloca o PC na memória apontada pelo SP e, em seguida, diminui o SP. Retornar de uma sub-rotina funciona em sentido inverso. É possível empurrar e puxar outros valores na pilha, mas é difícil fazer referência à memória em relação ao SP, portanto, escrever sub-rotinas reentrantes foi difícil. Essa coisa que tomamos como certa, chamar uma sub-rotina a qualquer momento que acharmos conveniente, não era tão comum nessa arquitetura. Freqüentemente, uma "pilha" separada seria criada para que os parâmetros e o endereço de retorno da sub-rotina fossem mantidos separados.

Se você olhar para o processador que inspirou o 6502, o 6800 , ele tinha um registro adicional, o Index Register (IX), tão amplo quanto o SP, que poderia receber o valor do SP.

Na máquina, chamar uma sub-rotina reentrante consistia em empurrar os parâmetros na pilha, empurrar PC, mudar PC para o novo endereço e, em seguida, a sub-rotina empurrava suas variáveis ​​locais na pilha . Como o número de variáveis ​​e parâmetros locais é conhecido, é possível resolvê-los em relação à pilha. Por exemplo, uma função que recebe dois parâmetros e possui duas variáveis ​​locais seria assim:

SP + 8: param 2
SP + 6: param 1
SP + 4: return address
SP + 2: local 2
SP + 0: local 1

Pode ser chamado várias vezes porque todo o espaço temporário está na pilha.

O 8080 , usado no TRS-80 e em vários microcomputadores baseados em CP / M, poderia fazer algo semelhante ao 6800, pressionando SP na pilha e colocando-a em seu registro indireto, HL.

Essa é uma maneira muito comum de implementar as coisas e tem ainda mais suporte em processadores mais modernos, com o Ponteiro Base que facilita o despejo de todas as variáveis ​​locais antes de retornar com facilidade.

O problema é como você retorna alguma coisa ? Os registros do processador não eram muito numerosos desde o início, e era necessário usar alguns deles até para descobrir qual pedaço de memória endereçar. Devolver as coisas na pilha seria complicado: você teria que estourar tudo, salvar o PC, pressionar os parâmetros de retorno (que seriam armazenados no local enquanto isso?), Depois pressionar o PC novamente e retornar.

Então, o que geralmente era feito era reservar um registro para o valor de retorno. O código de chamada sabia que o valor de retorno estaria em um registro específico, que teria que ser preservado até que pudesse ser salvo ou usado.

Vejamos uma linguagem que permite vários valores de retorno: adiante. O que a Forth faz é manter uma pilha de retorno separada (RP) e uma pilha de dados (SP), de modo que tudo que uma função precisava fazer era popular todos os seus parâmetros e deixar os valores de retorno na pilha. Como a pilha de retorno estava separada, ela não atrapalhou.

Como alguém que aprendeu a linguagem assembly e a Forth nos primeiros seis meses de experiência com computadores, vários valores de retorno parecem totalmente normais para mim. Operadores como o Forth's /mod, que retornam a divisão inteira e o resto, parecem óbvios. Por outro lado, eu posso ver facilmente como alguém cuja experiência inicial era C acha esse conceito estranho: vai contra suas expectativas arraigadas sobre o que é uma "função".

Quanto à matemática ... bem, eu estava programando computadores muito antes de chegar a funções nas aulas de matemática. Não é uma seção inteira de CS e linguagens de programação que é influenciado pela matemática, mas, em seguida, novamente, há uma seção inteira que não é.

Portanto, temos uma confluência de fatores em que a matemática influenciou o design de linguagem inicial, onde as restrições de hardware ditavam o que era facilmente implementado e onde as linguagens populares influenciavam a evolução do hardware (a máquina Lisp e os processadores de máquina Forth foram atropelamentos nesse processo).


@gnat O uso de "informar" como em "fornecer a qualidade essencial" foi intencional.
Daniel C. Sobral

sinta-se à vontade para reverter se você se sente fortemente sobre isso; por minha leitura influência se encaixa um pouco melhor aqui: "afeta ... de maneira importante"
mosquito

1
+1 Como ressalta, que a contagem esparsa de registros de CPUs anteriores, em comparação com um conjunto abundante de registradoras de CPUs contemporâneas (também usado em muitas ABIs, por exemplo, x64 abi) pode ser um divisor de águas e os motivos anteriormente convincentes para retornar apenas 1 valor hoje em dia pode ser apenas uma razão histórica.
BitTickler

Não estou convencido de que as micro CPUs iniciais de 8 bits tenham muita influência no design da linguagem e o que é suposto ser necessário em C ou Fortran, chamando convenções em qualquer arquitetura. O Fortran assume que você pode passar argumentos de matriz (essencialmente ponteiros). Portanto, você já tem grandes problemas de implementação do Fortran normal em máquinas como o 6502, devido à falta de modos de endereçamento para o índice de ponteiro +, como discutido em sua resposta e em Por que os compiladores C a Z80 produzem código ruim? em retrocomputação.SE.
Peter Cordes

O Fortran, como C, também pressupõe que você pode passar um número arbitrário de argumentos e ter acesso aleatório a eles e uma quantidade arbitrária de locais, certo? Como você acabou de explicar, você não pode fazer isso facilmente no 6502 porque o endereçamento relativo à pilha não é uma coisa, a menos que você abandone a reentrada. Você pode colocá-los no armazenamento estático. Se você pode passar uma lista arbitrária de argumentos, pode adicionar parâmetros ocultos extras para valores de retorno (por exemplo, além do primeiro) que não cabem nos registros.
Peter Cordes

7

As linguagens funcionais que eu conheço podem retornar vários valores facilmente através do uso de tuplas (em idiomas de tipo dinâmico, você pode até usar listas). Tuplas também são suportadas em outros idiomas:

f :: Int -> (Int, Int)
f x = (x - 1, x + 1)

// Even C++ have tuples - see Boost.Graph for use
std::pair<int, int> f(int x) {
  return std::make_pair(x - 1, x + 1);
}

No exemplo acima, fé uma função retornando 2 ints.

Da mesma forma, ML, Haskell, F #, etc., também podem retornar estruturas de dados (os ponteiros são de nível muito baixo para a maioria dos idiomas). Eu não ouvi falar de uma linguagem GP moderna com essa restrição:

data MyValue = MyValue Int Int

g :: Int -> MyValue
g x = MyValue (x - 1, x + 1)

Finalmente, os outparâmetros podem ser emulados mesmo em linguagens funcionais por IORef. Há várias razões pelas quais não há suporte nativo para variáveis ​​externas na maioria dos idiomas:

  • Semântica pouco clara : A função a seguir imprime 0 ou 1? Conheço idiomas que imprimem 0 e idiomas que imprimem 1. Existem benefícios para os dois (tanto em termos de desempenho quanto em relação ao modelo mental do programador):

    int x;
    
    int f(out int y) {
      x = 0;
      y = 1;
      printf("%d\n", x);
    }
    f(out x);
    
  • Efeitos não localizados : como no exemplo acima, você pode descobrir que pode ter uma cadeia longa e a função mais interna afeta o estado global. Em geral, fica mais difícil argumentar sobre quais são os requisitos da função e se a alteração é legal. Dado que a maioria dos paradigmas modernos tenta localizar os efeitos (encapsulamento no POO) ou eliminar os efeitos colaterais (programação funcional), entra em conflito com esses paradigmas.

  • Ser redundante : se você possui tuplas, possui 99% da funcionalidade dos outparâmetros e 100% de uso idiomático. Se você adicionar ponteiros à mistura, cobrirá os 1% restantes.

Tenho problemas para nomear um idioma que não pôde retornar vários valores usando uma tupla, classe ou outparâmetro (e na maioria dos casos, dois ou mais desses métodos são permitidos).


+1 por mencionar como as linguagens funcionais lidam com isso de maneira elegante e indolor.
Andres F.

1
Tecnicamente, você ainda está retornando um único valor ainda: D (é justo que esse valor único seja trivial para se decompor em vários valores).
Thomas Eding 12/07

1
Eu diria que um parâmetro com semântica real "out" deve se comportar como um compilador temporário que é copiado para o destino quando um método sai normalmente; um com variável semântica "inout" deve se comportar como um compilador temporário, carregado da variável passada na entrada e gravado na saída; um com semântica "ref" deve se comportar como um alias. Os chamados parâmetros "out" do C # são realmente parâmetros "ref" e se comportam como tais.
Supercat

1
A tupla "solução alternativa" também não é gratuita. Bloqueia oportunidades de otimização. Se existisse uma ABI que permitisse que N retornasse valores nos registros da CPU, o compilador poderia realmente otimizar, em vez de criar uma instância de tupla e construí-la.
BitTickler

1
@ BitTickler, nada impede o retorno dos primeiros n campos da estrutura a serem passados ​​pelos registradores, se você controlar a ABI.
Maciej Piechotka

6

Eu acho que é por causa de expressões como (a + b[i]) * c.

Expressões são compostas de valores "singulares". Uma função retornando um valor singular pode, portanto, ser usada diretamente em uma expressão, no lugar de qualquer uma das quatro variáveis ​​mostradas acima. Uma função de saída múltipla é pelo menos um pouco desajeitada em uma expressão.

Pessoalmente, sinto que esta é a coisa que é especial sobre um valor de retorno singular. Você pode contornar isso adicionando sintaxe para especificar quais dos vários valores de retorno você deseja usar em uma expressão, mas é mais desajeitado que a boa e velha notação matemática, que é concisa e familiar a todos.


4

Isso complica um pouco a sintaxe, mas não há uma boa razão no nível de implementação para não permitir. Ao contrário de algumas das outras respostas, o retorno de vários valores, quando disponível, leva a um código mais claro e eficiente. Não posso contar quantas vezes desejei poder retornar um X e um Y, ou um booleano "sucesso" e um valor útil.


3
Você poderia fornecer um exemplo de onde vários retornos fornecem código mais claro e / ou mais eficiente?
Steven Evers

3
Por exemplo, na programação C ++ COM, muitas funções têm um [out]parâmetro, mas praticamente todas retornam um HRESULT(código de erro). Seria bastante prático conseguir um par lá. Em linguagens com bom suporte para tuplas, como Python, isso é usado em muitos códigos que eu já vi.
Felix Dombek

Em alguns idiomas, você retornaria um vetor com as coordenadas X e Y e o retorno de qualquer valor útil contaria como "sucesso", com exceções, possivelmente carregando esse valor útil, sendo usado para falhas.
Doppelgreener

3
Muitas vezes, você acaba codificando informações no valor de retorno de maneiras não óbvias - ou seja; valores negativos são códigos de erro, valores positivos são resultados. Yuk. Ao acessar uma tabela de hash, é sempre complicado indicar se o item foi encontrado e também devolvê-lo.
Ddyer

@SteveEvers A Matlab sortfunção normalmente ordena um array: sorted_array = sort(array). Às vezes eu também preciso dos índices correspondentes: [sorted_array, indices] = sort(array). Às vezes, eu quero que os índices: [~, indices]= sort (array) . The function sort` possam realmente dizer quantos argumentos de saída são necessários; portanto, se trabalho adicional for necessário para 2 saídas em comparação com 1, ele poderá calcular essas saídas apenas se necessário.
Gerrit #

2

Na maioria dos idiomas em que as funções são suportadas, você pode usar uma chamada de função em qualquer lugar onde uma variável desse tipo possa ser usada: -

x = n + sqrt(y);

Se a função retornar mais de um valor, isso não funcionará. Linguagens tipadas dinamicamente, como python, permitem que você faça isso, mas, na maioria dos casos, gera um erro em tempo de execução, a menos que possa descobrir algo sensato a fazer com uma tupla no meio de uma equação.


5
Só não use funções inadequadas. Isso não é diferente do "problema" apresentado pelas funções que não retornam valores ou retornam valores não numéricos.
Ddyer 03/07/2013

3
Nos idiomas que usei que oferecem vários valores de retorno (SciLab, por exemplo), o primeiro valor de retorno é privilegiado e será usado nos casos em que apenas um valor é necessário. Portanto, não há problema real lá.
The Photon

E mesmo quando eles não são, como com desempacotamento de tupla do Python, você pode selecionar qual você quer:foo()[0]
Izkata

Exatamente, se uma função retorna 2 valores, seu tipo de retorno é 2 valores, não um valor único. A linguagem de programação não deve ler sua mente.
Mark E. Haase

1

Eu só quero aproveitar a resposta de Harvey. Originalmente, encontrei essa pergunta em um site de notícias tecnológicas (arstechnica) e encontrei uma explicação incrível: sinto que realmente responde ao cerne dessa questão e está ausente de todas as outras respostas (exceto a de Harvey):

A origem do retorno único das funções está no código da máquina. No nível do código da máquina, uma função pode retornar um valor no registro A (acumulador). Quaisquer outros valores de retorno estarão na pilha.

Uma linguagem que suporte dois valores de retorno o compilará como código de máquina que retorna um e coloca o segundo na pilha. Em outras palavras, o segundo valor de retorno acabaria como um parâmetro out de qualquer maneira.

É como perguntar por que a atribuição é uma variável de cada vez. Você pode ter um idioma que permita a, b = 1, 2 por exemplo. Mas isso acabaria no nível do código da máquina sendo a = 1 seguido por b = 2.

Há alguma justificativa para que as construções da linguagem de programação tenham alguma aparência do que realmente acontecerá quando o código for compilado e em execução.


Se linguagens de baixo nível como C suportassem vários valores de retorno como um recurso de primeira classe, as convenções de chamada C incluiriam vários registros de valor de retorno, da mesma forma que até 6 registros são usados ​​para passar argumentos de função de número inteiro / ponteiro no x86- 64 Sistema V ABI. (De fato, o x86-64 SysV retorna estruturas de até 16 bytes compactadas no par de registradores RDX: RAX. Isso é bom se a estrutura foi carregada e será armazenada, mas custa descompactação extra em comparação com membros separados em regs separados, mesmo se eles são mais estreitas do que 64 bits).
Peter Cordes

A convenção óbvia seria RAX, depois os argumentos de passagem de argumentos. (RDI, RSI, RDX, RCX, R8, R9). Ou na convenção do Windows x64, RCX, RDX, R8, R9. Mas como C não possui vários valores de retorno nativamente, as convenções de chamada ABI / C especificam apenas vários registros de retorno para números inteiros largos e algumas estruturas. Consulte o Procedimento Padrão de Chamada para a Arquitetura ARM®: 2 valores de retorno separados, mas relacionados, para obter um exemplo de uso de um int amplo para obter o compilador para tornar asm eficiente para receber 2 valores de retorno no ARM.
Peter Cordes

-1

Tudo começou com a matemática. FORTRAN, nomeado para "Formula Translation" foi o primeiro compilador. FORTRAN foi e é orientado para física / matemática / engenharia.

COBOL, quase tão antigo, não tinha valor de retorno explícito; Mal tinha sub-rotinas. Desde então, tem sido principalmente inércia.

Go , por exemplo, possui vários valores de retorno e o resultado é mais limpo e menos ambíguo do que usar parâmetros "out". Depois de um pouco de uso, é muito natural e eficiente. Eu recomendo que vários valores de retorno sejam considerados para todos os novos idiomas. Talvez para idiomas antigos também.


4
isto não responde à pergunta feita
gnat

@gnat quanto a mim, responde. OTOH um já tem que precisar de algum fundo de compreendê-lo, e essa pessoa provavelmente não vai fazer a pergunta ...
netch

@Netch não precisa de muito conhecimento para entender que declarações como "FORTRAN ... foi o primeiro compilador" são uma bagunça total. Nem está errado. Assim como o resto desta "resposta" #
gnat

link diz que houve tentativas anteriores de compiladores, mas que "a equipe do FORTRAN liderada por John Backus na IBM é geralmente creditada como tendo introduzido o primeiro compilador completo em 1957". A pergunta foi por que apenas um? Como eu disse. Era principalmente matemática e inércia. A definição matemática do termo "Função" requer exatamente um valor de resultado. Então era uma forma familiar.
Rickys

-2

Provavelmente tem mais a ver com o legado de como as chamadas de função são feitas nas instruções da máquina processadora e com o fato de que todas as linguagens de programação derivam do código da máquina: por exemplo, C -> Montagem -> Máquina.

Como os processadores executam chamadas de função

Os primeiros programas foram escritos em código de máquina e posteriormente montados. Os processadores suportaram chamadas de função, enviando uma cópia de todos os registros atuais para a pilha. Retornar da função exibirá o conjunto salvo de registros da pilha. Normalmente, um registro foi deixado intocado para permitir que a função de retorno retorne um valor.

Agora, por que os processadores foram projetados dessa maneira ... provavelmente era uma questão de restrições de recursos.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.