É uma má idéia retornar diferentes tipos de dados de uma única função em um idioma digitado dinamicamente?


65

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 Stringou 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.


Por que não lançar uma exceção no caso de erro?
TrueWill 27/01

11
@TrueWill abordarei isso na próxima frase.
Daniel Kaplan

Você percebeu que essa é uma prática comum em linguagens mais dinâmicas? É comum em linguagens funcionais , mas as regras são muito explícitas. E eu sei que é comum, especialmente em JavaScript, permitir que os argumentos sejam de tipos diferentes (como essa é essencialmente a única maneira de sobrecarregar as funções), mas raramente vi isso sendo aplicado para retornar valores. O único exemplo em que consigo pensar é no PowerShell, com sua disposição / desempacotamento de matriz principalmente automática em todos os lugares, e as linguagens de script são um caso excepcional.
Aaronaught 28/01

3
Não mencionado, mas há muitos exemplos (mesmo em Java Generics) de funções que usam um tipo de retorno como parâmetro; por exemplo, no Common Lisp, temos (coerce var 'string)rendimentos a stringou da (concatenate 'string this that the-other-thing)mesma forma. Eu escrevi coisas assim ThingLoader.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 um SubclassBque se estende SubclassA...
BRPocock

11
Linguagens de tipo dinâmico são como a colher no filme Matrix . Não tente definir o Spoon como uma string ou um número . Isso seria impossível. Em vez disso ... tente apenas entender a verdade. Que não há colher .
Reactgular

Respostas:


42

Ao contrário de outras respostas, há casos em que o retorno de tipos diferentes é aceitável.

Exemplo 1

sum(2, 3)  int
sum(2.1, 3.7)  float

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:

var sum = function (a, b) {
    return a + b;
};

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.

var user = authProvider.findCurrent();
// user is now:
// {
//     provider: 'Facebook',
//     name: {
//         firstName: 'Hello',
//         secondName: 'World',
//     },
//     email: 'hello.world@example.com',
//     age: 27
// }

Outros teriam o mínimo, seria um endereço de e-mail ou o pseudônimo.

var user = authProvider.findCurrent();
// user is now:
// {
//     provider: 'Google',
//     email: 'hello.world@example.com'
// }

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:

var user = authProvider.findCurrent();
if (user.age || 0 >= 16) {
    // The person can stand mature language.
    allowShowingContent();
} else if (user.age) {
    // OpenID/OAuth gave the age, but the person appears too young to see the content.
    showParentalAdvisoryRequestedMessage();
} else {
    // OpenID/OAuth won't tell the age of the person. Ask the user himself.
    askForAge();
}

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:

var age;
if (['Facebook', 'Yahoo', 'Blogger', 'LiveJournal'].contains(user.provider)) {
    age = user.age;
}

5
"Em linguagens estaticamente tipadas, isso envolve sobrecargas" Eu acho que você quis dizer "em algumas linguagens estaticamente tipificadas" :) Boas linguagens tipicamente estaticamente não exigem sobrecarga para algo como o seu sumexemplo.
Andrés F.

17
Para o seu exemplo específico, considere Haskell: 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.
Andrés F.

3
@MainMa No Scala, Iterator[A]possui o método def sum[B >: A](implicit num: Numeric[B]): B, que novamente permite somar qualquer tipo de número e é verificado em tempo de compilação.
Petr Pudlák 28/01

3
@Bakuriu É claro que você pode escrever essa função, embora seja necessário sobrecarregar as +strings (implementando Num, 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.

2
Não estou particularmente impressionado com o seu segundo exemplo. Esses dois objetos são do mesmo "tipo" conceitual, mas em alguns casos determinados campos não são definidos ou preenchidos. Você pode representar isso muito bem, mesmo em uma linguagem de tipo estaticamente com nullvalores.
Aaronaught

31

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 sumfunçã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).


40
Meus dois centavos: eu odeio quando isso acontece. Eu vi bibliotecas JS que retornam nulas quando não há resultados, um objeto em um resultado e uma matriz em dois ou mais resultados. Portanto, em vez de apenas percorrer uma matriz, você é forçado a determinar se é nulo, e se não for, se é uma matriz e, se for, faça uma coisa, caso contrário, faça outra coisa. Especialmente porque, no sentido normal, qualquer desenvolvedor sensato apenas adiciona o Objeto a uma Matriz e recicla a lógica do programa manipulando uma matriz.
Phyrfox

10
@ Izkata: Eu não acho que NaNseja "contraponto". NaN é realmente uma entrada válida para qualquer cálculo de ponto flutuante. Você pode fazer qualquer coisa NaNcom 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 .
Aaronaught 28/01

5
@Aaronaught Por outro lado, NaNcomo 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 se NaNcair em um loop condicional.
precisa saber é o seguinte

