Formas elegantes de devolver múltiples valores desde una función

Parece que en la mayoría de los lenguajes de progtwigción convencionales, devolver valores múltiples desde una función es una cosa extremadamente incómoda.

Las soluciones típicas son hacer una estructura o una clase de datos antigua y devolverla, o pasar al menos algunos de los parámetros por referencia o puntero en lugar de devolverlos.

El uso de referencias / punteros es bastante incómodo porque depende de los efectos secundarios y significa que aún tiene que pasar otro parámetro.

La solución de clase / estructura también es IMHO bastante incómoda porque luego terminas con un millón de pequeñas clases / estructuras que solo se usan para devolver valores de las funciones, lo que genera una confusión innecesaria y verbosidad.

Además, muchas veces hay un valor de retorno que siempre se necesita, y el rest solo es utilizado por la persona que llama en ciertas circunstancias. Ninguna de estas soluciones permite que la persona que llama ignore los tipos de retorno innecesarios.

    El único idioma que conozco que maneja múltiples valores de retorno de manera elegante es Python. Para aquellos de ustedes que no están familiarizados, utiliza el desempaquetado de la tupla:

    a, b = foo(c) # a and b are regular variables. myTuple = foo(c) # myTuple is a tuple of (a, b) 

    ¿Alguien tiene alguna otra buena solución a este problema? Los lenguajes que funcionan en los idiomas principales existentes además de Python y las soluciones de nivel de idioma que ha visto en idiomas no convencionales son bienvenidos.

    Casi todos los lenguajes funcionales con influencia de ML (que es la mayoría de ellos) también tienen un gran soporte de tupla que hace que este tipo de cosas sean triviales.

    Para C ++ me gusta boost :: tuple plus boost :: tie (o std :: tr1 si lo tienes)

     typedef boost::tuple XYZ; XYZ foo(); double x,y,z; boost::tie(x,y,z) = foo(); 

    o un ejemplo menos artificial

     MyMultimap::iterator lower,upper; boost::tie(lower,upper) = some_map.equal_range(key); 

    Algunos idiomas, en particular, Lisp y JavaScript, tienen una función llamada desestructuración o enlace de desestructuración. Esencialmente, se trata de desempaquetar tuplas con esteroides: en lugar de limitarse a secuencias como tuplas, listas o generadores, puede desempaquetar estructuras de objetos más complejas en una statement de asignación. Para más detalles, vea aquí para la versión de Lisp o aquí para la versión de JavaScript (algo más legible) .

    Aparte de eso, no conozco muchas características de idioma para tratar con múltiples valores de retorno en general. Sin embargo, hay algunos usos específicos de múltiples valores de retorno que a menudo pueden ser reemplazados por otras características de idioma. Por ejemplo, si uno de los valores es un código de error, podría ser mejor reemplazarlo con una excepción.

    Si bien la creación de nuevas clases para mantener múltiples valores de retorno es como un desorden, el hecho de que esté devolviendo esos valores juntos es a menudo una señal de que su código será mejor en general una vez que se cree la clase. En particular, otras funciones que tratan con los mismos datos pueden luego moverse a la nueva clase, lo que puede hacer que su código sea más fácil de seguir. Esto no es universalmente cierto, pero vale la pena considerarlo. (La respuesta de Cpeterso sobre grupos de datos expresa esto con más detalle).

    Ejemplo de PHP:

     function my_funct() { $x = "hello"; $y = "world"; return array($x, $y); } 

    Entonces, cuando se ejecuta:

     list($x, $y) = my_funct(); echo $x.' '.$y; // "hello world" 

    Si una función devuelve varios valores, es un signo que podría estar presenciando el olor del código “Grupo de datos” . A menudo, los grupos de datos son valores primitivos que nadie piensa que se conviertan en un objeto, pero ocurren cosas interesantes cuando comienza a buscar comportamientos para moverse hacia los nuevos objetos.

    Escribir clases de ayuda pequeñas puede ser una escritura adicional, pero proporciona nombres claros y una comprobación de tipos segura. Los desarrolladores que mantengan su código lo apreciarán. Y las clases pequeñas útiles a menudo crecen para ser utilizadas en otro código.

    Además, si una función devuelve varios valores, entonces podría estar haciendo demasiado trabajo. ¿Se podría refactorizar la función en dos (o más) funciones pequeñas?

    Nadie parece haber mencionado a Perl todavía.

     sub myfunc { return 1, 2; } my($val1, $val2) = myfunc(); 

    En cuanto a Java, consulte Thinking in Java de Bruce Eckel para obtener una buena solución (págs. 621 y siguientes).

    En esencia, puede definir una clase equivalente a la siguiente:

     public class Pair { public final T left; public final U right; public Pair (T t, U u) { left = t; right = u; } } 

    Luego puede usar esto como el tipo de retorno para una función, con los parámetros de tipo apropiados:

     public Pair getAnswer() { return new Pair("the universe", 42); } 

    Después de invocar esa función:

     Pair myPair = getAnswer(); 

    puede consultar myPair.left y myPair.right para acceder a los valores constituyentes.

    Hay otras opciones de azúcar sintáctica, pero lo anterior es el punto clave.

    Pienso que python’s es la forma más natural, cuando tuve que hacer lo mismo en php, la única era envolver el retorno en una matriz. Aunque tiene un desempaque similar.

    Incluso si ignora la maravillosa asignación de destrucción más reciente que Moss Collum mencionó , JavaScript es bastante bueno al devolver múltiples resultados.

     function give7and5() { return {x:7,y:5}; } a=give7and5(); console.log(ax,ay); 7 5 

    Tanto Lua como CLU (por el grupo de Barbara Liskov en MIT) tienen múltiples valores de retorno para todas las funciones, siempre es el valor predeterminado. Creo que los diseñadores de Lua se inspiraron en CLU. Me gusta que los valores múltiples sean los valores predeterminados para llamadas y devoluciones; Creo que es una forma muy elegante de pensar las cosas. En Lua y CLU este mecanismo es completamente independiente de las tuplas y / o registros.

    El lenguaje de ensamblaje portátil C– admite múltiples valores de retorno para su convención de llamada nativa pero no para la convención de llamada C. Ahí la idea es permitir que un escritor del comstackdor devuelva múltiples valores en registros de hardware individuales en lugar de tener que poner los valores en un grupo en la memoria y devolver un puntero a ese grupo (o como en C, hacer que la persona que llama pase un puntero a un grupo vacío).

    Como con cualquier mecanismo, se pueden abusar de los valores de retorno múltiples, pero no creo que sea más irrazonable que una función devuelva valores múltiples que tener una estructura que contenga valores múltiples.

    A menudo en PHP usaré una entrada referenciada:

     public function Validates(&$errors=array()) { if($failstest) { $errors[] = "It failed"; return false; } else { return true; } } 

    Eso me da los mensajes de error que quiero sin contaminar el retorno de bool, por lo que todavía puedo hacer:

     if($this->Validates()) ... 

    En C++ puede pasar un contenedor a la función para que la función pueda llenarla:

     void getValues(std::vector* result){ result->push_back(2); result->push_back(6); result->push_back(73); } 

    También podría hacer que la función devuelva un puntero inteligente (use shared_ptr ) a un vector :

     boost::shared_ptr< std::vector > getValues(){ boost::shared_ptr< std::vector > vec(new std::vector(3)); (*vec)[0] = 2; (*vec)[1] = 6; (*vec)[2] = 73; return vec; } 

    do#

     public struct tStruct { int x; int y; string text; public tStruct(int nx, int ny, string stext) { this.x = nx; this.y = ny; this.text = stext; } } public tStruct YourFunction() { return new tStruct(50, 100, "hello world"); } public void YourPrintFunction(string sMessage) { Console.WriteLine(sMessage); } 

    en lua llamas

     MyVar = YourFunction(); YourPrintfFunction(MyVar.x); YourPrintfFunction(MyVar.y); YourPrintfFunction(MyVar.text); 

    Salida: 50 100 hola mundo.

    Pablo

    Por cierto, Scala puede devolver varios valores de la siguiente manera (pegado desde una sesión de intérprete):

     scala> def return2 = (1,2) return2: (Int, Int) scala> val (a1,a2) = return2 a1: Int = 1 a2: Int = 2 

    Este es un caso especial de usar el patrón de coincidencia para la asignación .

    Aquí estaba mi idea de Ruby de devolver un valor especial que se ve y actúa como un escalar pero que en realidad tiene atributos ocultos:

    https://gist.github.com/2305169