¿Es una mala idea devolver diferentes tipos de datos de una sola función en un lenguaje de tipo dinámico?


65

Mi idioma principal es de tipo estático (Java). En Java, debe devolver un solo tipo de cada método. Por ejemplo, no puede tener un método que devuelva condicionalmente a Stringo devuelva condicionalmente un Integer. Pero en JavaScript, por ejemplo, esto es muy posible.

En un lenguaje escrito estáticamente, entiendo por qué es una mala idea. Si todos los métodos devueltos Object(el padre común del que heredan todas las clases), usted y el compilador no tienen idea de lo que están tratando. Tendrás que descubrir todos tus errores en tiempo de ejecución.

Pero en un lenguaje de tipo dinámico, puede que ni siquiera haya un compilador. En un lenguaje de tipo dinámico, no es obvio para mí por qué una función que devuelve varios tipos es una mala idea. Mi experiencia en lenguajes estáticos hace que evite escribir tales funciones, pero me temo que estoy siendo una mente cerrada acerca de una característica que podría hacer que mi código sea más limpio de una manera que no puedo ver.


Editar : voy a eliminar mi ejemplo (hasta que se me ocurra uno mejor). Creo que está guiando a la gente a responder a un punto que no estoy tratando de hacer.


¿Por qué no lanzar una excepción en el caso de error?
TrueWill

1
@TrueWill abordaré eso en la siguiente oración.
Daniel Kaplan

¿Has notado que esta es una práctica común en lenguajes más dinámicos? Es común en lenguajes funcionales , pero allí las reglas se hacen muy explícitas. Y sé que es común, especialmente en JavaScript, permitir que los argumentos sean de diferentes tipos (ya que esa es esencialmente la única forma de sobrecargar las funciones), pero rara vez he visto que esto se aplique a los valores de retorno. El único ejemplo en el que puedo pensar es PowerShell con su envoltura / desenvolvimiento de matriz en su mayoría automática en todas partes, y los lenguajes de script son un caso excepcional.
Aaronaught

3
No se menciona, pero hay muchos ejemplos (incluso en Java Generics) de funciones que toman un tipo de retorno como parámetro; por ejemplo, en Common Lisp que tiene (coerce var 'string)rendimientos de una stringo (concatenate 'string this that the-other-thing)de igual manera. También he escrito cosas ThingLoader.getThingById (Class<extends FindableThing> klass, long id)así. Y, allí, podría devolverle algo que subclasifique lo que solicitó: loader.getThingById (SubclassA.class, 14)podría devolver un SubclassBSubclassA
mensaje

1
Los idiomas escritos dinámicamente son como la Cuchara en la película The Matrix . No intente definir la cuchara como una cadena o un número . Eso sería imposible. En cambio ... solo trata de entender la verdad. Que no hay cuchara .
Reactgular

Respuestas:


42

A diferencia de otras respuestas, hay casos en los que es aceptable devolver diferentes tipos.

Ejemplo 1

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

En algunos lenguajes de tipo estático, esto implica sobrecargas, por lo que podemos considerar que existen varios métodos, cada uno de los cuales devuelve el tipo fijo predefinido. En lenguajes dinámicos, esta puede ser la misma función, implementada como:

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

Misma función, diferentes tipos de valor de retorno.

Ejemplo 2

Imagine que obtiene una respuesta de un componente OpenID / OAuth. Algunos proveedores de OpenID / OAuth pueden contener más información, como la edad de la persona.

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

Otros tendrían el mínimo, sería una dirección de correo electrónico o el seudónimo.

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

De nuevo, la misma función, resultados diferentes.

Aquí, el beneficio de devolver diferentes tipos es especialmente importante en un contexto en el que no te interesan los tipos y las interfaces, sino los objetos que realmente contienen. Por ejemplo, imaginemos que un sitio web contiene lenguaje maduro. Entonces findCurrent()se puede usar así:

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();
}

