¿Por qué muchos idiomas no admiten parámetros con nombre? [cerrado]

14

Estaba pensando en lo fácil que sería leer código si, al llamar a una función, pudiera escribir:

doFunction(param1=something, param2=somethingElse);

No puedo pensar en ningún inconveniente y haría que el código fuera mucho más legible. Sé que podría pasar una matriz como único argumento y tener las claves de la matriz como nombres de parámetros, sin embargo, eso aún no sería tan legible.

¿Hay una desventaja de esto que me estoy perdiendo? Si no, ¿por qué muchos idiomas no permiten esto?


fuente
1
¿Puedes dar un lenguaje que debería (y puede) tener parámetros con nombre pero no tenerlo?
Bryan Chen el
1
Creo que es demasiado detallado en muchos casos. Hasta donde he visto, si un idioma usa esto o no, tiende a depender de si impactaría el idioma de manera positiva o negativa. La mayoría de los lenguajes de estilo C funcionan bien sin él. En su caso, a menudo es bastante obvio lo que sucede de todos modos, y su ausencia realmente ayuda a reducir el desorden en general.
Panzercrisis
2
@SteveFallows: El "Idioma de parámetro con nombre" parece un nombre extraño para la definición de una API fluida (como lo llama Martin Fowler) en una clase de constructor. Puede encontrar ejemplos del mismo patrón en uno de los primeros elementos de Joshua Bloch's Effective Java .
jhominal
3
Esta no es necesariamente una pregunta basada en una opinión
Catskul
2
Convenido. "¿Por qué muchos idiomas no admiten parámetros con nombre?" Es más una cuestión de historia de los idiomas que de opinión. Sería una opinión si alguien lo preguntara como "¿No se nombran mejor los parámetros?".
Andy Fleming

Respuestas:

14

Especificar nombres de parámetros no siempre hace que el código sea más legible: como ejemplo, ¿preferiría leer min(first=0, second=1)o min(0, 1)?

Si acepta el argumento del párrafo anterior, es bastante obvio que especificar nombres de parámetros no debería ser obligatorio. ¿Por qué no todos los idiomas hacen que la especificación de nombres de parámetros sea una opción válida?

Puedo pensar en al menos dos razones:

  • En general, la introducción de una segunda sintaxis para una necesidad ya cubierta (aquí, pasar parámetros) es algo cuyos beneficios deben equilibrarse con el costo inherente de la complejidad del lenguaje introducido (tanto en la implementación del lenguaje como en el modelo mental del lenguaje del programador) ;
  • Hace que el cambio de nombres de parámetros sea un cambio de ruptura de API, que puede no cumplir con las expectativas de un desarrollador de que cambiar el nombre de un parámetro es un cambio trivial y sin interrupciones;

Tenga en cuenta que no conozco ningún lenguaje que implemente parámetros con nombre sin implementar también parámetros opcionales (que requieren parámetros con nombre), por lo que debe tener cuidado de sobreestimar sus beneficios de legibilidad, que se pueden obtener de manera más consistente con la definición de Interfaces fluidas .

jhominal
fuente
66
Codifico regularmente en varios idiomas diferentes, casi siempre tengo que buscar los parámetros para una subcadena simple en cualquier idioma. ¿Es una subcadena (inicio, longitud) o una subcadena (inicio, fin) y el final está incluido en la cadena?
James Anderson el
1
@JamesAnderson: ¿cómo resolvería un problema una subcadena de parámetros con nombre? Todavía tendría que buscar los parámetros para saber cuál es el nombre del segundo parámetro (a menos que existan ambas funciones y la función de polimorfismo del lenguaje permita parámetros del mismo tipo con nombres diferentes).
mouviciel
66
@mouviciel: Los parámetros con nombre no son muy útiles para el escritor , pero son fantásticos para el lector . Todos sabemos lo importantes que son los nombres de variables para crear código legible, y los parámetros con nombre permiten al creador de una interfaz dar buenos nombres de parámetros y buenos nombres de funciones, lo que facilita a las personas que usan esa interfaz escribir código legible.
Phoshi
@Phoshi - Estás en lo correcto. Simplemente olvidé la importancia de leer código cuando escribí mi comentario anterior.
Mouviciel
14

Los parámetros con nombre hacen que el código sea más fácil de leer, más difícil de escribir

Cuando leo un fragmento de código, los parámetros con nombre pueden introducir un contexto que hace que el código sea más fácil de entender. Considere, por ejemplo, este constructor: Color(1, 102, 205, 170). ¿Qué demonios significa eso? De hecho, Color(alpha: 1, red: 102, green: 205, blue: 170)sería mucho más fácil de leer. Pero, por desgracia, el compilador dice "no" , quiere Color(a: 1, r: 102, g: 205, b: 170). Al escribir código usando parámetros con nombre, pasa una cantidad innecesaria de tiempo buscando los nombres exactos; es más fácil olvidar los nombres exactos de algunos parámetros que olvidar su orden.

