La mayoría de los lenguajes de programación (lenguajes de tipo dinámico y estático) tienen palabras clave y / o sintaxis especiales que se ven muy diferentes a las variables de declaración para funciones de declaración. Veo funciones tan solo como declarar otra entidad con nombre:
Por ejemplo en Python:
x = 2
y = addOne(x)
def addOne(number):
return number + 1
Por qué no:
x = 2
y = addOne(x)
addOne = (number) =>
return number + 1
Del mismo modo, en un lenguaje como Java:
int x = 2;
int y = addOne(x);
int addOne(int x) {
return x + 1;
}
Por qué no:
int x = 2;
int y = addOne(x);
(int => int) addOne = (x) => {
return x + 1;
}
Esta sintaxis parece una forma más natural de declarar algo (ya sea una función o una variable) y una palabra clave menos como def
o function
en algunos idiomas. Y, en mi opinión, es más consistente (busco en el mismo lugar para comprender el tipo de una variable o función) y probablemente hace que el analizador / gramática sea un poco más simple de escribir.
Sé que muy pocos lenguajes utilizan esta idea (CoffeeScript, Haskell) pero los lenguajes más comunes tienen una sintaxis especial para funciones (Java, C ++, Python, JavaScript, C #, PHP, Ruby).
Incluso en Scala, que admite ambas formas (y tiene inferencia de tipos), es más común escribir:
def addOne(x: Int) = x + 1
Más bien que:
val addOne = (x: Int) => x + 1
OMI, al menos en Scala, esta es probablemente la versión más fácil de entender, pero este modismo rara vez se sigue:
val x: Int = 1
val y: Int = addOne(x)
val addOne: (Int => Int) = x => x + 1
Estoy trabajando en mi propio lenguaje de juguetes y me pregunto si hay algún inconveniente si diseño mi lenguaje de tal manera y si hay alguna razón histórica o técnica por la que este patrón no se sigue ampliamente.
(int => int) addOne = (x) => {
es mucho más "especial" y "complejo" queint addOne(int) {
...Respuestas:
Creo que la razón es que los lenguajes más populares provienen o fueron influenciados por la familia de lenguajes C en lugar de lenguajes funcionales y su raíz, el cálculo lambda.
Y en estos idiomas, las funciones no son solo otro valor:
const
,readonly
ofinal
para prohibir mutación), pero las funciones no pueden ser reasignados.Desde una perspectiva más técnica, el código (que se compone de funciones) y los datos están separados. Por lo general, ocupan diferentes partes de la memoria, y se accede a ellas de manera diferente: el código se carga una vez y luego solo se ejecuta (pero no se lee ni se escribe), mientras que los datos a menudo se asignan y desasignan constantemente y se escriben y leen, pero nunca se ejecutan.
Y dado que C estaba destinado a estar "cerca del metal", tiene sentido reflejar esta distinción en la sintaxis del lenguaje también.
El enfoque de "función es solo un valor" que forma la base de la programación funcional ha ganado fuerza en los lenguajes comunes solo relativamente recientemente, como lo demuestra la introducción tardía de lambdas en C ++, C # y Java (2011, 2007, 2014).
fuente
Es porque es importante que los humanos reconozcan que las funciones no son solo "otra entidad con nombre". A veces tiene sentido manipularlos como tales, pero aún pueden reconocerse de un vistazo.
Realmente no importa lo que la computadora piense acerca de la sintaxis, ya que un bloque de caracteres incomprensible está bien para que una máquina lo interprete, pero eso sería casi imposible de entender y mantener para los humanos.
Realmente es la misma razón por la que tenemos bucles while y for, switch y, de lo contrario, etc., aunque todos finalmente se reducen a una instrucción de comparación y salto. La razón es porque está ahí para el beneficio de los humanos que mantienen y entienden el código.
Tener sus funciones como "otra entidad con nombre" de la manera que está proponiendo hará que su código sea más difícil de ver y, por lo tanto, más difícil de entender.
fuente
whatsisname
fue más abordar el primer punto (y alertar sobre algún peligro de eliminar estos a prueba de fallas), mientras que su comentario está más relacionado con la segunda parte de la pregunta. De hecho, es posible cambiar esta sintaxis (y como usted describió, ya se ha hecho muchas veces ...) pero no se adaptará a todos (ya que oop tampoco se adapta a todos.)Quizás le interese saber que, en tiempos prehistóricos, un lenguaje llamado ALGOL 68 usaba una sintaxis cercana a la que usted propone. Reconociendo que los identificadores de función están vinculados a valores al igual que otros identificadores, podría en ese lenguaje declarar una función (constante) utilizando la sintaxis
Concretamente tu ejemplo leería
Reconociendo la redundancia en que el tipo inicial puede leerse desde el RHS de la declaración, y al ser un tipo de función siempre comienza
PROC
, esto podría (y generalmente sería) contratadopero tenga en cuenta que
=
todavía aparece antes de la lista de parámetros. También tenga en cuenta que si desea una variable de función (a la que luego se le podría asignar otro valor del mismo tipo de función),=
debe reemplazarse por:=
, dando cualquiera deSin embargo, en este caso ambas formas son, de hecho, abreviaturas; Como el identificador
func var
designa una referencia a una función generada localmente, la forma completamente expandida seríaEs fácil acostumbrarse a esta forma sintáctica particular, pero claramente no tenía muchos seguidores en otros lenguajes de programación. Incluso los lenguajes de programación funcionales como Haskell prefieren el estilo
f n = n+1
con la=
siguiente lista de parámetros. Supongo que la razón es principalmente psicológica; después de todo, incluso los matemáticos no suelen preferir, como yo, f = n ⟼ n + 1 sobre f ( n ) = n + 1.Por cierto, la discusión anterior resalta una diferencia importante entre las variables y las funciones: las definiciones de funciones generalmente vinculan un nombre a un valor de función específico, que no se puede cambiar más tarde, mientras que las definiciones de variables generalmente introducen un identificador con un valor inicial , pero uno que Puede cambiar más tarde. (No es una regla absoluta; las variables de función y las constantes que no son de función se producen en la mayoría de los idiomas). Además, en los lenguajes compilados, el valor vinculado en una definición de función suele ser una constante de tiempo de compilación, por lo que las llamadas a la función pueden ser compilado usando una dirección fija en el código. En C / C ++ esto es incluso un requisito; el equivalente del ALGOL 68
no se puede escribir en C ++ sin introducir un puntero de función. Este tipo de limitaciones específicas justifican el uso de una sintaxis diferente para las definiciones de funciones. Pero dependen de la semántica del lenguaje, y la justificación no se aplica a todos los idiomas.
fuente
Mencionaste Java y Scala como ejemplos. Sin embargo, pasó por alto un hecho importante: esas no son funciones, son métodos. Los métodos y funciones son fundamentalmente diferentes. Las funciones son objetos, los métodos pertenecen a los objetos.
En Scala, que tiene funciones y métodos, existen las siguientes diferencias entre métodos y funciones:
Por lo tanto, su reemplazo propuesto simplemente no funciona, al menos para esos casos.
fuente
Las razones que puedo pensar son:
fuente
Cambiando la pregunta, si uno no está interesado en tratar de editar el código fuente en una máquina que está extremadamente limitada por RAM, o minimizar el tiempo para leerlo en un disquete, ¿qué tiene de malo usar palabras clave?
Ciertamente, es más agradable de leer
x=y+z
questore the value of y plus z into x
, pero eso no significa que los caracteres de puntuación sean inherentemente "mejores" que las palabras clave. Si las variablesi
,j
yk
sonInteger
, yx
esReal
, considere las siguientes líneas en Pascal:La primera línea realizará una división entera truncada, mientras que la segunda realizará una división de números reales. La distinción se puede hacer muy bien porque Pascal usa
div
como su operador de división de números enteros truncados, en lugar de tratar de usar un signo de puntuación que ya tiene otro propósito (división de números reales).Si bien hay algunos contextos en los que puede ser útil hacer que una definición de función sea concisa (por ejemplo, una lambda que se usa como parte de otra expresión), generalmente se supone que las funciones se destacan y son fácilmente reconocibles visualmente como funciones. Si bien podría ser posible hacer la distinción mucho más sutil y usar nada más que caracteres de puntuación, ¿cuál sería el punto? Decir
Function Foo(A,B: Integer; C: Real): String
deja en claro cuál es el nombre de la función, qué parámetros espera y qué devuelve. Tal vez uno podría acortarlo en seis o siete caracteres reemplazándolosFunction
con algunos caracteres de puntuación, pero ¿qué se ganaría?Otra cosa a tener en cuenta es que en la mayoría de los marcos existe una diferencia fundamental entre una declaración que siempre asociará un nombre con un método particular o un enlace virtual particular, y uno que crea una variable que inicialmente identifica un método o enlace particular, pero podría cambiarse en tiempo de ejecución para identificar a otro. Dado que estos son conceptos semánticamente muy diferentes en la mayoría de los marcos de procedimientos, tiene sentido que tengan una sintaxis diferente.
fuente
void f() {}
realidad es más corto que el equivalente lambda en C ++ (auto f = [](){};
), C # (Action f = () => {};
) y Java (Runnable f = () -> {};
). La concisión de lambdas proviene de la inferencia de tipos y la omisiónreturn
, pero no creo que eso esté relacionado con lo que plantea esta pregunta.Bueno, la razón podría ser que esos idiomas no son lo suficientemente funcionales, por así decirlo. En otras palabras, rara vez define funciones. Por lo tanto, el uso de una palabra clave adicional es aceptable.
En los idiomas de la herencia ML o Miranda, OTOH, usted define funciones la mayoría de las veces. Mire algún código de Haskell, por ejemplo. Literalmente es principalmente una secuencia de definiciones de funciones, muchas de ellas tienen funciones locales y funciones locales de esas funciones locales. Por lo tanto, una palabra clave divertida en Haskell sería un error tan grande como requerir una declaración de evaluación en un lenguaje imperativo para comenzar con la asignación . La asignación de causa es, con mucho, la declaración más frecuente.
fuente
Personalmente, no veo ningún defecto fatal en tu idea; puede encontrar que es más complicado de lo que esperaba expresar ciertas cosas usando su nueva sintaxis, y / o puede encontrar que necesita revisarla (agregando varios casos especiales y otras características, etc.), pero dudo que se encuentre necesitando abandonar la idea por completo.
La sintaxis que ha propuesto se parece más o menos a una variante de algunos de los estilos de notación que a veces se utilizan para expresar funciones o tipos de funciones en matemáticas. Esto significa que, como todas las gramáticas, probablemente atraerá más a algunos programadores que a otros. (Como matemático, me gusta).
Sin embargo, debe tener en cuenta que en la mayoría de los idiomas, la
def
sintaxis de estilo (es decir, la sintaxis tradicional) se comporta de manera diferente a una asignación de variable estándar.C
yC++
, las funciones generalmente no se tratan como "objetos", es decir, fragmentos de datos mecanografiados para copiar y colocar en la pila y otras cosas. (Sí, puede tener punteros de función, pero todavía apuntan a código ejecutable, no a "datos" en el sentido típico).self
(que, por cierto, no es realmente una palabra clave; podría hacer que cualquier identificador válido sea el primer argumento de un método).Debe considerar si su nueva sintaxis representa con precisión (y con suerte de forma intuitiva) lo que el compilador o el intérprete realmente está haciendo. Puede ser útil leer, por ejemplo, la diferencia entre lambdas y métodos en Ruby; esto le dará una idea de cómo su paradigma de funciones son solo datos difiere del paradigma típico de OO / procedimiento.
fuente
Para algunos idiomas, las funciones no son valores. En tal lenguaje para decir que
es una definición de función, mientras que
declara una constante, es confuso porque está usando una sintaxis para significar dos cosas.
Otros lenguajes, como ML, Haskell y Scheme, tratan las funciones como valores de primera clase, pero proporcionan al usuario una sintaxis especial para declarar constantes con valores de función. * Están aplicando la regla que "el uso acorta la forma". Es decir, si una construcción es común y detallada, debe darle al usuario una abreviatura. No es elegante darle al usuario dos sintaxis diferentes que significan exactamente lo mismo; a veces la elegancia debe ser sacrificada por la utilidad.
Si, en su idioma, las funciones son de primera clase, ¿por qué no tratar de encontrar una sintaxis lo suficientemente concisa como para no sentirse tentado a encontrar un azúcar sintáctico?
- Editar -
Otro problema que nadie ha planteado (todavía) es la recursividad. Si usted permite
y tu permites
¿se sigue que debes permitir
En un lenguaje vago (como Haskell), no hay problema aquí. En un lenguaje esencialmente sin controles estáticos (como LISP), no hay ningún problema aquí. Pero en un lenguaje entusiasta revisado estáticamente, debe tener cuidado con la forma en que se definen las reglas de la verificación estática, si desea permitir las dos primeras y prohibir las últimas.
- Fin de la edición -
* Se podría argumentar que Haskell no pertenece a esta lista. Proporciona dos formas de declarar una función, pero ambas son, en cierto sentido, generalizaciones de la sintaxis para declarar constantes de otros tipos.
fuente
Esto puede ser útil en lenguajes dinámicos donde el tipo no es tan importante, pero no es tan legible en lenguajes tipados estáticos donde siempre desea saber el tipo de su variable. Además, en lenguajes orientados a objetos es muy importante saber el tipo de su variable, para saber qué operaciones admite.
En su caso, una función con 4 variables sería:
Cuando miro el encabezado de la función y veo (x, y, z, s) pero no sé los tipos de estas variables. Si quiero saber qué tipo
z
es el tercer parámetro, tendré que mirar el comienzo de la función y comenzar a contar 1, 2, 3 y luego ver que el tipo esdouble
. De la primera manera miro directamente y veodouble z
.fuente
var addOne = (int x, long y, double z, String s) => { x + 1 }
en un lenguaje de tipo estático no estúpido de su elección (ejemplos: C #, C ++, Scala). Incluso la inferencia de tipo local muy limitada utilizada por C # es completamente suficiente para esto. Entonces, esta respuesta es simplemente criticar una sintaxis específica que es dudosa en primer lugar, y que en realidad no se usa en ningún lugar (aunque la sintaxis de Haskell tiene un problema muy similar).Hay una razón muy simple para tener esa distinción en la mayoría de los idiomas: es necesario distinguir la evaluación y la declaración . Tu ejemplo es bueno: ¿por qué no te gustan las variables? Bueno, las expresiones de variables se evalúan de inmediato.
Haskell tiene un modelo especial donde no hay distinción entre evaluación y declaración, por lo que no hay necesidad de una palabra clave especial.
fuente
Las funciones se declaran de manera diferente a los literales, objetos, etc. en la mayoría de los idiomas porque se usan de manera diferente, se depuran de manera diferente y plantean diferentes fuentes potenciales de error.
Si una referencia de objeto dinámico o un objeto mutable se pasa a una función, la función puede cambiar el valor del objeto mientras se ejecuta. Este tipo de efecto secundario puede dificultar el seguimiento de lo que hará una función si está anidada dentro de una expresión compleja, y este es un problema común en lenguajes como C ++ y Java.
Considere depurar algún tipo de módulo de kernel en Java, donde cada objeto tiene una operación toString (). Si bien puede esperarse que el método toString () restaure el objeto, es posible que deba desmontar y volver a ensamblar el objeto para traducir su valor a un objeto String. Si está tratando de depurar los métodos que toString () llamará (en un escenario de enlace y plantilla) para hacer su trabajo, y resalte accidentalmente el objeto en la ventana de variables de la mayoría de los IDE, puede bloquear el depurador. Esto se debe a que el IDE intentará toString () el objeto que llama al código que está en proceso de depuración. Ningún valor primitivo es tan malo porque el lenguaje, no el programador, define el significado semántico de los valores primitivos.
fuente