2
@ Izkata: NaN e null têm comportamentos totalmente diferentes. Uma referência nula explodirá imediatamente após o acesso, um NaN se propaga. Por que o último acontece é óbvio, ele permite que você escreva expressões matemáticas sem precisar verificar o resultado de cada subexpressão. Pessoalmente, vejo o nulo como muito mais prejudicial, porque o NaN representa um conceito numérico adequado do qual você não pode se safar e, matematicamente, a propagação é o comportamento correto.
Phoshi

4
Não sei por que isso se transformou em um argumento "nulo versus tudo o resto". Eu estava apenas apontando que o NaNvalor 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?
Aaronaught 28/01

26

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:

def load_file(file): 
    if something: 
       return ['a ', 'list', 'of', 'strings'] 
    return open(file, 'r')

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 listou tuple( tupleé uma lista imutável em python).

Mesmo fazendo formalmente:

def do_something():
    if ...: 
        return None
    return something_else

ou:

function do_something(){
   if (...) return null; 
   return sth;
}

retorna tipos diferentes, pois python Nonee Javascript nullsã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.


4
Boa resposta. Vale ressaltar que o equivalente Java / estaticamente digitado é que uma função retorne uma interface em vez de um tipo concreto específico, com a interface representando a API / abstração.
Mikera

11
Eu acho que você está detalhando, no Python, uma lista e uma tupla podem ser de "tipos" diferentes, mas são do mesmo "tipo de pato", ou seja, suportam o mesmo conjunto de operações. E casos como esse são exatamente o motivo pelo qual a digitação com patos foi introduzida. Você também pode fazer x = getInt () longo em Java.
precisa saber é

“Retornando tipos diferentes” significa “retornando objetos com APIs diferentes”. (Algumas linguagens tornam a maneira como o objeto é construído uma parte fundamental da API, outras não; é uma questão de quão expressivo é o sistema de tipos.) No exemplo de lista / arquivo, do_fileretorna uma iterável de strings de qualquer maneira.
Gilles 'SO- stop be evil'

11
Nesse exemplo de Python, você também pode chamar iter()a lista e o arquivo para garantir que o resultado possa ser usado apenas como um iterador nos dois casos.
RemcoGerlich 28/01

11
O retorno 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.
Matt

9

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 objectou retorna null.

Mas é um pequeno passo para dizer "Vou usar isso para criar um padrão de fábrica " e retornar um fooou outro barou bazdependendo do humor da função. Depurar isso se tornará um pesadelo quando o chamador espera, foomas recebeu bar.

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.


6

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 objectparece 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 dynamicpalavra - chave em C #. Rob Conery conseguiu escrever com êxito um Mapeador Objeto-Relacional em 400 linhas de código usando a dynamicpalavra - chave.

Obviamente, tudo o que dynamicrealmente faz é agrupar uma objectvariável com alguma segurança do tipo tempo de execução.


4

É 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.


4

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.

total = sum_of_array([20, 30, 'q', 50])
if (type_of(total) == Boolean) {
  display_error(...)
} else {
  record_number(total)
}

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:

total = sum_of_array([20, 30, 'q', 50])
display_number(total)

Como a primeira linha retorna com êxito, presumo que, totalna 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:

total = sum_of_array([20, 30, 40, 50])
if (type_of(total) == String) {
  write_message(total)
} else {
  record_number(total)
}

Você pode argumentar que seria útil para a função retornar um número inteiro ou um número flutuante, por exemplo:

sum_of_array(20, 30, 40) -> int
sum_of_array(23.45, 45.67, 67.789044) -> float

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.


Desculpe, eu desviei você com meu pobre exemplo. Esqueça que eu mencionei isso em primeiro lugar. Seu primeiro parágrafo está no ponto. Eu também gosto do seu segundo exemplo.
Daniel Kaplan

4

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 nullou 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:

def transferMoney(amount: Decimal): Either[String, Decimal]

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.


O método em Java retornando exceção soa estranho. " Retornar uma exceção " ... hmm
gnat

3
Uma exceção é um resultado possível de um método em Java. Na verdade, esqueci um quarto tipo: Unit(não é necessário retornar um método). É verdade que você não usa a returnpalavra - chave, mas é um resultado que volta do método. Com exceções verificadas, ele é mencionado explicitamente na assinatura do tipo.
Jörg W Mittag

Eu vejo. Seu argumento parece ter alguns méritos, mas a maneira como ele é apresentado não o torna atraente ... é exatamente o oposto #
gnat

4
Em muitos idiomas, as condições de erro são modeladas como uma sub-rotina retornando um tipo de resultado ou um tipo de erro. Exceções são uma ideia semelhante, exceto que também são construções de fluxo de controle (iguais em poder expressivo a GOTO, na verdade).
Jörg W Mittag

