sábado, 10 de diciembre de 2011

Sobre la comparación entre lenguaje de programación

El otro día en un almuerzo salió el tema de si tiene sentido comparar distintos lenguajes de programación.

Una de las posturas era que en realidad, con cualquier lenguaje de programación moderno (léase Java, C#, Objective-C, Ruby, etc.) puedo solucionar cualquier problema que pueda solucionar con otro de estos lenguajes.

De todas formas yo creo que sí se pueden comparar, y que si bien con cualquiera se pueden resolver los problemas, no se obtiene la misma productividad.

Voy a poner un ejemplo, usando una muy pequeña característica de los lenguajes de programación: los loops de tipo "for".

El caso más simple, es querer recorrer una colección partiendo del primer item hasta el último, avanzando de a uno. Para eso me alcanza una construcción del tipo:

for index = 0 to count-1 {
  // hacer algo con index
}


¿Qué pasa ahora si quiero contar de dos en dos? ¿Me sirve la construcción anterior? Bueno, sí... puedo escribir lo siguiente:

for fakeIndex = 0 to (count-1)/2 {
  index = fakeIndex * 2
  // hacer algo con index
}


No es complicado, pero no parece muy práctico. Un lenguaje que le pueda decir el "step" al comando "for" no es más potente, pero sí más práctico. La forma de escribirlo quedaría entonces:

for index = 0 to count-1 step 2 {
  // hacer algo con index
}


El problema que tiene esto, es que si quiero que el step sea variable, por ejemplo quiero iterar sobre las potencias de 2, con el step solo no me alcanza. Lo puedo resolver, pero queda más complicado. Una solución es usar la sintaxis que usa C, donde se le indica el valor inicial, la condición de parada, y el incremento. Así:

for (int index = 1; index <= someValue; index *= 2) {
  // hacer algo con index
}


Esta iteración sí la puedo construir con el primer tipo de "for", pero es más difícil: hay que pensar como mapear el índice de la iteración con el índice que realmente quiero manejar.

De todas formas, cuando uno tiene este "for" como es el caso de C y todos sus lenguajes derivados, lo más común es usarlo para acceder a los elementos de una colección:

for (int index = 0; index < array.count; index++) {
  value = array[index];
  // hacer algo con value
}


En general, el índice se termina usando solo para acceder al elemento correspondiente de la colección. El valor inicial, el final y el incremento son una construcción artificial. Lo que realmente quiero es poder iterar sobre los elementos de mi colección.

Supongamos ahora que tengo una construcción que me permite hacer esto:

for (value in array) {
  // hacer algo con value
}


¿Es esta construcción más potente que la anterior? No, las dos permiten expresar lo mismo. Pero sin duda es mucho más conveniente, aumenta la productividad y minimiza el riesgo de cometer errores (por ejemplo si uso "index <= array.count" en vez de "index < array.count", error bastante común).

¿Se puede hacer mejor? Sí, se puede, y lo curioso del caso es que para la forma de mejorarlo es eliminar la construcción "for", que es lo que se tiene en Ruby (donde sí existe el "for" pero no se usa...).

Si no tengo el comando "for", entonces tengo que buscar otra forma de iterar sobre una colección o conjunto de valores. Esto en Ruby se resuelve con el método "each" (o "each_index") de la siguiente forma:

array.each do |value|
  # hacer algo con value
end


o

(0..5).each do |index|
  # hacer algo con index
end


¿Por qué esto es mejor? Porque si el "each" es un método y no una construcción del lenguaje, quiere decir que el lenguaje me permite iterar de la forma que quiera. Ahora puedo separar el uso del "for" según para que lo quiera.

Las cosas más comunes para las que recorro una colección son para transformar los elementos en otra colección, filtrar los valores según alguna condición, o acumular en una variable. Entonces puedo tener métodos que hagan estas cosas, y en mi programa no voy a necesitar la iteración, porque en realidad lo que quería no era iterar sino hacer algo de más alto nivel a la colección.

En conclusión, es verdad que no hay nada que no pueda resolver con las construcciones más simples, y por lo tanto no hay un lenguaje más potente que otro, lo que sí hay son lenguajes más productivos.


Actualización, 12/12/11 8:56: Willy me encontró un bug, así que lo corregí... Había un loop que empezaba en cero e incrementaba multiplicando por dos, por lo que nunca iba a salir...

3 comentarios:

  1. Es un tema interesante la comparación de las herramientas con las que trabajamos, pero cual seria el objetivo final de esta comparación ?
    A mi se me ocurren algunas:
    * Como un método para aprender sobre lo que hace otro y como.
    * Hablar de como afecta el Ambiente de Desarrollo (no solo el lenguaje) nuestro trabajo.
    * Hablar sobre cual es más potente (falta def. significado).
    * Hablar sobre cual es más productivo (falta def. significado).
    * Hablar de "teoría de desarrollo" en general (diferentes paradigmas, etc).

    En cuanto a mi caso (uso Smalltalk) tendría que hacer un diferenciación entre ambiente de desarrollo y lenguaje. Pq Smalltalk (el Ambiente) en realidad es un mundo de objetos interconectados que se comunican enviándose mensaje, y los métodos se implementan en un lenguaje DESAFORTUNADAMENTE TAMBIÉN llamado Smalltalk (el lenguaje). En el AMBIENTE Smalltalk si quiero puedo especificar que determinadas clase compilen sus métodos con determinado compilador. Esto se usa por ejemplo para programar métodos en assembler o C para mejorar performance, etc. Hecha la aclaración...

    Creo que el punto que tocas vos la PRODUCTIVIDAD se ve muy afectado NO solo por el Lenguaje sino también por el ambiente de desarrollo que en el que esta insertado ese lenguaje (en Smalltalk hay un lenguaje inserto en un mundo de objetos).
    Por que si el ambiente NO influyera entones todos estaríamos programando en Assembler o Código Nativo, y con un NotePad + Compilador es imposible. Pero si tenes un ambiente sobre el cual programas la cosa cambia, claro que depende de lo "evolucionado" que es el ambiente.
    Un ejemplo extremo es Smalltalk/X, que programas como en cualquier otro Smalltalk (hay lenguaje + ambiente) pero te compila tu aplicación a Assembler, es extremo porque podría decirse que no es un lenguaje, que es un generador, pero es discutible (pq es un lenguaje).

    Creo que alguno de los puntos más importantes son como decís vos la Productividad (a largo plazo) y el "Time to Market" (cuanto demoro en incorporar cualquier tipo de cambio a mi sistema y hacerlo disponible).
    Como regla general se podría decir que si nuestro lenguaje (+ambiente) es bueno el "Time to Market" debería bajar con el tiempo, si sube es pq hay algo que anda mal.

    Hay otro punto interesante...
    En mi humilde opinión el estudio de la sentencias del lenguaje nos da la productividad a Corto Plazo y el estudio del "ambiente de desarrollo" nos da la Productividad a Largo Plazo (medida en número de proyectos o en años). Entendiendo por ambiente todo lo que incluye desarrollo hasta implantación, así como manejo de versiones y repositorio de paquetes, etc.
    Es claro que hay una interdependencia entre las "dos productividades", también podría decirse que corto y largo plazo son aspectos de una misma cosa.

    En Orientación a Objetos recomendaría tener disponible Block (bloques) que son parecidas a las Lambda Expression de Lisp.

    coleccionDeCliente := Cliente todos. "Supongamos que me devuelve una coleccion/array de 10.000 clientes".

    "Los ordeno por facturación Total y los guardo en la variable "
    porFacturacionTotal := coleccionDeCliente asSortedCollection: [a: :b | a facturacionTotal >= b facturacionTotal].

    "aqui el bloque es [a: :b | a facturacionTotal >= b facturacionTotal] "

    "Los ordeno por facturación ultimo mes y los guardo en la variable "
    porFacturacionDelMes := coleccionDeCliente asSortedCollection: [a: :b | a facturacionMesActual >= b facturacionMesActual].

    "aqui el bloque es [a: :b | a facturacionMesActual >= b facturacionMesActual] "

    Los Bloques aumentan la productividad a Corto Plazo, pero no me asegura que funcionen a largo plazo.

    Saludos,
    Bruno

    ResponderEliminar
  2. Bruno:

    Yo lo tomo más que nada como un ejercicio para ver de que forma puedo mejorar lo que estoy haciendo.

    Con respecto a lo que decís de la productividad, sin duda que hay muchas más cosas que influyen, además del lenguaje: ambiente de desarrollo, bibliotecas disponibles, comunidad, proyectos open source, documentación, etc.

    Pero una vez que estás en el código, cuanto más natural sea de escribir, cuanto más natural sea para entender lo que hizo otro, mejor.

    Como dato curioso, el otro día que fui al RubyConfUy, la mayoría de los que presentaron usaban "vi" como editor (o alguna variante: vim, MacVim, etc). ¿Qué dice eso (que no se precisa un IDE) del lenguaje? :)

    ResponderEliminar
  3. Marcos,

    Si cuanto mas natural mejor, tomaría una regla general del Diseño de Smalltalk:
    "Los mecanismos del pensamiento y la comunicación humanos han sido pulidos durante millones de años, y deberíamos considerarlos bien diseñados. Más aún, como deberemos trabajar con este diseño durante el próximo millón de años, nos ahorrará tiempo si hacemos que nuestros modelos de computación sean compatibles con la mente, en vez de hacerlo al revés."

    /* ¿Qué dice eso (que no se precisa un IDE) del lenguaje? :) */

    En realidad estoy totalmente en desacuerdo, todo lenguaje debe tener su IDE y cuanto mas evolucionada e integrada al lenguaje mejor.

    Es mas el código de la IDE debe ser parte del lenguaje, para poder adaptar la IDE a mis necesidades. A cualquier Smalltalk se le puede extender la IDE pq el código también son clases, como cualquier otro elemento del sistema.

    Programar en el VI y compilar, la verdad paso, que lo sufra otro. Me parece demasiado antiguo programar así, es como los 70 la época de Cobol.

    Saludos,
    Bruno

    ResponderEliminar