April 2007 - Artículos

(This post is a slightly-edited English translation of a previous one in Spanish, with some added comments at the end) 

 

Until now, every time somebody mentions the potential dangers that will “surround” programmers with the introduction of the new features of C# 3.0 (specially those related to the abuse of local variable type inference and extension methods), my reaction is always the same: I consider that these language enhancements are necessary in order to support the integrated query mechanism; that they are not, in principle, awful or anyhow fundamentally flawed; and that through continuous education we will be able to warn developers so that they can sort out the possible dangers that the use of these features could convey.

 

A few days ago I read a post by Luke Hoban that seems to have convinced me even more of the role that we, trainers, will have to play in the future education of .NET programmers; in this case, by showing them not to use C# 3.0 features or integrated queries for tasks where these features don't make much sense. Luke's post shows how to solve a weight distribution puzzle using a kind of "brute force" approach: a series of “nested loops” implemented using the standard query operator Range(min, max), that sequentially produces the integer values between min y max, both inclusive. That seemed to me (at least at first sight) a situation in which the use of an integrated query does not offer any advantages over traditional loops, but can severely affect performance.

 

The puzzle reminded me of a certain kind of problems I used to propose back in my years as University teacher (20 years ago) to freshman students receiving the course “Introduction to Programming” (we used Pascal then). So I decided to program one of those in two different ways: using traditional loops and “a la Hoban”, in order to compare both implementations.

 

The problem: Write a program that finds all the four-digit integers that are equal to the sum of the fourth powers of each of its digits. For instance, one of these numbers is 1634:

 

                 1634 = 14 + 64 + 34 + 44

 

Solution (naïve): To iterate over four nested loops covering all the possible combinations of digits, and check if the condition holds true for each of these combinations.

 

At the end of this post is included the code of a program that implements both variants and measures the time they take to execute. I have tried to make both variants as similar as possible; in particular, I have introduced a generic list in the loop-based variant, taking into account that the LINQ-based version will have to deal with sequences.

 

On my laptop, the ratio between the times spent by both variants was of 80:95. Honestly, I was expecting a somewhat bigger difference. Maybe just the size of the problem is too small. But the important thing is: ¿does the integrated query offers any advantage here? ¿should we learn a new way of writing loops? Opinions are welcome.

 

(Added 04/27/2007)

 

The thread following the publication of the original post has raised two possible advantages of the LINQ approach: a) it leads to more declarative code that expresses more the intent rather than the explicit way of obtaining the result; b) being expression-based, the LINQ approach can be more amenable to certain compiler optimizations.

The author wishes to thank Alfredo Novoa for his very helpful comments.

 

//*********************************************************************

class Program

{

    static void Main(string[] args)

    {

        Stopwatch sw = new Stopwatch();

        sw.Start();

        // "classic" version

        List<int> numbers = new List<int>();

        for (int i1 = 1; i1 <= 9; i1++)

        for (int i2 = 0; i2 <= 9; i2++)         for (int i3 = 0; i3 <= 9; i3++)

        for (int i4 = 0; i4 <= 9; i4++)

            if (Condition(i1, i2, i3, i4))

                numbers.Add(Number(i1, i2, i3, i4));

        foreach (int n in numbers)

            Console.WriteLine(n);

        sw.Stop();

        Console.WriteLine("Ellapsed: " + sw.ElapsedMilliseconds.ToString());

        sw.Start();

        // LINQ version

        IEnumerable<int> numbers2 =

            from j1 in Enumerable.Range(1, 9)

            from j2 in Enumerable.Range(0, 9)

            from j3 in Enumerable.Range(0, 9)

            from j4 in Enumerable.Range(0, 9)

                where Condition(j1, j2, j3, j4)

                    select Number(j1, j2, j3, j4);

        foreach (int n in numbers2)

            Console.WriteLine(n);

        sw.Stop();

        Console.WriteLine("Ellapsed: " + sw.ElapsedMilliseconds.ToString());        Console.ReadLine();

    }

    static bool Condition(int a, int b, int c, int d)

    {

        return Number(a, b, c, d) == Fourth(a) + Fourth(b) + Fourth(c) + Fourth(d);

    }

    static int Number(int a, int b, int c, int d) { return 1000 * a + 100 * b + 10 * c + d; }

    static int Square(int a) { return a * a; }     static int Fourth(int a) { return Square(a) * Square(a); }

}