Refactorizar esto en código donde cada proveedor tendrá su propia función que devolverá un tipo fijo bien definido no solo degradaría la base del código y causaría la duplicación del código, sino que tampoco brindaría ningún beneficio. Uno puede terminar haciendo horrores como:

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

55
"En los idiomas con escritura estática, esto implica sobrecargas" Creo que quiso decir "en algunos idiomas con escritura estática" :) Los buenos idiomas con escritura estática no requieren sobrecarga para algo como su sumejemplo.
Andres F.

17
Para su ejemplo específico, considere Haskell: sum :: Num a => [a] -> a. Puede sumar una lista de cualquier cosa que sea un número. A diferencia de JavaScript, si intenta sumar algo que no es un número, el error se detectará en el momento de la compilación.
Andres F.

3
@MainMa En Scala, Iterator[A]tiene un método def sum[B >: A](implicit num: Numeric[B]): B, que nuevamente permite sumar cualquier tipo de números y se verifica en tiempo de compilación.
Petr Pudlák

3
@Bakuriu Por supuesto, puede escribir dicha función, aunque primero tendría que sobrecargar las +cadenas (implementando Num, lo cual es una idea terrible en cuanto al diseño pero legal) o inventar un operador / función diferente que esté sobrecargado por enteros y cadenas respectivamente. Haskell tiene polimorfismo ad-hoc a través de clases de tipos. Una lista que contiene enteros y cadenas es mucho más difícil (posiblemente imposible sin extensiones de lenguaje) pero es un asunto bastante diferente.

2
No estoy particularmente impresionado con tu segundo ejemplo. Esos dos objetos son el mismo "tipo" conceptual, es solo que en algunos casos ciertos campos no están definidos o poblados. Podría representar eso bien incluso en un lenguaje estáticamente tipado con nullvalores.
Aaronaught

31

En general, es una mala idea por las mismas razones que el equivalente moral en un lenguaje de tipo estático es una mala idea: no tiene idea de qué tipo concreto se devuelve, por lo que no tiene idea de lo que puede hacer con el resultado (aparte de pocas cosas que se pueden hacer con cualquier valor). En un sistema de tipo estático, tiene anotaciones verificadas por el compilador de tipos de retorno y similares, pero en un lenguaje dinámico todavía existe el mismo conocimiento: es informal y se almacena en cerebros y documentación en lugar de en el código fuente.

Sin embargo, en muchos casos, hay una rima y una razón para el tipo que se devuelve y el efecto es similar a la sobrecarga o al polimorfismo paramétrico en los sistemas de tipo estático. En otras palabras, el tipo de resultado es predecible, simplemente no es tan simple de expresar.

Pero tenga en cuenta que puede haber otras razones por las que una función específica está mal diseñada: por ejemplo, una sumfunción que devuelve falso en entradas no válidas es una mala idea principalmente porque ese valor de retorno es inútil y propenso a errores (0 <-> falsa confusión).


40
Mis dos centavos: odio cuando eso sucede. He visto bibliotecas JS que devuelven nulo cuando no hay resultados, un objeto en un resultado y una matriz en dos o más resultados. Entonces, en lugar de simplemente recorrer una matriz, se ve obligado a determinar si es nulo, y si no lo es, si es una matriz, y si lo es, haga una cosa, de lo contrario haga otra. Especialmente porque, en el sentido normal, cualquier desarrollador sensato simplemente agregará el Objeto a una Matriz y reciclará la lógica del programa para que no maneje una matriz.
phyrfox

10
@Izkata: No creo que NaNsea ​​"contrapunto" en absoluto. NaN es en realidad una entrada válida para cualquier cálculo de punto flotante. Puedes hacer cualquier cosa con lo NaNque podrías hacer con un número real. Es cierto que el resultado final de dicho cálculo puede no ser muy útil para usted, pero no dará lugar a errores extraños de tiempo de ejecución y no es necesario que lo verifique todo el tiempo ; solo puede verificarlo una vez al final de un serie de cálculos. NaN no es un tipo diferente , es solo un valor especial , algo así como un objeto nulo .
Aaronaught

