Minha linguagem principal é digitada estaticamente (Java). Em Java, você deve retornar um único tipo de cada método. Por exemplo, você não pode ter um método que retorne condicionalmente a String
ou retorne condicionalmente a Integer
. Mas em JavaScript, por exemplo, isso é muito possível.
Em uma linguagem de tipo estaticamente, entendo por que essa é uma má idéia. Se todo método retornado Object
(o pai comum de onde todas as classes herdam), você e o compilador não têm idéia do que estão lidando. Você terá que descobrir todos os seus erros em tempo de execução.
Mas em uma linguagem de tipo dinâmico, pode até não haver um compilador. Em uma linguagem de tipo dinâmico, não é óbvio para mim por que uma função que retorna vários tipos é uma má idéia. Minha formação em linguagens estáticas me impede de escrever essas funções, mas receio que eu esteja pensando sobre um recurso que poderia tornar meu código mais limpo de maneiras que não consigo ver.
Edit : Vou remover meu exemplo (até que eu possa pensar em um melhor). Eu acho que está orientando as pessoas a responderem a um ponto que não estou tentando fazer.
fonte
(coerce var 'string)
rendimentos astring
ou da(concatenate 'string this that the-other-thing)
mesma forma. Eu escrevi coisas assimThingLoader.getThingById (Class<extends FindableThing> klass, long id)
também. E, não, eu só poderia retornar algo que subclasses que você pediu:loader.getThingById (SubclassA.class, 14)
pode retornar umSubclassB
que se estendeSubclassA
...Respostas:
Ao contrário de outras respostas, há casos em que o retorno de tipos diferentes é aceitável.
Exemplo 1
Em algumas linguagens estaticamente tipadas, isso envolve sobrecargas, portanto, podemos considerar que existem vários métodos, cada um retornando o tipo fixo predefinido. Em linguagens dinâmicas, pode ser a mesma função, implementada como:
Mesma função, diferentes tipos de valor de retorno.
Exemplo 2
Imagine que você obtenha uma resposta de um componente OpenID / OAuth. Alguns provedores OpenID / OAuth podem conter mais informações, como a idade da pessoa.
Outros teriam o mínimo, seria um endereço de e-mail ou o pseudônimo.
Novamente, mesma função, resultados diferentes.
Aqui, o benefício de retornar tipos diferentes é especialmente importante em um contexto em que você não se importa com tipos e interfaces, mas com quais objetos realmente contêm. Por exemplo, vamos imaginar que um site contenha linguagem madura. Então o
findCurrent()
pode ser usado assim:Refatorar isso em código, em que todo provedor terá sua própria função, que retornará um tipo fixo e bem definido, além de degradar a base de código e causar duplicação de código, mas também não trará nenhum benefício. Pode-se acabar fazendo horrores como:
fonte
sum
exemplo.sum :: Num a => [a] -> a
. Você pode somar uma lista de qualquer coisa que seja um número. Ao contrário do javascript, se você tentar somar algo que não é um número, o erro será detectado no momento da compilação.Iterator[A]
possui o métododef sum[B >: A](implicit num: Numeric[B]): B
, que novamente permite somar qualquer tipo de número e é verificado em tempo de compilação.+
strings (implementandoNum
, que é uma péssima idéia em termos de design, mas legal) ou inventar um operador / função diferente sobrecarregado por números inteiros e strings respectivamente. Haskell possui polimorfismo ad-hoc por meio de classes de tipo. Uma lista contendo números inteiros e seqüências de caracteres é muito mais difícil (possivelmente impossível sem extensões de idioma), mas uma questão bastante diferente.null
valores.Em geral, é uma má idéia pelas mesmas razões que o equivalente moral em uma linguagem de tipo estaticamente é uma má idéia: você não tem idéia de qual tipo concreto é retornado; portanto, não tem idéia do que pode fazer com o resultado (além do método poucas coisas que podem ser feitas com qualquer valor). Em um sistema de tipo estático, você tem anotações verificadas pelo compilador de tipos de retorno e similares, mas em uma linguagem dinâmica o mesmo conhecimento ainda existe - é apenas informal e armazenado no cérebro e na documentação, e não no código-fonte.
Em muitos casos, no entanto, há rima e motivo para o retorno do tipo e o efeito é semelhante à sobrecarga ou polimorfismo paramétrico em sistemas do tipo estático. Em outras palavras, o tipo de resultado é previsível, mas não é tão simples de expressar.
Mas observe que pode haver outras razões pelas quais uma função específica foi mal projetada: Por exemplo, uma
sum
função retornando false em entradas inválidas é uma má idéia, principalmente porque esse valor de retorno é inútil e propenso a erros (0 <-> falsa confusão).fonte
NaN
seja "contraponto". NaN é realmente uma entrada válida para qualquer cálculo de ponto flutuante. Você pode fazer qualquer coisaNaN
com um número real. É verdade que o resultado final do referido cálculo pode não ser muito útil para você, mas não gera erros estranhos de tempo de execução e você não precisa verificar o tempo todo - basta verificar uma vez no final de um série de cálculos. NaN não é um tipo diferente , é apenas um valor especial , mais ou menos como um Objeto Nulo .NaN
como o objeto nulo, tende a se esconder quando ocorrem erros, apenas para que eles apareçam mais tarde. Por exemplo, é provável que o seu programa exploda seNaN
cair em um loop condicional.NaN
valor não é (a) realmente um tipo de retorno diferente e (b) possui semântica de ponto flutuante bem definida e, portanto, realmente não se qualifica como um análogo a um valor de retorno de tipo variável. Na verdade, não é tão diferente de zero na aritmética inteira; se um zero "acidentalmente" cair nos seus cálculos, é provável que você acabe com zero como resultado ou com um erro de divisão por zero. Os valores "infinito" definidos pelo ponto flutuante do IEEE também são ruins?Em linguagens dinâmicas, você não deve perguntar se retorna tipos diferentes, mas objetos com APIs diferentes . Como a maioria das linguagens dinâmicas realmente não se importa com os tipos, use várias versões da digitação com patos .
Ao retornar tipos diferentes, faz sentido
Por exemplo, este método faz sentido:
Como o arquivo e uma lista de strings são iteráveis (em Python) que retornam strings. Tipos muito diferentes, a mesma API (a menos que alguém tente chamar métodos de arquivo em uma lista, mas isso seja uma história diferente).
Você pode retornar condicionalmente
list
outuple
(tuple
é uma lista imutável em python).Mesmo fazendo formalmente:
ou:
retorna tipos diferentes, pois python
None
e Javascriptnull
são tipos por si mesmos.Todos esses casos de uso teriam sua contrapartida em linguagens estáticas, a função retornaria apenas uma interface adequada.
Ao retornar objetos com diferentes API condicionalmente, é uma boa ideia
Quanto ao fato de retornar APIs diferentes ser uma boa idéia, na maioria dos casos, a IMO não faz sentido. O único exemplo sensato que vem à mente é algo parecido com o que o @MainMa disse : quando sua API pode fornecer uma quantidade variável de detalhes, pode fazer sentido retornar mais detalhes quando disponível.
fonte
do_file
retorna uma iterável de strings de qualquer maneira.iter()
a lista e o arquivo para garantir que o resultado possa ser usado apenas como um iterador nos dois casos.None or something
é determinante para a otimização do desempenho feita por ferramentas como PyPy, Numba, Pyston e outras. Este é um dos Python-ism que tornam o python não tão rápido.Sua pergunta me faz querer chorar um pouco. Não pelo exemplo de uso que você forneceu, mas porque alguém levará inconscientemente essa abordagem muito longe. Está a um pequeno passo do código ridiculamente insustentável.
O caso de uso da condição de erro meio que faz sentido, e o padrão nulo (tudo deve ser um padrão) nas linguagens estaticamente tipadas faz o mesmo tipo de coisa. Sua chamada de função retorna um
object
ou retornanull
.Mas é um pequeno passo para dizer "Vou usar isso para criar um padrão de fábrica " e retornar um
foo
ou outrobar
oubaz
dependendo do humor da função. Depurar isso se tornará um pesadelo quando o chamador espera,foo
mas recebeubar
.Então eu não acho que você esteja com a mente fechada. Você está sendo cauteloso ao usar os recursos do idioma.
Divulgação: minha formação é em linguagens estaticamente tipificadas, e geralmente trabalhei em equipes maiores e diversas, nas quais a necessidade de código de manutenção era bastante alta. Portanto, minha perspectiva provavelmente também está distorcida.
fonte
O uso de genéricos em Java permite retornar um tipo diferente, mantendo a segurança do tipo estático. Você simplesmente especifica o tipo que deseja retornar no parâmetro de tipo genérico da chamada de função.
Se você pode usar uma abordagem semelhante em Javascript é, obviamente, uma questão em aberto. Como o Javascript é uma linguagem de tipo dinâmico, retornar uma
object
parece ser a escolha óbvia.Se você deseja saber onde um cenário de retorno dinâmico pode funcionar quando você está acostumado a trabalhar em linguagem de tipo estaticamente, considere examinar a
dynamic
palavra - chave em C #. Rob Conery conseguiu escrever com êxito um Mapeador Objeto-Relacional em 400 linhas de código usando adynamic
palavra - chave.Obviamente, tudo o que
dynamic
realmente faz é agrupar umaobject
variável com alguma segurança do tipo tempo de execução.fonte
É uma má idéia, eu acho, retornar tipos diferentes condicionalmente. Uma das maneiras pelas quais isso ocorre com frequência é se a função pode retornar um ou mais valores. Se apenas um valor precisar ser retornado, pode parecer razoável apenas devolvê-lo, em vez de empacotá-lo em uma matriz, para evitar a necessidade de descompactá-lo na função de chamada. No entanto, isso (e muitas outras instâncias) impõe ao chamador a obrigação de distinguir e manipular os dois tipos. A função será mais fácil de raciocinar se está sempre retornando o mesmo tipo.
fonte
A "má prática" existe independentemente de seu idioma ser digitado estaticamente. A linguagem estática faz mais para afastá-lo dessas práticas, e você pode encontrar mais usuários que reclamam de "más práticas" em uma linguagem estática porque é uma linguagem mais formal. No entanto, os problemas subjacentes existem em uma linguagem dinâmica e você pode determinar se eles são justificados.
Aqui está a parte questionável do que você propõe. Se não souber que tipo é retornado, não posso usar o valor de retorno imediatamente. Eu tenho que "descobrir" algo sobre isso.
Geralmente, esse tipo de mudança de código é simplesmente uma prática ruim. Isso torna o código mais difícil de ler. Neste exemplo, você vê por que lançar e capturar casos de exceção é popular. Em outras palavras: se sua função não pode fazer o que diz, não deve retornar com êxito . Se eu chamar sua função, quero fazer o seguinte:
Como a primeira linha retorna com êxito, presumo que,
total
na verdade, contenha a soma da matriz. Se não retornar com sucesso, pularemos para outra página do meu programa.Vamos usar outro exemplo que não é apenas sobre a propagação de erros. Talvez sum_of_array tente ser inteligente e retornar uma sequência legível por humanos em alguns casos, como "Essa é a minha combinação de armários!" se e somente se a matriz for [11,7,19]. Estou tendo problemas para pensar em um bom exemplo. De qualquer forma, o mesmo problema se aplica. Você precisa inspecionar o valor de retorno antes de poder fazer qualquer coisa com ele:
Você pode argumentar que seria útil para a função retornar um número inteiro ou um número flutuante, por exemplo:
Mas esses resultados não são de tipos diferentes, na sua opinião. Você os tratará como números, e é só com isso que você se importa. Então sum_of_array retorna o tipo de número. É disso que se trata o polimorfismo.
Portanto, existem algumas práticas que você pode estar violando se sua função puder retornar vários tipos. Conhecê-los o ajudará a determinar se sua função específica deve retornar vários tipos de qualquer maneira.
fonte
Na verdade, não é muito incomum retornar tipos diferentes, mesmo em um idioma estaticamente tipado. É por isso que temos tipos de união, por exemplo.
De fato, os métodos em Java quase sempre retornam um dos quatro tipos: algum tipo de objeto ou
null
ou uma exceção ou eles nunca retornam.Em muitos idiomas, as condições de erro são modeladas como sub-rotinas retornando um tipo de resultado ou um tipo de erro. Por exemplo, em Scala:
Este é um exemplo estúpido, é claro. O tipo de retorno significa "retornar uma string ou um decimal". Por convenção, o tipo esquerdo é o tipo de erro (nesse caso, uma sequência com a mensagem de erro) e o tipo certo é o tipo de resultado.
Isso é semelhante às exceções, exceto pelo fato de que as exceções também são construções de fluxo de controle. Eles são, de fato, equivalentes em poder expressivo a
GOTO
.fonte
Unit
(não é necessário retornar um método). É verdade que você não usa areturn
palavra - chave, mas é um resultado que volta do método. Com exceções verificadas, ele é mencionado explicitamente na assinatura do tipo.GOTO
, na verdade).Nenhuma resposta ainda mencionou os princípios do SOLID. Em particular, você deve seguir o princípio de substituição de Liskov, de que qualquer classe que receba um tipo diferente do esperado ainda possa trabalhar com o que receber, sem fazer nada para testar qual tipo é retornado.
Portanto, se você lançar algumas propriedades extras em um objeto ou envolver uma função retornada com algum tipo de decorador que ainda cumpre o que a função original pretendia realizar, você será bom, desde que nenhum código que chame sua função dependa disso. comportamento, em qualquer caminho de código.
Em vez de retornar uma string ou um número inteiro, um exemplo melhor pode ser que ele retorne um sistema de aspersão ou um gato. Isso é bom se todo o código de chamada for chamado functionInQuestion.hiss (). Efetivamente, você possui uma interface implícita que o código de chamada está esperando, e uma linguagem digitada dinamicamente não forçará você a tornar a interface explícita.
Infelizmente, seus colegas de trabalho provavelmente o farão, então você provavelmente terá que fazer o mesmo trabalho na documentação, exceto que não há uma maneira universalmente aceita, concisa e analisável por máquina para fazer isso - como existe quando você define uma interface em um idioma que os possui.
fonte
O único lugar em que me vejo enviando tipos diferentes é para entrada inválida ou "exceções do homem pobre", onde a condição "excepcional" não é muito excepcional. Por exemplo, do meu repositório de utilitários PHP funciona este exemplo resumido:
A função retorna nominalmente um BOOLEAN, no entanto, retorna NULL na entrada inválida. Note que desde o PHP 5.3, todas as funções internas do PHP também se comportam dessa maneira . Além disso, algumas funções internas do PHP retornam FALSE ou INT na entrada nominal, consulte:
fonte
null
, porque (a) falha alto , por isso é mais provável que seja corrigida na fase de desenvolvimento / teste, (b) é fácil de manusear, porque posso capturar todas as exceções raras / inesperadas uma vez por todo o meu aplicativo (no meu controlador frontal) e apenas registro-o (geralmente envio e-mails para a equipe de desenvolvimento), para que possa ser corrigido mais tarde. Na verdade, eu odeio que a biblioteca PHP padrão use principalmente a abordagem "return null" - ela realmente torna o código PHP mais propenso a erros, a menos que você verifique tudoisset()
, o que é um fardo demais.null
erro, por favor, deixe isso claro em um bloco de documentos (por exemplo@return boolean|null
), portanto, se eu encontrar seu código algum dia, não precisarei verificar a função / corpo do método.Não acho que seja uma má ideia! Em contraste com essa opinião mais comum, e como já apontado por Robert Harvey, linguagens de tipo estaticamente como Java introduziram o Generics exatamente para situações como a que você está perguntando. Na verdade, o Java tenta manter (sempre que possível) a segurança de tipo no tempo de compilação e, às vezes, os genéricos evitam a duplicação de código, por que? Porque você pode escrever o mesmo método ou a mesma classe que manipula / retorna tipos diferentes . Vou fazer apenas um exemplo muito breve para mostrar essa ideia:
Java 1.4
Java 1.5 ou superior
Em uma linguagem de tipo dinâmico, como você não tem segurança de digitação no tempo de compilação, você é absolutamente livre para escrever o mesmo código que funciona para muitos tipos. Como mesmo em uma linguagem de tipo estaticamente Genéricos foram introduzidos para resolver esse problema, é claramente uma dica de que escrever uma função que retorna tipos diferentes em uma linguagem dinâmica não é uma má idéia.
fonte
Desenvolver software é basicamente uma arte e um ofício de gerenciar a complexidade. Você tenta restringir o sistema nos pontos que pode pagar e limitar as opções em outros pontos. A interface da função é um contrato que ajuda a gerenciar a complexidade do código, limitando o conhecimento necessário para trabalhar com qualquer parte do código. Ao retornar tipos diferentes, você expande significativamente a interface da função adicionando a ela todas as interfaces de tipos diferentes que você retorna E adicionando regras não óbvias sobre a interface retornada.
fonte
O Perl usa muito isso porque o que uma função faz depende do seu contexto . Por exemplo, uma função pode retornar uma matriz se usada no contexto da lista, ou o comprimento da matriz se usado em um local em que um valor escalar é esperado. Desde o tutorial que é o primeiro hit de "contexto perl" , se você fizer isso:
Então @now é uma variável de matriz (é o que significa @) e conterá uma matriz como (40, 51, 20, 9, 0, 109, 5, 8, 0).
Se, em vez disso, você chamar a função de uma maneira em que seu resultado terá que ser escalar, com ($ variable are scalars):
então ele faz algo completamente diferente: $ now será algo como "sexta-feira, 9 de janeiro, 20:51:40 2009".
Outro exemplo em que posso pensar é na implementação de APIs REST, em que o formato do que é retornado depende do que o cliente deseja. Por exemplo, HTML ou JSON ou XML. Embora tecnicamente sejam todos fluxos de bytes, a ideia é semelhante.
fonte
String
. Agora, as pessoas estão pedindo documentação sobre o tipo de retorno. As ferramentas java podem gerá-lo automaticamente se eu retornar uma classe, mas como eu escolhi,String
não posso gerar a documentação automaticamente. Em retrospecto / moral da história, gostaria de retornar um tipo por método.Na terra dinâmica, trata-se de digitar patos. A coisa mais responsável exposta / voltada ao público a fazer é agrupar tipos potencialmente diferentes em um wrapper que lhes dê a mesma interface.
Ocasionalmente, pode fazer sentido criar tipos diferentes, sem nenhum invólucro genérico, mas sinceramente nos últimos sete anos escrevendo JS, não é algo que eu achei razoável ou conveniente fazer com muita frequência. Principalmente isso é algo que eu faria no contexto de ambientes fechados, como o interior de um objeto, onde as coisas estão sendo montadas. Mas não é algo que eu tenha feito com bastante frequência que quaisquer exemplos me lembrem.
Principalmente, eu recomendaria que você parasse de pensar sobre os tipos por completo. Você lida com tipos quando necessário em um idioma dinâmico. Não mais. É esse o ponto. Não verifique o tipo de cada argumento. Isso é algo que eu só ficaria tentado a fazer em um ambiente onde o mesmo método poderia dar resultados inconsistentes de maneiras não óbvias (então, definitivamente, não faça algo assim). Mas não é do tipo que importa, é como funciona o que quer que você me dê.
fonte