En muchos idiomas, la sintaxis function_name(arg1, arg2, ...)
se usa para llamar a una función. Cuando queremos llamar a la función sin ningún argumento, debemos hacerlo function_name()
.
Me resulta extraño que un compilador o un intérprete de script requiera ()
detectarlo con éxito como una llamada de función. Si se sabe que una variable es invocable, ¿por qué no function_name;
sería suficiente?
Por otro lado, en algunos idiomas podemos hacer: function_name 'test';
o incluso function_name 'first' 'second';
llamar a una función o un comando.
Creo que los paréntesis habrían sido mejores si solo fueran necesarios para declarar el orden de prioridad, y en otros lugares eran opcionales. Por ejemplo, hacer if expression == true function_name;
debe ser tan válido como if (expression == true) function_name();
.
Lo más molesto en mi opinión es hacerlo 'SOME_STRING'.toLowerCase()
cuando claramente la función prototipo no necesita argumentos. ¿Por qué los diseñadores decidieron en contra de lo más simple 'SOME_STRING'.lower
?
Descargo de responsabilidad: ¡No me malinterpreten, me encanta la sintaxis tipo C! ;) Solo estoy preguntando si podría ser mejor. ¿El hecho de requerir ()
tiene ventajas de rendimiento o facilita la comprensión del código? Tengo mucha curiosidad sobre cuál es exactamente la razón.
fuente
()
, pero lo que destaca en su publicación es laif (expression == true)
declaración. Te preocupas por lo superfluo()
, pero luego usa un superfluo== true
:)Respuestas:
Para los lenguajes que usan funciones de primera clase , es bastante común que la sintaxis de referirse a una función sea:
mientras que el acto de llamar a esa función es:
a
en el ejemplo anterior sería una referencia a la función anterior (y podría llamarla haciendoa()
), mientrasb
que contendría el valor de retorno de la función.Si bien algunos lenguajes pueden realizar llamadas a funciones sin paréntesis, puede resultar confuso si llaman a la función o simplemente se refieren a la función.
fuente
make_list
que agrupa sus argumentos en una lista, hay una gran diferencia entref(make_list)
pasar una funciónf
o pasar una lista vacía.SATInstance.solve
o alguna otra función pura increíblemente costosa a otra función sin intentar ejecutarla de inmediato. También hace que las cosas sean realmente incómodas sisomeFunction
hace algo diferente dependiendo de sisomeFunction
se declara que es puro. Por ejemplo, si tengo (pseudocódigo)func f() {return g}
,func g() {doSomethingImpure}
yfunc apply(x) {x()}
, a continuación,apply(f)
las llamadasf
. Si luego declaro quef
es puro, de repenteapply(f)
pasag
aapply
, yapply
llamag
y hace algo impuro.apply(f)
, sig
es impuro (¿yapply
también?), Entonces todo es impuro y se descompone, por supuesto.foo.bar()
no es una operación "llamar métodobar
de objetofoo
" sino más bien dos operaciones, "desreferenciar campobar
de objetofoo
y luego llamar al objeto devuelto desde ese campo". Entonces,.
es el operador de selector de campo y()
es el operador de llamada y son independientes.De hecho, Scala permite esto, aunque hay una convención que se sigue: si el método tiene efectos secundarios, los paréntesis deben usarse de todos modos.
Como escritor compilador, la presencia garantizada de paréntesis me parece bastante conveniente; Siempre sabría que es una llamada al método, y no tendría que construir una bifurcación para el caso extraño.
Como programador y lector de código, la presencia de paréntesis no deja dudas de que es una llamada a un método, aunque no se pasan parámetros.
El paso de parámetros no es la única característica definitoria de una llamada a método. ¿Por qué trataría un método sin parámetros diferente de un método que tiene parámetros?
fuente
def foo()
es un método con una lista de parámetros vacía, ydef foo
es un método sin lista de parámetros, ¡ y los dos son cosas diferentes ! En general, un método sin lista de parámetros debe llamarse sin lista de argumentos y un método con una lista de parámetros vacía debe llamarse con una lista de argumentos vacía. Llamar a un método sin lista de parámetros con un argumento vacío es en realidad ...apply
método del objeto devuelto por la llamada al método con una lista de argumentos vacía, mientras que llamar a un método con una lista de parámetros vacía sin una lista de argumentos puede, según el contexto, interpretarse de diversas maneras como llamar al método con una lista de argumentos vacía, η-expansión en un método parcialmente aplicado (es decir, "referencia de método"), o podría no compilarse en absoluto. Además, Scala sigue el Principio de acceso uniforme, al no distinguir (sintácticamente) entre llamar a un método sin una lista de argumentos y hacer referencia a un campo.Esto es realmente una casualidad bastante sutil de opciones de sintaxis. Hablaré idiomas funcionales, que se basan en el cálculo lambda escrito.
En dichos idiomas, cada función tiene exactamente un argumento . Lo que a menudo pensamos como "argumentos múltiples" es en realidad un único parámetro del tipo de producto. Entonces, por ejemplo, la función que compara dos enteros:
toma un solo par de enteros. Los paréntesis no denotan los parámetros de la función; se usan para emparejar patrones con el argumento. Para convencerlo de que este es realmente un argumento, podemos usar las funciones proyectivas en lugar de la coincidencia de patrones para deconstruir el par:
Por lo tanto, los paréntesis son realmente una conveniencia que nos permite combinar patrones y guardar algunos tipos de escritura. No son obligatorios
¿Qué pasa con una función que aparentemente no tiene argumentos? Tal función en realidad tiene dominio
Unit
. El miembro único deUnit
generalmente se escribe como()
, por eso aparecen los paréntesis.Para llamar a esta función, tendríamos que aplicarla a un valor de tipo
Unit
, que debe ser()
, por lo que terminamos escribiendosay_hi ()
.Entonces, ¡realmente no existe una lista de argumentos!
fuente
()
parecen a cucharas menos el mango.leq
función que usa en<
lugar de<=
es bastante confuso!()
para representar la unidad. No me sorprendería que al menos parte de la razón fuera asemejarse a una "función sin parámetros". Sin embargo, hay otra posible explicación para la sintaxis. En el álgebra de tipos,Unit
es de hecho la identidad de los tipos de productos. Un producto binario se escribe como(a,b)
, podríamos escribir un producto unario como(a)
, por lo que una extensión lógica sería escribir el producto nulo simplemente()
.En Javascript, por ejemplo, usar un nombre de método sin () devuelve la función misma sin ejecutarla. De esta manera, por ejemplo, puede pasar la función como argumento a otro método.
En Java, se eligió que un identificador seguido de () o (...) significa una llamada al método, mientras que un identificador sin () se refiere a una variable miembro. Esto podría mejorar la legibilidad, ya que no tiene dudas de si se trata de un método o una variable miembro. De hecho, se puede usar el mismo nombre tanto para un método como para una variable miembro, ambos serán accesibles con su respectiva sintaxis.
fuente
Perl Best Practices
haceObject::toString
identifica un métodoLa sintaxis sigue la semántica, así que comencemos por la semántica:
En realidad, hay varias formas:
Tener una sintaxis similar para diferentes usos es la mejor manera de crear un lenguaje ambiguo o, al menos, uno confuso (y tenemos suficientes de esos).
En C y lenguajes similares a C:
func()
&func
(C creando un puntero de función)someVariable::someMethod
por ejemplo (limitado al receptor del método, pero sigue siendo útil)Observe cómo cada uso presenta una sintaxis diferente, lo que le permite distinguirlos fácilmente.
fuente
::
operador en Java es un operador de aplicación parcial, no un operador de currículum. La aplicación parcial es una operación que toma una función y uno o más valores y devuelve una función que acepta menos valores. El curry funciona solo con funciones, no con valores.En un lenguaje con efectos secundarios, la OMI es realmente útil para diferenciar (en teoría, sin efectos secundarios) la lectura de una variable
y llamar a una función, que podría generar efectos secundarios
OTOH, si no hay efectos secundarios (aparte de los codificados en el sistema de tipos), entonces no tiene sentido incluso diferenciar entre leer una variable y llamar a una función sin argumentos.
fuente
noun.verb
la sintaxis podría tener un significado tan claronoun.verb()
y un poco menos abarrotado. Del mismo modo, una función quemember_
devuelve una referencia izquierda podría ofrecer una sintaxis amigable que una función de establecimiento típica:obj.member_ = new_value
vs.obj.set_member(new_value)
(el_
postfix es una pista que dice "expuesto" que recuerda_
prefijos para funciones "ocultas").noun.verb
significa invocación de función, ¿cómo lo diferencia del simple acceso de miembro?Ninguna de las otras respuestas ha intentado abordar la pregunta: ¿cuánta redundancia debería haber en el diseño de un lenguaje? Porque incluso si puede diseñar un lenguaje que
x = sqrt y
establezca x en la raíz cuadrada de y, eso no significa que necesariamente deba hacerlo.En un lenguaje sin redundancia, cada secuencia de caracteres significa algo, lo que significa que si comete un solo error, no recibirá un mensaje de error, su programa hará lo incorrecto, lo que podría ser algo muy diferente de lo que usted intencionado y muy difícil de depurar. (Como cualquiera que haya trabajado con expresiones regulares lo sabrá). Un poco de redundancia es algo bueno porque permite detectar muchos de sus errores, y cuanto más redundancia haya, más probable es que los diagnósticos sean precisos. Ahora, por supuesto, puede llevar eso demasiado lejos (ninguno de nosotros quiere escribir en COBOL en estos días), pero hay un equilibrio correcto.
La redundancia también ayuda a la legibilidad, porque hay más pistas. El inglés sin redundancia sería muy difícil de leer, y lo mismo ocurre con los lenguajes de programación.
fuente
:=
y la comparación==
, y=
solo se puede utilizar para la inicialización en tiempo de compilación, entonces la probabilidad de errores tipográficos convertirá las comparaciones en tareas disminuirá considerablemente.()
invocación de una función de argumento cero, pero también requeriría un token para "tomar la dirección de" una función. La primera acción es mucho más común, por lo que guardar un token en el caso menos común no es tan útil.En la mayoría de los casos, estas son elecciones sintácticas de la gramática del lenguaje. Es útil para la gramática que las diversas construcciones individuales sean (relativamente) inequívocas cuando se toman en conjunto. (Si hay ambigüedades, como en algunas declaraciones de C ++, debe haber reglas específicas para la resolución). El compilador no tiene la libertad para adivinar; se requiere seguir la especificación del idioma.
Visual Basic, en varias formas, diferencia entre procedimientos que no devuelven un valor y funciones.
Los procedimientos deben llamarse como una declaración y no requieren parens, solo argumentos separados por comas, si los hay. Las funciones deben llamarse como parte de una expresión y requieren los parens.
Es una distinción relativamente innecesaria que hace que la refactorización manual entre las dos formas sea más dolorosa de lo que debe ser.
(Por otra parte de Visual Basic utiliza los mismos parens
()
para las referencias de la matriz como para las llamadas de función, por lo que las referencias de la matriz se parece a las llamadas de función. Y esto facilita la refactorización el manual de una matriz en una llamada de función! Por lo tanto, podríamos considerar otros lenguajes usan[]
's para referencias de matriz, pero estoy divagando ...)En los lenguajes C y C ++, se accede automáticamente al contenido de las variables utilizando su nombre, y si desea hacer referencia a la variable en lugar de su contenido, aplica el
&
operador unario .Este tipo de mecanismo también podría aplicarse a los nombres de funciones. El nombre de la función sin procesar podría implicar una llamada a la función, mientras que un
&
operador unario se usaría para referirse a la función (en sí misma) como datos. Personalmente, me gusta la idea de acceder a funciones sin argumentos sin efectos secundarios con la misma sintaxis que las variables.Esto es perfectamente plausible (al igual que otras opciones sintácticas para esto).
fuente
call <procedure name> (<arguments>)
? Estoy bastante seguro de que era una forma válida de usar los procedimientos cuando hice la programación QuickBASIC en otra vida ...Call
un método donde casi nunca lo necesito en el código de producción "normal".Consistencia y legibilidad.
Si me entero de que se llama a la función de
X
la siguiente manera:X(arg1, arg2, ...)
, entonces espero que funcione de la misma manera para que no hay argumentos:X()
.Ahora, al mismo tiempo, aprendo que puedes definir la variable como un símbolo y usarlo así:
Ahora, ¿qué pensaría cuando encuentre esto?
Tu invitado es tan bueno como el mío. En circunstancias normales, el
X
es una variable. Pero si tomáramos su camino, ¡también podría ser una función! ¿Debo recordar si qué símbolo se asigna a qué grupo (variables / funciones)?Podríamos imponer restricciones artificiales, por ejemplo, "Las funciones comienzan con mayúscula. Las variables comienzan con minúscula", pero eso es innecesario y complica las cosas, aunque a veces puede ayudar a algunos de los objetivos de diseño.
Nota al margen 1: Otras respuestas ignoran por completo una cosa más: el lenguaje puede permitirle usar el mismo nombre para funciones y variables, y distinguir por contexto. Ver
Common Lisp
como un ejemplo. Funciónx
y variablex
coexisten perfectamente bien.Nota lateral 2: La respuesta aceptada nos muestra la sintaxis:
object.functionname
. En primer lugar, no es universal para los idiomas con funciones de primera clase. En segundo lugar, como programador trataría esto como una información adicional:functionname
pertenece a unobject
. Siobject
es un objeto, una clase o un espacio de nombres no importa mucho, pero me dice que pertenece a algún lado . Esto significa que debe agregar una sintaxis artificialobject.
para cada función global o crear algunaobject
para contener todas las funciones globales.Y de cualquier manera, pierde la capacidad de tener espacios de nombres separados para funciones y variables.
fuente
Para agregar a las otras respuestas, tome este ejemplo de C:
Si el operador de invocación fuera opcional, no habría forma de distinguir entre la asignación de función y la llamada a función.
Esto es aún más problemático en los idiomas que carecen de un sistema de tipos, por ejemplo, JavaScript, donde la inferencia de tipos no se puede utilizar para descubrir qué es y qué no es una función.
fuente
function cross(a, b) { return a + b; }