55
@Aaronaught Por otro lado, NaNcomo el objeto nulo, tiende a ocultarse cuando se producen errores, solo para que aparezcan más tarde. Por ejemplo, es probable que su programa explote si se NaNdesliza en un bucle condicional.
Izkata

2
@Izkata: NaN y nulo tienen comportamientos totalmente diferentes. Una referencia nula explotará inmediatamente después del acceso, un NaN se propaga. Por qué sucede esto último es obvio, le permite escribir expresiones matemáticas sin tener que verificar el resultado de cada subexpresión. Personalmente, considero que nulo es mucho más dañino, porque NaN representa un concepto numérico adecuado sin el cual no se puede escapar y, matemáticamente, la propagación es el comportamiento correcto.
Phoshi

44
No sé por qué esto se convirtió en un argumento "nulo contra todo lo demás". Simplemente estaba señalando que el NaNvalor (a) no es en realidad un tipo de retorno diferente y (b) tiene una semántica de punto flotante bien definida y, por lo tanto, realmente no califica como un análogo a un valor de retorno de tipo variable. En realidad, no es tan diferente de cero en aritmética de enteros; Si un cero "accidentalmente" se desliza en sus cálculos, a menudo es probable que termine con cero como resultado o con un error de división por cero. ¿Los valores de "infinito" definidos por el punto flotante IEEE también son malos?
Aaronaught

26

En lenguajes dinámicos, no debe preguntar si devuelve diferentes tipos sino objetos con diferentes API . Como a la mayoría de los lenguajes dinámicos no les importan los tipos, pero usa varias versiones de tipeo de pato .

Cuando regresan diferentes tipos tienen sentido

Por ejemplo, este método tiene sentido:

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

Porque tanto el archivo como una lista de cadenas son (en Python) iterables que devuelven cadenas. Tipos muy diferentes, la misma API (a menos que alguien intente llamar a los métodos de archivo en una lista, pero esta es una historia diferente).

Puede devolver condicionalmente listo tuple( tuplees una lista inmutable en python).

Formalmente incluso haciendo:

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

o:

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

devuelve diferentes tipos, ya que python Noney Javascript nullson tipos por derecho propio.

Todos estos casos de uso tendrían su contraparte en lenguajes estáticos, la función simplemente devolvería una interfaz adecuada.

Al devolver objetos con diferentes API condicionalmente es una buena idea

En cuanto a si devolver una API diferente es una buena idea, IMO en la mayoría de los casos no tiene sentido. El único ejemplo sensato que viene a la mente es algo parecido a lo que dijo @MainMa : cuando su API puede proporcionar una cantidad variable de detalles, podría tener sentido devolver más detalles cuando estén disponibles.


44
Buena respuesta. Vale la pena señalar que el equivalente Java / estáticamente tipado es hacer que una función devuelva una interfaz en lugar de un tipo concreto específico, con la interfaz que representa la API / abstracción.
mikera

1
Creo que estás haciendo trampas, en Python una lista y una tupla pueden ser de diferentes "tipos", pero son del mismo "tipo de pato", es decir, admiten el mismo conjunto de operaciones. Y casos como este son exactamente por qué se introdujo la tipificación de patos. También puedes hacer long x = getInt () en Java.
Kaerber

"Devolver diferentes tipos" significa "devolver objetos con diferentes API". (Algunos lenguajes hacen que la forma en que se construye el objeto sea una parte fundamental de la API, otros no; eso es cuestión de cuán expresivo es el sistema de tipos). En su ejemplo de lista / archivo, do_filedevuelve un iterable de cadenas de cualquier manera.
Gilles 'SO- deja de ser malvado'

1
En ese ejemplo de Python, también puede llamar iter()tanto a la lista como al archivo para asegurarse de que el resultado solo se pueda usar como un iterador en ambos casos.
RemcoGerlich