Esto una vez me mordió al usar una DateTimeAPI que tenía dos clases de hermanos para puntos y duraciones con interfaces casi idénticas. Si bien DateTime->new(...)aceptó un second => 30argumento, el DateTime::Duration->new(...)deseado seconds => 30, y similar para otras unidades. Sí, tiene mucho sentido, pero esto me mostró que los parámetros nombrados son fáciles de usar.

Los malos nombres ni siquiera hacen que sea más fácil de leer

Otro ejemplo de cómo los parámetros nombrados pueden ser malos es probablemente el lenguaje R. Este fragmento de código crea un diagrama de datos:

plot(plotdata$n, plotdata$mu, type="p", pch=17,  lty=1, bty="n", ann=FALSE, axes=FALSE)

Se ven dos argumentos posicionales para el X y Y las filas de datos, y luego una lista de parámetros con nombre. Hay muchas más opciones con valores predeterminados, y solo se enumeran aquellos cuyos valores predeterminados quería cambiar o especificar explícitamente. Una vez que ignoramos que este código usa números mágicos y podría beneficiarse del uso de enumeraciones (¡si R tuviera alguno!), El problema es que muchos de estos nombres de parámetros son bastante indescifrables.

  • pches en realidad el carácter de la trama, el glifo que se dibujará para cada punto de datos. 17es un círculo vacío, o algo así.
  • ltyes el tipo de línea Aquí 1hay una línea continua.
  • btyes el tipo de caja Configurarlo para "n"evitar que se dibuje un cuadro alrededor de la trama.
  • ann controla la apariencia de las anotaciones de eje.

Para alguien que no sabe lo que significa cada abreviatura, estas opciones son bastante confusas. Esto también revela por qué R usa estas etiquetas: no como código autodocumentado, sino (siendo un lenguaje de tipo dinámico) como claves para asignar los valores a sus variables correctas.

Propiedades de parámetros y firmas

Las firmas de funciones pueden tener las siguientes propiedades:

  • Los argumentos pueden ser ordenados o desordenados,
  • con o sin nombre,
  • requerido u opcional.
  • Las firmas también se pueden sobrecargar por tamaño o tipo,
  • y puede tener un tamaño no especificado con varargs.

Diferentes idiomas aterrizan en diferentes coordenadas de este sistema. En C, los argumentos están ordenados, sin nombre, siempre son obligatorios y pueden ser varargs. En Java, la situación es similar, excepto que las firmas pueden sobrecargarse. En el Objetivo C, las firmas se ordenan, nombran, requieren y no se pueden sobrecargar porque es solo azúcar sintáctico alrededor de C.

Los idiomas escritos dinámicamente con varargs (interfaces de línea de comandos, Perl, ...) pueden emular parámetros con nombre opcionales. Los idiomas con sobrecarga de tamaño de firma tienen algo así como parámetros opcionales posicionales.

Cómo no implementar parámetros con nombre

Cuando pensamos en parámetros con nombre, generalmente asumimos parámetros con nombre, opcionales y sin ordenar. Implementar estos es difícil.

Los parámetros opcionales pueden tener valores predeterminados. Estos deben ser especificados por la función llamada y no deben compilarse en el código de llamada. De lo contrario, los valores predeterminados no se pueden actualizar sin volver a compilar todo el código dependiente.

Ahora, una pregunta importante es cómo se pasan los argumentos a la función. Con parámetros ordenados, los argumentos se pueden pasar en un registro o en su orden inherente en la pila. Cuando excluimos los registros por un momento, el problema es cómo colocar argumentos opcionales desordenados en la pila.

Para eso, necesitamos un cierto orden sobre los argumentos opcionales. ¿Qué pasa si se cambia el código de declaración? Como el orden es irrelevante, un reordenamiento en la declaración de función no debería cambiar la posición de los valores en la pila. También debemos considerar si es posible agregar un nuevo parámetro opcional. Desde la perspectiva de los usuarios, esto parece ser así, porque el código que no utilizó ese parámetro anteriormente debería funcionar con el nuevo parámetro. Por lo tanto, esto excluye pedidos como el uso del orden en la declaración o el uso del orden alfabético.

Considere esto también a la luz del subtipo y el Principio de sustitución de Liskov: en la salida compilada, las mismas instrucciones deberían poder invocar el método en un subtipo con posiblemente nuevos parámetros con nombre y en un supertipo.

Posibles implementaciones