//*********************************************************************

Publicado por Octavio Hernández | 1 comment(s)
Archivado en:

English version 

 

Hasta el momento, cada vez que alguien menciona los peligros potenciales que comenzarán a “rondar” al programador con la introducción de las nuevas características de C# 3.0 (la inferencia de tipos de variables locales –léase var- y los métodos extensores, principalmente), mi reacción es siempre la misma: considero que son recursos necesarios para ofrecer el soporte a las consultas integradas, no son en principio características carentes de atractivo ni mucho menos “impresentables”, y a través de la educación podremos alertar a los desarrolladores para que sean capaces de evitar los peligros que el uso de estas características podría esconder.

 

Hace unos días he visto un post de Luke Hoban que ha reforzado mi convicción en el papel que tendremos los formadores en la educación de los programadores .NET; en este caso, enseñándoles a no utilizar las consultas integradas para lo que no tiene sentido. El post en cuestión muestra cómo resolver la distribución de pesas en una balanza utilizando un enfoque de fuerza bruta: una serie de “bucles” anidados que se modelan utilizando el operador de consulta estándar Range(min, max), que produce secuencialmente los valores enteros entre min y max, ambos inclusive. Me pareció una situación en la que la consulta integrada no aporta nada con relación a bucles tradicionales, y que sí podría comprometer el rendimiento.

 

El problema en cuestión me recordó ciertos ejercicios que poníamos hace 20 años en la Universidad a los estudiantes de la asignatura “Introducción a la Programación” (bajo la inestimable dirección del Dr. Miguel Katrib). Así que decidí programar uno de estos ejercicios por las dos vías: mediante bucles “tradicionales” y “a la Hoban”, para comparar ambas implementaciones.

 

Enunciado: Escriba un programa que determine todos los números de cuatro cifras que son iguales a la suma de las cuartas potencias de cada una de las cifras que lo componen. Por ejemplo, uno de esos números es 1634:

 

                 1634 = 14 + 64 + 34 + 44

 

Solución (naïve): Iteramos en un cuádruple bucle anidado sobre todos los posibles valores de cada una de las cuatro cifras del número.

 

Al final de este post se presenta el código del programa que prueba ambas variantes; en la variante basada en bucles, hemos introducido una lista genérica para hacerla lo más parecida posible a la variante LINQ.

 

En mi portátil, la relación entre los tiempos consumidos fue de 80:95. Honestamente, esperaba un desequilibrio bastante mayor. Pero lo importante es: ¿aporta algo la utilización de una consulta integrada aquí? Las opiniones son bienvenidas.

 

//*********************************************************************

class Program

{

    static void Main(string[] args)

    {

        Stopwatch sw = new Stopwatch();

        sw.Start();

        // versión "clásica"

        List<int> cumplen = new List<int>();

        for (int i1 = 1; i1 <= 9; i1++)

        for (int i2 = 0; i2 <= 9; i2++)         for (int i3 = 0; i3 <= 9; i3++)

        for (int i4 = 0; i4 <= 9; i4++)

            if (Condicion(i1, i2, i3, i4))

                cumplen.Add(Numero(i1, i2, i3, i4));

        foreach (int n in cumplen)

            Console.WriteLine(n);

        sw.Stop();

        Console.WriteLine("Transcurrido: " + sw.ElapsedMilliseconds.ToString());

        sw.Start();

        // versión LINQ

        IEnumerable<int> cumplen2 =

            from j1 in Enumerable.Range(1, 9)

            from j2 in Enumerable.Range(0, 9)

            from j3 in Enumerable.Range(0, 9)

            from j4 in Enumerable.Range(0, 9)

                where Condicion(j1, j2, j3, j4)

                    select Numero(j1, j2, j3, j4);

        foreach (int n in cumplen2)

            Console.WriteLine(n);

        sw.Stop();

        Console.WriteLine("Transcurrido: " + sw.ElapsedMilliseconds.ToString());        Console.ReadLine();

    }

    static bool Condicion(int a, int b, int c, int d)

    {

        return Numero(a, b, c, d) == Cuarta(a) + Cuarta(b) + Cuarta(c) + Cuarta(d);

    }

    static int Numero(int a, int b, int c, int d) { return 1000 * a + 100 * b + 10 * c + d; }