1
El retorno None or somethinges un asesino para la optimización del rendimiento realizado por herramientas como PyPy, Numba, Pyston y otras. Este es uno de los Python-ism que hacen que Python no sea tan rápido.
Matt

9

Tu pregunta me da ganas de llorar un poco. No para el uso de ejemplo que proporcionó, sino porque alguien inconscientemente llevará este enfoque demasiado lejos. Está a un paso del código ridículamente imposible de mantener.

El caso de uso de la condición de error tiene sentido, y el patrón nulo (todo tiene que ser un patrón) en lenguajes tipados estáticamente hace el mismo tipo de cosas. Su llamada de función devuelve objecto vuelve null.

Pero es un pequeño paso para decir "Voy a usar esto para crear un patrón de fábrica " y devolver bien fooo baro bazdependiendo del estado de ánimo de la función. La depuración de esto se convertirá en una pesadilla cuando la persona que llama espera foopero recibió bar.

Así que no creo que te estén cerrando la mente. Estás siendo cauteloso en cómo usar las funciones del lenguaje.

Divulgación: mi experiencia es en lenguajes tipados estáticamente y, en general, he trabajado en equipos más grandes y diversos donde la necesidad de código mantenible era bastante alta. Por lo tanto, mi perspectiva probablemente también esté sesgada.


6

El uso de Generics en Java le permite devolver un tipo diferente, mientras conserva la seguridad del tipo estático. Simplemente especifique el tipo que desea devolver en el parámetro de tipo genérico de la llamada a la función.

Si podría usar un enfoque similar en Javascript es, por supuesto, una pregunta abierta. Dado que Javascript es un lenguaje de tipo dinámico, devolver un objectparece ser la opción obvia.

Si desea saber dónde podría funcionar un escenario de retorno dinámico cuando está acostumbrado a trabajar en un lenguaje de tipo estático, considere mirar la dynamicpalabra clave en C #. Rob Conery pudo escribir con éxito un Mapeador Relacional de Objetos en 400 líneas de código usando la dynamicpalabra clave.

Por supuesto, todo dynamiclo que realmente hace es envolver una objectvariable con cierta seguridad de tipo de tiempo de ejecución.


4

Creo que es una mala idea devolver diferentes tipos condicionalmente. Una de las formas en que esto surge con frecuencia para mí es si la función puede devolver uno o más valores. Si solo se necesita devolver un valor, puede parecer razonable simplemente devolver el valor, en lugar de empaquetarlo en una matriz, para evitar tener que desempaquetarlo en la función de llamada. Sin embargo, esta (y la mayoría de las otras instancias de esto) impone una obligación a la persona que llama para distinguir y manejar ambos tipos. Será más fácil razonar sobre la función si siempre devuelve el mismo tipo.


4

La "mala práctica" existe independientemente de si su idioma está estáticamente escrito. El lenguaje estático hace más para alejarte de estas prácticas, y puedes encontrar más usuarios que se quejan de "malas prácticas" en un lenguaje estático porque es un lenguaje más formal. Sin embargo, los problemas subyacentes están ahí en un lenguaje dinámico, y puede determinar si están justificados.

Aquí está la parte objetable de lo que propones. Si no sé qué tipo se devuelve, entonces no puedo usar el valor de retorno inmediatamente. Tengo que "descubrir" algo al respecto.

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

A menudo, este tipo de cambio en el código es simplemente una mala práctica. Hace que el código sea más difícil de leer. En este ejemplo, verá por qué es popular lanzar y capturar casos de excepción. Dicho de otra manera: si su función no puede hacer lo que dice que debe hacer, no debería regresar con éxito . Si llamo a su función, quiero hacer esto:

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

Debido a que la primera línea regresa con éxito, supongo que en totalrealidad contiene la suma de la matriz. Si no regresa con éxito, saltamos a otra página de mi programa.