Si no podemos tener un orden definitivo, entonces necesitamos una estructura de datos desordenada.

  • La implementación más simple es simplemente pasar el nombre de los parámetros junto con los valores. Así se emulan los parámetros nombrados en Perl o con las herramientas de línea de comandos. Esto resuelve todos los problemas de extensión mencionados anteriormente, pero puede ser una gran pérdida de espacio, no una opción en el código de rendimiento crítico. Además, procesar estos parámetros con nombre ahora es mucho más complicado que simplemente extraer valores de una pila.

    En realidad, los requisitos de espacio se pueden reducir mediante la agrupación de cadenas, que puede reducir las comparaciones de cadenas posteriores a las comparaciones de puntero (excepto cuando no se puede garantizar que las cadenas estáticas se agrupen realmente, en cuyo caso las dos cadenas tendrán que compararse en detalle).

  • En cambio, también podríamos pasar una estructura de datos inteligente que funcione como un diccionario de argumentos con nombre. Esto es barato en el lado de la persona que llama, porque el conjunto de claves se conoce estáticamente en la ubicación de la llamada. Esto permitiría crear una función hash perfecta o precalcular un trie. La persona que llama aún tendrá que probar la existencia de todos los posibles nombres de parámetros, lo cual es algo costoso. Python usa algo como esto.

Entonces es demasiado caro en la mayoría de los casos

Si una función con parámetros con nombre debe ser extensible correctamente, no se puede suponer un orden definitivo. Entonces solo hay dos soluciones:

  • Haga que el orden de los parámetros nombrados sea parte de la firma y no permita cambios posteriores. Esto es útil para el código de autodocumentación, pero no ayuda con argumentos opcionales.
  • Pase una estructura de datos de valor clave al destinatario, que luego debe extraer información útil. Esto es muy costoso en comparación, y generalmente solo se ve en lenguajes de script sin un énfasis en el rendimiento.

Otras trampas

Los nombres de variables en una declaración de función generalmente tienen algún significado interno y no son parte de la interfaz, incluso si muchas herramientas de documentación todavía los mostrarán. En muchos casos, desearía diferentes nombres para una variable interna y el argumento con nombre correspondiente. Los idiomas que no permiten elegir los nombres visibles externamente de un parámetro con nombre no ganan mucho si el nombre de la variable no se utiliza teniendo en cuenta el contexto de la llamada.

Un problema con las emulaciones de argumentos con nombre es la falta de comprobación estática en el lado de la persona que llama. Esto es especialmente fácil de olvidar al pasar un diccionario de argumentos (mirándote, Python). Esto es importante porque pasando un diccionario es una solución común, por ejemplo en JavaScript: foo({bar: "baz", qux: 42}). Aquí, ni los tipos de valores ni la existencia o ausencia de ciertos nombres se pueden verificar estáticamente.

Emulación de parámetros con nombre (en lenguajes estáticamente escritos)

Simplemente usar cadenas como claves, y cualquier objeto como valor no es muy útil en presencia de un verificador de tipo estático. Sin embargo, los argumentos con nombre se pueden emular con estructuras o literales de objeto:

// Java

static abstract class Arguments {
  public String bar = "default";
  public int    qux = 0;
}

void foo(Arguments args) {
  ...
}

/* using an initializer block */
foo(new Arguments(){{ bar = "baz"; qux = 42; }});
amon
fuente
3
" it is easier to forget the exact names of some parameters than it is to forget their order" ... esa es una afirmación interesante.
Catskul
1
El primer contraejemplo no es un problema con los parámetros con nombre, y más bien una falla con la forma en que los plurales en inglés se asignan a los nombres de campo en las interfaces. El segundo contraejemplo es un problema similar con los desarrolladores que toman malas decisiones de nombres. (Ninguna opción de interfaz de paso de parámetros será en sí misma una condición suficiente para la legibilidad; la pregunta es si es o no una condición necesaria o ayuda en algunos casos).
mtraceur
1
+1 para los puntos generales sobre costos de implementación y compensaciones para hacer parámetros con nombre. Solo creo que el comienzo perderá gente.
mtraceur
@mtraceur Tienes un punto. Ahora, 5 años después, probablemente escribiría la respuesta de manera bastante diferente. Si lo desea, puede editar mi respuesta para eliminar cosas menos útiles. (Por lo general, tales ediciones serían rechazadas ya que contradicen la intención del autor, pero aquí estoy bien con eso). Por ejemplo, ahora creo que muchos problemas de parámetros con nombre son solo restricciones de lenguajes dinámicos, y deberían obtener un sistema de tipos. Por ejemplo, JavaScript tiene Flow y TypeScript, Python tiene MyPy. Con una integración IDE adecuada que elimina la mayoría de los problemas de usabilidad. Todavía estoy esperando algo en Perl.
amon
7

Por la misma razón que la notación húngara ya no se usa ampliamente; Intellisense (o su equivalente moral en IDE que no sean de Microsoft). La mayoría de los IDE modernos le dirán todo lo que necesita saber sobre un parámetro simplemente pasando el cursor sobre la referencia del parámetro.

