El balance entre el uso de métodos como operadores.
Estaba escribiendo una respuesta al post de ezamudio Operadores en Ceylon pero como siempre me extendí más de la cuenta y mejor cree esta entrada separada.
El tema es la sobre carga de operadores en Ceylon.
Es interesante tratar de alcanzar el balance entre el uso de operadores y métodos en un lenguaje de programación.
Por un lado, si se permite que cualquier método se pueda utilizar como operador, se puede terminar creando código que solamente el autor del programa o la biblioteca entienda y eso cuando aún lo tiene fresco en la mente. Me parece que este es una de las críticas más fuertes hacía C++ y a Scala ( aunque Scala además permite una sintaxis diferente para la invocación de métodos que es sin usar punto )
Por otro lado si se prohibe del todo se tiene un lenguaje que es sumamente claro y fácil de entender por cualquiera pero que resulta tedioso porque el programador tiene que escribir toooodo el nombre del método y no simplemente un operador ( Java , Javascript y Go por ejemplo )
Y por último están los lenguajes "flexibles" ( llamémoslos así ) como Ruby y ahora Ceylon donde está permitido cierto número de operadores pero no se pueden crear nuevos, el inconveniente inicial es conocer el conjunto de operadores que pueden ser sobrecargados por un método pero una vez superada es curva inicial no hay más sorpresas.
Si el lenguaje es Ceylon o Ruby, ya sé que es lo que pasa aquí:
En algún momento yo pensé que la mejor opción era que simplemente se pudiera utilizar cualquier nombre en los métodos para esto. Entonces, si se quiere escribir
en vez de
o
basta con tener un método que se llame
e invocarlo. Se vería así:
Lo cual al verlo es ( o debería de ser ) reconocido por el que lee como una invocación a un método: hay un objeto a la izquierda, luego un punto, luego algo y luego paréntesis con un argumento y esto para alguien con experiencia en Java, C++, C#, y hasta en C podría resultar familiar.
Hasta ahí todo bien, el problema viene cuando se empiezan a combinar más y más métodos y curiosamente lo que empieza a meter ruido son los paréntesis y el punto.
Compare:
vs.
Sin importar cual es la semántica ( que quiere decir esta sentencia ) la segunda parece más limpia que la primera. Lo que han hecho otros lenguajes de programación para solucionar esto, es hacer el uso de paréntesis y de el punto algo opcional, con lo cual se va eliminando el ruido, pero se termina con varias formas de escribir lo mismo lo cual crea estilos diferentes para un mismo lenguaje.
Caminando más hacia atrás en la historia tenemos que lenguajes como Smalltalk lo tenían claro desde entonces.
Lo anterior sería en Smalltalk sería escrito así:
Dadaaa! tal como lo queríamos en primer lugar, pero ahí el inconveniente está ahora en los métodos con nombres más largos:
Y ahora nadie sabe de forma intuitiva que método le corresponde a quién ni quien es el objeto ni cual es el método. ejemplo
vs.
Otro inconveniente ( en Smalltalk ) es la precedencia de operadores ... bueno, el problema básicamente es que no hay porque no hay operadores.
No es lo que alguien que sepa C y sus descendientes esperan. Por cierto, alguien sabe de donde fregaos salió que la multiplicación tiene precedencia a la suma? ( me refiero a las matemáticas ) En fin volviendo al tema.
Total que no hay una forma sencilla, quizá la respuesta la tienen los lenguajes de programación concatenativos ( Factor por ejemplo ) , pero la mera verdad, la primera vez que vi una sentencia escrita en uno de esos no entendí naaada, sin embargo después de ver una explicación todo me pareció maravilloso.
Lo que concluyo entonces es que lo que parece simple y natural para algunos puede ser totalmente ajeno, extraño y hasta ofensivo para otros ( bueno no al punto de ofensivo :P ) pero después de conocerlo un poquito más todo parece tan claro.
Por ejemplo la implementación de QuickSort en Haskell para quien lo conoce será un ejemplo de elegancia y de legibilidad y para quien no lo conoce pues un montón de letritas ahí puestas unas con otras.
Por cierto más tarde me enteré que este no un verdadero quicksort, pero ese ya es otro tema
Este es el tema con los lenguajes de programación, siempre se está buscando encontrar nuevas y mejores formas de expresar los programas. Desde el inicio con lenguajes como COBOL la intención es hacer claro y sencillo para el humano cual es la intención del programa, el punto importante a notar es que claro y sencillo es algo subjetivo hasta cierto punto y de ahí la divergencia de opiniones.
Me parece muy bueno que Ceylon tome un camino pragmático en cuanto a los operadores y la sobrecarga de los mismos. Así se busca entonces alcanzar el balance entre flexibilidad y legibilidad.
- OscarRyz's blog
- Inicie sesión o regístrese para enviar comentarios
Interesante entrada.
El punto es que depende el lenguaje de programación como bien mencionas...Por ejemplo en Ruby (y creo que también en Groovy) TODO es un objeto. De modo que no hay operadores sino métodos, y por ejemplo cuando tu das en Ruby "1 - 3" realmente "-" es un método de instancia para objetos de tipo numérico, en éste caso Fixnum.
De Ceylon, no conozco mucho y de verdad no me llama la atención, no por que se vea mal, sino porqué me sigue pareciendo imperativo y de momento quiero aprender algo más funcional sin dejar mis preciados objetos que tanto me costó dominar xD. Pero, de lo que nadie habla es si es tan "objetal" como Ruby o Scala, en donde en realidad no estás usando operadores sino métodos.
Claro, esta respuesta palabras de un novato son. Sin embargo, es interesante saber como funciona la cosa y pues preguntar lo que uno no sabe.
Creo que para la entrada se hubiera podido poner un ejemplo de Python en el lado de los lenguajes muy flexibles por su "naturaleza" híbrida en cuanto a paradigmas se refiere, en donde muchos llegaran a pensar que son lenguajes hechos para herejes totales xD.
En fin, muy buena entrada.
Python no
Ojo que acá hablo solamente de su relación con la posibilidad de usar métodos ( o funciones ) en vez de operadores. En Python esto no es posible, no se puede escribir el método + ( más )
Ejemplo de Ruby y Python definiendo el método +
Aunque Ruby es OO puro aún así tiene truquitos para la precedencia de los operadores por ejemplo:
Se ejecuta primero el por u luego el plus ( en otras palabras ejecuta esto:
) .
Ceylon hace lo mismo, pero en ambos lenguajes los operadores con los que se puede hacer esto es limitado
Así que Python en este sentido es igual de Java, JavaScript y Go
No hay sobrecarga de operadores en Scala
Si nos referimos al concepto sobrecarga de operadores como la posibilidad de usar un operador "tradcional" y que el compilador sustituya el operador como la invocación de un método con un nombre específico sobre el valor del lado izquierdo del operador, entonces C++ tiene sobrecarga de operadores (
según sea el caso), pero Scala no lo tiene aunque la sustitución de
sea similar.
como identificador, mientras que Scala sí. Lo que Scala permite es la notación infija para los métodos que aceptan un argumento (unary methods) y la notación postfija para los métodos sin argumentos (nullary methods).
como la invocación a una función
que es lo que realmente sucede en la implementación, por supuesto después de toda comprobación que esa función puede operar sobre ese objeto.
C++ no permite al símbolo
No veo difícil aceptar la notación infija que utiliza Scala y que también utiliza Ceylon.
Es un problema de acostumbrarse a ese tipo de notación en un lenguaje orientado a objetos, sobre todo para los lenguajes orientados a objetos puros donde todo valor es un objeto. A la larga, los que llegamos de los lenguajes procedurales hace muchos años nos acostumbramos a la notación con el operador punto (.) para enviar mensajes a los objetos. Inicialmente interpretabamos la expresión
Con respecto a la precedencia de la multiplicación sobre la suma, no es ningún misterio. Es un postulado del álgebra, que se establece sin necesidad de demostración, como las leyes asociativas, de clausuras, de distributividad, etc. A partir de esos postulados se prueban teoremas que son válidos para las diferentes álgebras que se creen, como es el caso de la aritmética.
Tampoco en Ceylon
Ceylon no tiene sobrecarga de operadores. Lo que tiene se llama polimorfismo de operadores: puedes modificar el comportamiento de algunos operadores existentes cuando se usan sobre tus objetos (empezando por permitir que se usen en tus objetos), de manera muy formal: implementas una interfaz (con generics) para indicar el comportamiento que quieres ofrecer. Por ejemplo para usar el + con tus objetos:
Las clases Integer y Float por ejemplo, implementan Summable (no de manera tan simple como lo puse aqui, porque de hecho permiten sumar Integer con Float y Float con Integer, es interesante ver esa definicion).
En Scala lo que tienes es que puedes definir metodos con nombre "normalito" o sea puras letras, numeros y subguiones, o bien puedes usar caracteres especiales, cualquier combinacion, con la excepcion de dos puntos porque esos se usan para indicar la asociatividad del metodo. Esto, junto con la opcion de usar espacios en vez de punto y parentesis para invocar un metodo que tiene solamente un parametro (o definir metodos que reciben grupos de parametros, separados por parentesis), es lo que da la ilusion de que haya sobrecarga de operadores. El Dr. Ferro conoce mucho mejor los terminos y semantica de esto, yo solamente hablo aqui del efecto final visto de manera muy practica.
Todo eso te permite hacer tarugadas como esta que hice (con humor), pero donde puedes ver que pues al final tiene su chiste entenderle a esa ultima linea de codigo que generalmente se consideraria graffiti en ASCII pero en Scala estoy usando implicits, singletons, la sintaxis "funcional" del metodo apply, y referencias a metodos.
Imperativo vs funcional?
Imperativo vs funcional? segun yo la clasificacion es imperativo o declarativo y son estilos... OO vs funcional es otra clasificacion y ahi son paradigmas.
En cuanto a Ceylon y la programacion funcional, en la pagina oficial hay algo al respecto.
Imperativo también es un
Imperativo también es un paradigma, aunque suena paradójico que un lenguaje de programación tenga varios paradigmas, pero así es, por eso a algunos ( más bien casi todos ) son multiparadigmas.
Quizá el error es llamarlo "paradigma" en primer lugar, pero quizá es una de esas cosas que les pasa a los gringos.
Funcional es cuando la materia prima que se usa para construir son funciones. Cuando la materia prima son objetos entonces es orientado a objetos no importando que en uno se pueda simular hacer lo que tiene el otro ( ver por ejemplo lambda4j )
Imperativo vs funcional
Es correcto (en mi humilde opinión) hacer la comparación entre lo imperativo y los funcional atendiendo a varias cosas entre ellas:
En los lenguajes imperativos las construcciones básicas son sentencias o estatutos imperativos: cambiar valores existentes (el estado de un programa), etc.
En los lenguajes funcionales las construcciones básicas son declarativas: declarar nuevos valores, etc., y la computación fluye mediante la evaluación de expresiones.
La programación funcional en cierto sentido es programación declarativa, lo que no quiere decir que toda la programación declarativa se funcional. En matemática operamos normalmente con descripciones declarativas, mientras que en computación trabajamos más con descripciones imperativas.
Definiciones Laxas
Esas son definiciones muy laxas, no? Entonces C es un lenguaje funcional? porque solamente tiene funciones, y puedes pasar a una funcion un apuntador a otra funcion, etc. O es orientado a objetos? porque si hago structs y en algunos campos de esas structs meto apuntadores a funciones, pues son como objetos...
Una posible respuesta es que podemos usar C de cualquiera de las dos formas. La realidad es que C ni es funcional ni es orientado a objetos; es un lenguaje procedural y hablando de imperativo o declarativo, es totalmente imperativo. Pero cuando dices que un lenguaje es orientado a objetos, es porque los objetos son intrinsecos en ese lenguaje, tiene palabras reservadas y varias cosas para manejar objetos. Cuando es funcional, tiene palabras reservadas y varias cosas para manejar funciones, y debe soportar que con una sintaxis sencilla puedas hacer funciones que reciban funciones y que devuelvan funciones. Y en realidad, cuando hablas de lenguajes funcionales, a lo que hacen referencia con el termino "funcion" es a la acepcion matematica, que implica NO EFECTOS SECUNDARIOS. Si haces funciones que modifican cosas, estrictamente hablando eso no es programacion funcional, aunque uses puras funciones y uses puro estilo declarativo. Y hacer programacion puramente funcional, en este sentido estricto, es REALMENTE MUY DIFICIL.
Ceylon puede usarse "en estilo funcional" porque soporta funciones de primer nivel y pronto va a tener ya soporte para funciones de orden superior (recuerden que esta apenas la version 0.1 liberada y por eso no tiene muchas cosas aun). Es decir, en Ceylon puedes hacer un programa con puras funciones, y hacer funciones que reciben funciones como parametros y que devuelven funciones, y manejarias los tipos existentes solamente y no crearias tipos adicionales. O lo puedes usar como OOP y crear tus jerarquias de clases y hacer tus componentes y aprovechar al maximo el sistema de tipos. O puedes hacer una combinacion, porque puedes obtener una referencia a un metodo y pasarla como parametro a una funcion o a otro metodo. Puedes tener un metodo que devuelva una funcion (o una referencia a otro metodo, pero para el receptor es lo mismo). Pero Ceylon es principalmente un lenguaje orientado a objetos. El aspecto "funcional" (lo digo en el sentido laxo de moda hoy en dia, que es simplemente que puedes manejar funciones de orden superior) esta ahi porque es muy util y le da mucho poder al programador.
Noupe, C no dice que pasa
Noupe, C no dice que pasa funciones, dice que maneja apuntadores. De la misma forma maneja estructuras con datos, pero no les llama objetos.
Si tienes un lenguaje funcional con efectos secundarios, lo que tienes es un lenguaje funcional impuro, nada más. Lisp, el papá de muchos lenguajes es funcional impuro.
Java es OO pero impuro también
Scala en Funcional y OO ( de ahí que se le llame Object Functional ) porque sus construcciones se basan en objetos y en funciones.
Ceylon quizá podría caer en esta categoría ( personalmente espero que no ) solo con aceptar esos conceptos en su vocabulario.
En Smalltalk por ejemplo ( OO puro ) , el paso de bloques de código ( que van entre corchetes [] ) es fundamental para el lenguaje, sin ellos no se podría escribir ni un if, y sin embargo jamás se le ha llamado funcional, simplemente porque no explica lo que hace en términos de funciones, sino en términos de objetos. Es decir, lo que recibe no es una función, sino un bloque de código, aunque en naturaleza, comportamiento y en prácticamente todo se parezcan muchísimo.
Recuerden la enseñanza del maestro Qc Na
Todos los valores en Scala son objetos
Scala es un lenguaje oientado a objetos con "cosmética" funcional. Sus construcciones se basan exclusivamente en objetos y su sintaxis permite declarar valores y funciones al estilo funcional. Toda función es un objeto instancia de una clase que implementa algún trait.
Esa cosmética de Scala
Creo que es bueno siempre recordar que la evaluación de la siguiente expresión lambda:
No es otra cosa que:
Entenderlo de esta última forma (y por supuesto después usar la primera) es saludable.
Y también lo es (_+1) en
Y también lo es
en
, sin embargo a mi me parece que más bien se les fue o no pudieron esconder esa implementación, más que sea una característica del lenguaje, el hecho que tengan de
a
habla más bien de la imposibilidad de hacer chequeo estático sin ellos.
Bueno al menos eso fue lo que yo entendí y lo hice igual
Sin embargo Scala les dice explicitamente "función", y "literal de función" y "función de alto nivel" etc y me parece ( me parece ) que más bien usar objetos obedece a una limitación en de la plataforma para la cual compila este lenguaje que a una decisión de diseño.
funciones en Ceylon
En Ceylon sí hay funciones de primer nivel. no necesitan estar dentro de un objeto o ser definidas como objetos, aunque sí hay una clase que las representa: Callable.
En Java se compilan a clases con un metodo estatico y todas las llamadas a f() en Ceylon se traducen a f.f() en java.
Hola Barbaro,En los
Hola Barbaro,
Standard ML me deja escribir:
Entonces es un lenguaje imperativo?
Lisp me deja escribir (si tengo la syntaxis correcta):
Lisp tambien es un lenguaje imperativo?
Haskell tiene varios Monads como State y IO que me dejan escribir codigo en estilo imperativo. Hasta Haskell is un lenguaje imperativo!
No, no creo que tiene mucho sentido describir un lenguaje como "funcional" o "no funcional". Nego que es una caracteristica del lenguaje mismo. Lo que importa mas es como yo (como programador) utilizo el lenguaje.
Yo puedo escribir codigo funcional en Java si me da ganas hacerlo.
Hehe, para invitarles a verlo
Hehe, para invitarles a verlo de otra perspectiva...
Existe la idea - en un nivel aun mas abstracto que la especificacion del lenguaje - que una clase en Ceylon es de hecho una funcion que retorna la clausura de sus propias declaraciones locales.
Compara:
Bastante similar no? Asi el lenguaje puede ser extremadamente regular...
Exacto, de ahí que pusiera el
Exacto, de ahí que pusiera el link de "Objects and Closures are Equivalent"
Entonces lo que diferencia a uno paradigma de otro es como llama a las cosas.
¿Estamos creando objetos y pasando bloques de código? ¿O estamos creando funciones y pasando funciones? ¿Estamos haciendo ambas?
Que un lenguaje no sea 100% de un solo estilo/paradigma, no lo hace automáticamente híbrido, puede seguir siendo de un solo estilo sin más confusión, Java por ejemplo es principalmente Orientado a Objetos, aunque sea extremadamente fácil de escribir código que se parezca muchísimo a C en el.
Yo no veo por ejemplo, ningún problema con que un lenguaje dijera: Este lenguaje usa objetos y permite que sus métodos puedan ser almacenados, declarados y etc.
Por ejemplo de nuevo, Smalltalk desde desde hace mucho tiempo permite declarar variables de tipo bloque, pasarlos como parámetros, almacenarlos en estructuras de datos y no se le llama funcional ni nada; es OO puro.
Hola Gavin
Por supuesto que no se puede negar la existencia de un estilo imperativo en los lenguajes fundamentalmente funcionales que mencionas, como tampoco se puede negar la existencia de un estilo funcional en los lenguajes fundamentalmente imperativos.
Coincido contigo en que puedo escribir con un estilo funcional en Java, como también puedo escribir orientado a objetos en el lenguaje C. De hecho cuando enseñaba programación orientada a objetos hace ya 15 años me gustaba ilustrar en C, lo que de manera "más" normal hacía con clases y objetos en C++.
Por supuesto que un lenguaje siempre contribuye más a una manera de programar que a otra, pero eso no impide distinguir entre programación funcional de la programación imperativa. Para mí, Scala es un lenguaje orientado a objetos primariamente, como lo es Ceylon, y puedo en ambos escribir "programas funcionales".
Creo que coincidimos en esa apreciación.
¿Funciones de nivel tope en Scala?
Efectivamente, como dice Enrique, en Ceylon podemos escribir funciones de nivel tope, lo que es algo muy bueno del lenguaje.
en el siguiente código de Scala no podría también con considerarse como la definición de una "función" a nivel tope.
Salvando las diferencias en sintaxis (que es mucha), me pregunto si lo que defino como
pues sí
Desde un punto de vista totalmente práctico, el objeto suma puede ser usado como una función. La cosa está en la declaración; la sintaxis es completamente la de un objeto. Es curioso que Scala no permita definir funciones de manera "sencilla" en nivel tope o primer nivel. Hay azúcar sintáctica para el "extends" pero nada más. Es decir se puede declarar como
, pero no sé por qué no permiten declarar simplemente
.
Del mismo modo si
fuera una clase normal en vez de singleton, se podria inicializar tal vez con algunos parámetros y luego invocarse apply; eso es común verlo en ciertas bibliotecas de Scala y con eso es que da la apariencia o estilo funcional, cuando se ve la sintaxis del uso de ciertos objetos.
Barbaro, por supuesto no nego
Barbaro, por supuesto no nego que un lenguaje formenta un estilo especifico y, de veras, es mas facil utlizar un estilo funcional en Haskell que en Ruby, por ejemplo. Sin embargo, creo que tiene mas que ver con las bibliotecas y los costumbres de la comunidad que con las caracteristicas y syntaxis del lenguaje.
Precedencia de operadores
Respecto al "polimorfismo de operadores": ¿ Cómo se maneja la precedencia?
Es decir, si yo tengo una clase MyInteger que implementa Summable y Multiplicable o como se llame y hago
¿eso es
como en matemática o es
como en el horrible BigDecimal de java?
¿y donde queda especificado eso? ¿Es una convención y siempre * tiene mas precedencia que +, o se declara/programa de alguna manera?
Mis dos centavos respecto a si un lenguaje es funcional/orientado a objetos/procedural/: Lo que define la clasificación del lenguaje no es "lo que te deja hacer el lenguaje" sino "lo que te deja hacer facilmenteel lenguaje". Se puede escribir código funcional en Java...pero es muy difícil. Se puede escribir código orientado a objetos y es mucho mas fácil. Luego, java es orientado a objetos (o un poco menos, diría un purista smalltalker). C permite escribir código funcional y orientado a objetos pero con bastante dificultad, es procedural. Y así. Scala....no se...
especificación
La precedencia de los operadores no es convención, eso está en la especificación (convención es cuando nada más todo mundo se puso de acuerdo en hacer algo de cierta forma pero se puede hacer diferente). Y es como dices,
porque multiplicación precede a la suma. Lo del BigDecimal supongo que te refieres a que haces
y pues se ejecuta en el orden en que está indicado ahí, porque son objetos y no hay sobrecarga de operadores en Java (ni polimorfismo de operadores ni nada con operadores; el único operador que está sobrecargado pero de manera inflexible es el + para concatenación, junto con una conversión implícita de tipos).
Precedencia de operadores
Es cierto, es especificación y no convención, me expresé mal. Me gusta que uno pueda definir una suma y un producto entre clases y no pueda redefinir el orden de precedencia.
En lo de BigDecimal también tenés razón, me estaba quejando de que a.add(b).multiply(c) no sigue la precedencia de matemática sino la de orientación a objetos. En mi experiencia es mucho mas molesto recordar eso que escribir 'add' en vez de '+'.
Supongo que en Ceylon la asociatividad a izquierda o derecha de los operadores también seguirá la de los operadores matemáticos por especificación.
Pero dado que el BigDecimal/BigInteger de java no satisface Summable ni Multiplicable....¿habrá un par de clases equivalentes en Ceylon, asi podemos escribir a + b * c con precisión arbitraria?
en el futuro
Por ahora Ceylon sólo tiene Integer y Float; ciertamente habrá versiones BigDecimal y BigInteger pero no se ha iniciado trabajo en esa parte (hay varias otras cosas que tienen más prioridad por el momento). Mientras tanto, teniendo interop pues ya se podrá usar BigDecimal/BigInteger desde Ceylon (y por tanto no será tan difícil en todo caso hacer un envoltorio para ellas).
Lo interesante será tener implementaciones similares para el backend en js...