Usemos otro ejemplo que no se trata solo de propagar errores. Tal vez sum_of_array intenta ser inteligente y devolver una cadena legible por humanos en algunos casos, como "¡Esa es mi combinación de casillero!" si y solo si la matriz es [11,7,19]. Tengo problemas para pensar en un buen ejemplo. De todos modos, se aplica el mismo problema. Debe inspeccionar el valor de retorno antes de poder hacer algo con él:

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

Podría argumentar que sería útil para la función devolver un número entero o un flotante, por ejemplo:

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

Pero esos resultados no son diferentes, en lo que a usted respecta. Los tratarás a ambos como números, y eso es todo lo que te importa. Entonces sum_of_array devuelve el tipo de número. De esto se trata el polimorfismo.

Entonces, hay algunas prácticas que podría estar violando si su función puede devolver varios tipos. Conocerlos lo ayudará a determinar si su función particular debería devolver múltiples tipos de todos modos.


Lo siento, te extravié con mi pobre ejemplo. Olvida que lo mencioné en primer lugar. Tu primer párrafo está en punto. También me gusta tu segundo ejemplo.
Daniel Kaplan

4

En realidad, no es muy raro devolver diferentes tipos incluso en un lenguaje estáticamente tipado. Por eso tenemos tipos de unión, por ejemplo.

De hecho, los métodos en Java casi siempre devuelven uno de cuatro tipos: algún tipo de objeto nullo una excepción o nunca regresan en absoluto.

En muchos idiomas, las condiciones de error se modelan como subrutinas que devuelven un tipo de resultado o un tipo de error. Por ejemplo, en Scala:

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

Este es un ejemplo estúpido, por supuesto. El tipo de retorno significa "devolver una cadena o un decimal". Por convención, el tipo izquierdo es el tipo de error (en este caso, una cadena con el mensaje de error) y el tipo derecho es el tipo de resultado.

Esto es similar a las excepciones, excepto por el hecho de que las excepciones también son construcciones de flujo de control. De hecho, son equivalentes en poder expresivo a GOTO.


El método en Java que devuelve la excepción suena extraño. " Devolver una excepción " ... hmm
mosquito

3
Una excepción es un posible resultado de un método en Java. En realidad, olvidé un cuarto tipo: Unit(no se requiere un método para regresar). Es cierto que en realidad no usa la returnpalabra clave, pero es un resultado que se obtiene del método. Con excepciones marcadas, incluso se menciona explícitamente en la firma de tipo.
Jörg W Mittag

Veo. Su punto parece tener algunos méritos, pero la forma en que se presenta no lo hace parecer convincente ... más bien opuesto
mosquito

44
En muchos idiomas, las condiciones de error se modelan como una subrutina que devuelve un tipo de resultado o un tipo de error. Las excepciones son una idea similar, excepto que también son construcciones de flujo de control (igual en poder expresivo GOTO, en realidad).
Jörg W Mittag

4

Ninguna respuesta ha mencionado aún los principios SÓLIDOS. En particular, debe seguir el principio de sustitución de Liskov, de que cualquier clase que reciba un tipo distinto al esperado puede seguir trabajando con lo que sea, sin hacer nada para probar qué tipo se devuelve.

Por lo tanto, si arroja algunas propiedades adicionales sobre un objeto, o envuelve una función devuelta con algún tipo de decorador que aún logra lo que la función original estaba destinada a lograr, es bueno, siempre y cuando ningún código que llame a su función se base en esto comportamiento, en cualquier ruta de código.

En lugar de devolver una cadena o un entero, un mejor ejemplo podría ser que devuelve un sistema de rociadores o un gato. Esto está bien si todo lo que va a hacer el código de llamada es llamar a functionInQuestion.hiss (). Efectivamente, tiene una interfaz implícita que el código de llamada espera, y un lenguaje de tipo dinámico no lo obligará a hacer explícita la interfaz.

Lamentablemente, sus compañeros de trabajo probablemente lo harán, por lo que es probable que tenga que hacer el mismo trabajo de todos modos en su documentación, excepto que no existe una forma universalmente aceptada, analizable y analizable por máquina para hacerlo, como ocurre cuando define una interfaz en un idioma que los tiene.