Dicho esto, varios lenguajes admiten parámetros con nombre, incluidos C # y Delphi. En C #, es opcional, por lo que no tiene que usarlo si no lo desea, y hay otras formas de declarar miembros específicamente, como la inicialización de objetos.

Los parámetros con nombre son principalmente útiles cuando solo desea especificar un subconjunto de parámetros opcionales, y no todos. En C #, esto es muy útil porque ya no necesita un montón de métodos sobrecargados para proporcionar al programador esta flexibilidad.

Robert Harvey
fuente
44
Python usa parámetros con nombre, y es una característica maravillosa del lenguaje. Deseo que más idiomas lo usen. Facilita los argumentos predeterminados ya que ya no está obligado, como en C ++, a especificar explícitamente cualquier argumento "antes" de los valores predeterminados. (Como lo hizo en su último párrafo, así es como Python se escapa sin tener sobrecarga ... los argumentos predeterminados y los argumentos de nombre le permiten hacer las mismas cosas). Los argumentos con nombre también hacen que el código sea más legible. Cuando tienes algo así sin(radians=2), no necesitas "Intellisense".
Gort the Robot
2

Bash ciertamente admite este estilo de programación, y tanto Perl como Ruby admiten la aprobación de listas de parámetros de nombre / valor (como lo haría cualquier idioma con soporte nativo de hash / mapa). Nada le impide elegir definir una estructura / registro o hash / mapa que adjunte nombres a los parámetros.

Para los idiomas que incluyen almacenes hash / map / keyvalue de forma nativa, puede obtener la funcionalidad que desea, simplemente adoptando el modismo para pasar hashes de clave / valor a sus funciones / métodos. Una vez que lo haya probado en un proyecto, tendrá una mejor idea de si ha ganado algo, ya sea por el aumento de la productividad derivado de la facilidad de uso o la mejora de la calidad a través de la reducción de defectos.

Tenga en cuenta que los programadores experimentados se han acostumbrado a pasar parámetros por orden / ranura. También puede encontrar que este idioma solo es valioso cuando tiene más de unos pocos argumentos (digamos> 5/6). Y dado que una gran cantidad de argumentos a menudo son indicativos de métodos (demasiado) complejos, es posible que este modismo solo sea beneficioso para los métodos más complejos.

ChuckCottrill
fuente
2

Creo que es la misma razón por la cual c # es más popular que VB.net. Si bien VB.NET es más "legible", por ejemplo, escribe "end if" en lugar de un corchete de cierre, simplemente termina siendo más cosas que rellenan el código y eventualmente lo hacen más difícil de entender.

Resulta que lo que hace que el código sea más fácil de entender es la concisión. Cuanto menos mejor. Los nombres de los parámetros de función son normalmente bastante obvios de todos modos, y realmente no van tan lejos para ayudarlo a comprender el código.

Rocklan
fuente
1
Estoy totalmente de acuerdo con que "cuanto menos, mejor". Llevado a su extremo lógico, los ganadores del código golf serían los "mejores" programas.
Riley Major
2

Los parámetros con nombre son una solución a un problema en la refactorización del código fuente y no pretenden hacer que el código fuente sea más legible . Los parámetros con nombre se utilizan para ayudar al compilador / analizador a resolver los valores predeterminados para las llamadas a funciones. Si desea que el código sea más legible, agregue comentarios significativos.

Los lenguajes que son difíciles de refactorizar a menudo admiten parámetros con nombre, porque foo(1)se romperán cuando se firmen los foo()cambios, pero foo(name:1)es menos probable que se rompan, lo que requiere menos esfuerzo del programador para realizar cambios foo.

¿Qué haces cuando tienes que introducir un nuevo parámetro a la siguiente función y hay cientos o miles de líneas de código que llaman a esa función?

foo(int weight, int height, int age = 0);

La mayoría de los programadores harán lo siguiente.

foo(int weight, int height, int age = 0, string gender = null);

Ahora, no se requiere refactorización y la función puede ejecutarse en modo heredado cuando genderes nulo.

Especificar genderun valor ahora está HARDCODED en la solicitud age. Como este ejemplo:

 foo(10,10,0,"female");

El programador miró la definición de la función, vio que agetiene un valor predeterminado 0y usó ese valor.

Ahora el valor predeterminado para ageen la definición de función es completamente inútil.

Los parámetros con nombre le permiten evitar este problema.

foo(weight: 10, height: 10, gender: "female");

Se pueden agregar nuevos parámetros fooy no tiene que preocuparse por el orden en que se agregaron, y puede cambiar los valores predeterminados sabiendo que el valor predeterminado que establece es realmente su verdadero valor predeterminado.

Reactgular
fuente