4

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.


3

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:

function ensure_fields($consideration)
{
        $args = func_get_args();
        foreach ( $args as $a ) {
                if ( !is_string($a) ) {
                        return NULL;
                }
                if ( !isset($consideration[$a]) || $consideration[$a]=='' ) {
                        return FALSE;
                }
        }

        return TRUE;
}

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:

strpos('Hello', 'e');  // Returns INT(1)
strpos('Hello', 'q');  // Returns BOOL(FALSE)

4
E é por isso que eu odeio PHP. Bem, uma das razões, pelo menos.
Aaronaught 28/01

11
Um voto negativo porque alguém não gosta da linguagem em que eu desenvolvo profissionalmente?!? Espere até eles descobrirem que sou judeu! :)
dotancohen

3
Nem sempre assuma que as pessoas que comentam e as que votam negativamente são as mesmas.
Aaronaught 28/01

11
Prefiro ter uma exceção em vez de 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 tudo isset(), o que é um fardo demais.
Script #

11
Além disso, se você retornar um nullerro, 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.
Script #

3

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

public static Boolean getBoolean(String property){
    return (Boolean) properties.getProperty(property);
}
public static Integer getInt(String property){
    return (Integer) properties.getProperty(property);
}

Java 1.5 ou superior

public static <T> getValue(String property, Class<T> clazz) throws WhateverCheckedException{
    return clazz.getConstructor(String.class).newInstance(properties.getProperty(property));
}
//the call will be
Boolean b = getValue("useProxy",Boolean.class);
Integer b = getValue("proxyPort",Integer.class);

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.


Outro voto negativo sem explicação! Obrigado comunidade SE!
thermz

2
Tenha uma simpatia +1. Tente abrir sua resposta com uma resposta mais clara e direta e evite comentar sobre diferentes respostas. (Eu te daria outra +1, desde que eu concordo com você, mas eu só tenho um para dar.)
DougM

3
Os genéricos não existem em linguagens de tipo dinâmico (que eu saiba). Não haveria razão para eles. A maioria dos genéricos é um caso especial, em que o "contêiner" é mais interessante do que o conteúdo (é por isso que algumas linguagens, como Java, na verdade usam apagamento de tipo). O tipo genérico é realmente um tipo próprio. Métodos genéricos , sem um tipo genérico real, como no seu exemplo aqui, quase sempre são apenas açúcar de sintaxe para uma semântica de atribuição e conversão. Não é um exemplo particularmente convincente do que a pergunta realmente está perguntando.
Aaronaught 28/01

11
Tento ser um pouco mais sintético: "Como mesmo os genéricos em uma linguagem de tipo estaticamente foram introduzidos para resolver esse problema, é claramente uma dica que escrever uma função que retorne tipos diferentes em uma linguagem dinâmica não é uma má idéia". Melhor agora? Verifique a resposta de Robert Harvey ou o comentário do BRPocock e você perceberá que o exemplo de genéricos está relacionado a essa pergunta
thermz

11
Não se sinta mal, @thermz. Os votos negativos costumam dizer mais sobre o leitor do que sobre o escritor.
Karl Bielefeldt

2

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.


1

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:

my @now = localtime();

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):

my $now = localtime();

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.


Você pode querer mencionar o modo específico ele faz isso em perl - wantarray ... e talvez um link para alguma discussão se é bom ou ruim - Uso de wantarray considerada prejudicial à PerlMonks

Por coincidência, estou lidando com isso com uma API Java Rest que estou construindo. Tornei possível que um recurso retornasse XML ou JSON. O menor tipo de denominador comum nesse caso é a 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, Stringnão posso gerar a documentação automaticamente. Em retrospecto / moral da história, gostaria de retornar um tipo por método.
Daniel Kaplan

Estritamente, isso equivale a uma forma de sobrecarga. Em outras palavras, existem várias funções federadas e, dependendo dos detalhes da chamada, uma ou outra versão é escolhida.
Ian

1

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.

function ThingyWrapper(thingy){ //a function constructor (class-like thingy)

    //thingy is effectively private and persistent for ThingyWrapper instances

    if(typeof thingy === 'array'){
        this.alertItems = function(){
            thingy.forEach(function(el){ alert(el); });
        }
    }
    else {
        this.alertItems = function(){
            for(var x in thingy){ alert(thingy[x]); }
        }
    }
}

function gimmeThingy(){
    var
        coinToss = Math.round( Math.random() ),//gives me 0 or 1
        arrayThingy = [1,2,3],
        objectThingy = { item1:1, item2:2, item3:3 }
    ;

    //0 dynamically evaluates to false in JS
    return new ThingyWrapper( coinToss ? arrayThingy : objectThingy );
}

gimmeThingy().alertItems(); //should be same every time except order of numbers - maybe

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ê.

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.