3

El único lugar donde me veo enviando diferentes tipos es para entradas no válidas o "excepciones de hombres pobres" donde la condición "excepcional" no es muy excepcional. Por ejemplo, desde mi repositorio de funciones de utilidad PHP, este ejemplo abreviado:

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;
}

La función nominalmente devuelve un BOOLEAN, sin embargo, devuelve NULL en una entrada no válida. Tenga en cuenta que desde PHP 5.3, todas las funciones internas de PHP también se comportan de esta manera . Además, algunas funciones internas de PHP devuelven FALSO o INT en la entrada nominal, consulte:

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

44
Y es por eso que odio PHP. Bueno, una de las razones, de todos modos.
Aaronaught

1
¿Un voto negativo porque a alguien no le gusta el lenguaje que desarrollo profesionalmente? ¡Espera hasta que descubran que soy judío! :)
dotancohen

3
No siempre asuma que las personas que comentan y las personas que votan negativamente son las mismas personas.
Aaronaught

1
Prefiero tener una excepción en lugar de null, porque (a) falla alto , por lo que es más probable que se solucione en la fase de desarrollo / prueba, (b) es fácil de manejar, porque puedo detectar todas las excepciones raras / inesperadas una vez por toda mi aplicación (en mi controlador frontal) y solo lo registro (generalmente envío correos electrónicos al equipo de desarrollo), para que pueda solucionarlo más tarde. Y en realidad odio que la biblioteca PHP estándar utilice principalmente el enfoque de "retorno nulo"; realmente solo hace que el código PHP sea más propenso a errores, a menos que verifique todo isset(), lo que es una carga demasiado pesada.
scriptin

1
Además, si devuelve nullun error, por favor, déjelo claro en un docblock (por ejemplo @return boolean|null), de modo que si encuentro su código algún día no tendría que verificar el cuerpo de la función / método.
scriptin

3

¡No creo que sea una mala idea! En contraste con esta opinión más común, y como ya señaló Robert Harvey, los lenguajes estáticamente tipados como Java introdujeron genéricos exactamente para situaciones como la que usted está preguntando. En realidad, Java intenta mantener (siempre que sea posible) la seguridad de tipos en el momento de la compilación y, a veces, los genéricos evitan la duplicación de código, ¿por qué? Porque puede escribir el mismo método o la misma clase que maneja / devuelve diferentes tipos . Voy a hacer un breve ejemplo para mostrar esta idea:

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+

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

En un lenguaje de escritura dinámico, dado que no tiene seguridad de escritura en tiempo de compilación, es absolutamente libre de escribir el mismo código que funciona para muchos tipos. Dado que incluso en un lenguaje de tipo estático, se introdujeron genéricos para resolver este problema, es claramente una pista de que escribir una función que devuelve diferentes tipos en un lenguaje dinámico no es una mala idea.


¡Otro voto negativo sin explicación! Gracias comunidad SE!
thermz

2
Ten una simpatía +1. Debe intentar abrir su respuesta con una respuesta más clara y directa, y abstenerse de comentar sobre diferentes respuestas. (Te daría otro +1, ya que estoy de acuerdo contigo, pero solo tengo uno para dar).
DougM

3
Los genéricos no existen en lenguajes de tipo dinámico (que yo sepa). No habría razón para que lo hicieran. La mayoría de los genéricos son un caso especial, donde el "contenedor" es más interesante que lo que hay en él (por lo que algunos lenguajes, como Java, realmente usan borrado de tipo). El tipo genérico es en realidad un tipo propio. Los métodos genéricos , sin un tipo genérico real, como en su ejemplo aquí, casi siempre son sintaxis de azúcar para una semántica de asignación y conversión. No es un ejemplo particularmente convincente de lo que realmente pregunta la pregunta.
Aaronaught

1
Intento ser un poco más sintético: "Dado que incluso en un lenguaje estáticamente escrito, se introdujeron genéricos para resolver este problema, es claramente una pista de que escribir una función que devuelve diferentes tipos en un lenguaje dinámico no es una mala idea". ¿Mejor ahora? Verifique la respuesta de Robert Harvey o el comentario de BRPocock y se dará cuenta de que el ejemplo de los genéricos está relacionado con esta pregunta
thermz

1
No te sientas mal, @thermz. Los votos negativos a menudo dicen más sobre el lector que el escritor.
Karl Bielefeldt

2

El desarrollo de software es básicamente un arte y un oficio de gestionar la complejidad. Intenta reducir el sistema en los puntos que puede permitirse y limitar las opciones en otros puntos. La interfaz de la función es un contrato que ayuda a gestionar la complejidad del código al limitar el conocimiento requerido para trabajar con cualquier parte del código. Al devolver diferentes tipos, expande significativamente la interfaz de la función agregando todas las interfaces de diferentes tipos que devuelve Y agregando reglas no obvias sobre qué interfaz se devuelve.


1

Perl usa esto mucho porque lo que hace una función depende de su contexto . Por ejemplo, una función podría devolver una matriz si se usa en el contexto de la lista, o la longitud de la matriz si se usa en un lugar donde se espera un valor escalar. Del tutorial que es el primer éxito para "contexto perl" , si lo hace:

my @now = localtime();

Entonces @now es una variable de matriz (eso es lo que significa @), y contendrá una matriz como (40, 51, 20, 9, 0, 109, 5, 8, 0).

Si, en cambio, llama a la función de una manera donde su resultado tendrá que ser un escalar, con ($ las variables son escalares):

my $now = localtime();

entonces hace algo completamente diferente: $ ahora será algo así como "Vie 9 de enero 20:51:40 2009".

Otro ejemplo en el que puedo pensar es en la implementación de API REST, donde el formato de lo que se devuelve depende de lo que el cliente quiera. Por ejemplo, HTML o JSON o XML. Aunque técnicamente esos son todos flujos de bytes, la idea es similar.


Es posible que desee mencionar la forma específica en que lo hace en perl - wantarray ... y tal vez un enlace a alguna discusión si es bueno o malo - Uso de wantarray considerado perjudicial en PerlMonks

Por coincidencia, estoy lidiando con esto con una API Java Rest que estoy construyendo. Hice posible que un recurso devolviera XML o JSON. El tipo de denominador común más bajo en ese caso es a String. Ahora las personas solicitan documentación sobre el tipo de devolución. Las herramientas de Java pueden generarlo automáticamente si devuelvo una clase, pero debido a que elegí Stringno puedo generar la documentación automáticamente. En retrospectiva / moral de la historia, me gustaría devolver un tipo por método.
Daniel Kaplan

Estrictamente, esto equivale a una forma de sobrecarga. En otras palabras, hay múltiples funciones federadas, y dependiendo de los detalles de la llamada, se elige una u otra versión.
Ian

1

En la tierra dinámica se trata de tipear patos. La cosa más responsable expuesta / pública es envolver tipos potencialmente diferentes en un contenedor que les da la misma interfaz.

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

Puede tener sentido ocasionalmente expulsar diferentes tipos sin un envoltorio genérico, pero honestamente en 7 años de escribir JS, no es algo que haya encontrado que sea razonable o conveniente hacer con mucha frecuencia. Principalmente, eso es algo que haría en el contexto de entornos cerrados como el interior de un objeto donde las cosas se están armando. Pero no es algo que haya hecho con tanta frecuencia como para que se me ocurran ejemplos.

Principalmente, te aconsejaría que dejes de pensar en los tipos por completo. Tratas con los tipos cuando lo necesitas en un lenguaje dinámico. No más. Es todo el punto. No verifique el tipo de cada argumento. Eso es algo que solo estaría tentado de hacer en un entorno en el que el mismo método podría dar resultados inconsistentes de maneras no obvias (así que definitivamente no haga algo así). Pero no es el tipo lo que importa, sino cómo funciona lo que sea que me des.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.