    static int Cuadrado(int a) { return a * a; }     static int Cuarta(int a) { return Cuadrado(a) * Cuadrado(a); }

}

//*********************************************************************

 

Publicado por Octavio Hernández | 9 comment(s)
Archivado en:

El título de este post está obviamente inspirado en el lamentablemente ya desaparecido programa de José Luis Garci por la 2 de TVE.

Después de una semana anterior "movidita", en la que aprendí no poco sobre WPF de la mano de ese fenómeno que es Cristian Manteiga, durante el fin de semana al único software que me "acerqué" lo hice como espectador. Pero ¡vaya espectáculo! Una maravilla de programa que no había visto antes y me causó tremenda impresión.

Me refiero a Pro-Tools 7.3 (http://www.digidesign.com/), una suite de herramientas para la creación de piezas musicales, edición y mezcla de audio y vídeo y muchas cosas más que en manos de "titanes" como Javier Limón, José Luis Crespo o mi propio hermano El Negro permite realizar con asombrosa facilidad tareas que hasta hace relativamente poco requerían del tiempo y esfuerzo de muchas personas a la vez. Piezas de software como ésta hacen pensar en lo que ha avanzado el mundo desde que la informática se ha hecho omnipresente en él.

Eso sí, debo decir que lo que vi en acción fue la versión para MacIntosh del producto :-)

 

Publicado por Octavio Hernández | con no comments
Archivado en:

Haciendo eco del post de David Salgado, reproduzco aquí las respuestas a las preguntas sobre LINQ recibidas por SMS durante el pasado Developer's Day. Me he permitido unas ligeras adiciones, siempre que revisa al cabo de un tiempo lo que se ha escrito se encuentran maneras de mejorarlo :-)

1.      No entiendo porqué la select está al final en LINQ ¿porqué no respeta la sintaxis SQL?

 

Piensa en la sentencia SELECT de SQL, por ejemplo:

 

            // candidatas para David Sal(Del)gado

            SELECT Nombre, Apellidos

            FROM Amigos

            WHERE Sexo = ‘M’ AND EstadoCivil = ‘S’ AND Edad <= 25

 

Después de teclear SELECT, el entorno de desarrollo no podrá darte ayuda de ningún tipo con relación a los campos que quieres seleccionar, porque aún no sabe con qué tabla(s) vas a trabajar (lo cual se indica en la cláusula FROM).

 

Con la sintaxis que propone LINQ:

 

            from f in db.Amigos

            where f.Sexo == ‘M’ and f.EstadoCivil = ‘S’ and f.Edad <= 25

            select new { f.Nombre, f.Apellidos };

 

se resuelve ese problema. Cuando tecleas 'f.' detrás del where, ya el entorno sabe que f es de tipo Amigo, porque db.Amigos es de tipo Table<Amigo> (que implementa IEnumerable<Amigo>).

 

Esta decisión ha sido motivo de amplias discusiones (no violentas :-) dentro de los equipos de desarrollo de MS. De hecho, en la Presentación Preliminar de Mayo de 2006, en VB.NET el Select se ponía delante. Ahora ambos lenguajes utilizan el Select al final.

 

2.      ¿Qué es eso del var en C# 3.0? ¿Acaso el lenguaje va a dejar de ser estáticamente tipado?

 

La sentencia var en C# 3.0 hará que el compilador infiera o deduzca el tipo de la variable en función del tipo de la expresión que se coloque del lado derecho de la asignación (var solo se podrá utilizar junto con una expresión de inicialización). Por ejemplo, en:

 

            var s = “Hola”;

 

el compilador determinará que el tipo de la variable s es string. No es object, ni Variant como en VB6, ni una variable “amorfa” como en Javascript, ni nada por el estilo. La aparición de var no implica que C# deje de ser un lenguaje con fuerte control de tipos ni mucho menos. Simplemente el lenguaje nos facilita la vida, no obligándonos a indicar el tipo de la variable y encargándose él de deducirlo. En principio, el uso de var solo es imprescindible cuando la expresión a la derecha de la asignación es de un tipo anónimo (otra nueva característica de C# 3.0).

 

Por supuesto, esta característica debe usarse juiciosamente, porque su uso indiscriminado puede llevar a que nuestro código se haga menos legible.

 

3.      Si no sabes el tipo de algo, ¿cómo sabes con qué propiedades y métodos lo manejas?

 

Esta pregunta tiene relación con la anterior. Dado que C# es un lenguaje estáticamente tipado, el compilador (y el entorno, que se apoya en él) siempre sabe el tipo de cualquier variable o expresión, ya sea porque se lo decimos nosotros o porque él lo deduce (como ocurre cuando usamos var para declarar una variable). Suponiendo que hemos definido la variable ‘s’ de la pregunta anterior, cuando tecleemos ‘s.’ dentro del editor nos aparecerá la lista de propiedades, métodos y eventos de la clase string. Y si se trata de una variable de un tipo anónimo (generado internamente por el compilador), la ayuda Intellisense nos dirá qué propiedades, métodos y eventos tenemos a nuestra disposición.

 

4.      ¿Qué rendimiento presenta LINQ respecto a la consulta directa usando el DOM para XML o sentencias SQL para BBDD?

 

Aunque no he hecho pruebas de rendimiento específicas, pienso que el rendimiento de LINQ to XML (por ejemplo, en búsquedas XPath) debe ser muy cercano al que podemos obtener hoy con el DOM. En el caso de LINQ to SQL, la diferencia podría ser algo mayor. Yo y otros compañeros hemos detectado en ocasiones que las sentencias SQL generadas por LINQ to SQL (se puede utilizar la propiedad Log del objeto de conexión para enviar las sentencias a la consola o un fichero y así poder examinarlas) son menos eficientes de lo que podría obtenerse escribiendo la sentencia “a mano”. Esto es lo que ocurre casi siempre que dejamos de hacer algo manualmente y comenzamos a apoyarnos en una herramienta automática. En defensa de LINQ to SQL hay que decir que:

 

a)      Estamos trabajando aún con una versión pre-beta, que sin lugar a dudas mejorará mucho antes de la salida del producto final.

b)      En cualquier caso, se seguirá investigando a ese respecto para futuras versionesJ.

c)      El contexto de datos tiene una propiedad Connection que da acceso a la conexión ADO.NET subyacente, a la que podremos recurrir en caso de que necesitemos obtener un rendimiento superior al que nos dé LINQ to SQL.

 

Como en todo, es cuestión de sopesar las ventajas contra los inconvenientes de una tecnología, y ver si es conveniente o no utilizarla. Para mí personalmente las ventajas (mayor expresividad y claridad del lenguaje, verificación del código en tiempo de compilación - al no tener que escribir las sentencias SQL como cadenas dentro de la aplicación-, ayuda Intellisense para las consultas integradas, mayor potencia en las API de actualización, etc.) superan a los inconvenientes en la mayoría de las situaciones en las que me encuentro habitualmente durante mi trabajo.

 

5.      Ahora mismo tengo un AMD 3200+ con 1gb de RAM y vs05 corre a tirones ¿qué necesitaré para ORCAS?

 

Para que funcione la CTP de marzo, la página de descarga recomienda un Pentium III+ con 1 GB de memoria libre (lo que queda después de la carga del sistema operativo y los servicios). Mi experiencia personal me dice que 2 MB es lo óptimo. Los requisitos finales que se necesitará para ejecutar la versión definitiva de Orcas deberán ser algo menores.

 

En cualquier caso, no está de más repetir la recomendación de siempre: ¡no instalar esta CTP en un PC de trabajo!

 

6.      ¿Hay LINQ to AS400?

 

La versión final de Orcas incorporará únicamente proveedores de LINQ to SQL para SQL Server 2000 y 2005. No obstante, ya hay otros fabricantes trabajando muy cerca de Microsoft para producir los correspondientes proveedores (no tengo la menor idea de si IBM está entre ellos).

 

Navegando por la red, hoy he encontrado este artículo en el que se habla de un gran interés de IBM hacia LINQ (y un cierto desinterés por parte de Oracle):

 

     Redmond Developer News

 

7.      ¿Mejora Windows Mobile con Orcas?

 

Windows Mobile 6 incorporará múltiples ventajas con respecto a la versión actual, aunque todo parece indicar que los desarrolladores no seremos los más beneficiados. La principal ventaja será que .NET CF vendrá incorporado ya en ROM, como puede leerse en la entrevista al Product Manager de Windows Mobile 6, en la dotNetManía de este mes (www.dotnetmania.com)

 

Publicado por Octavio Hernández | 2 comment(s)
Archivado en: