Funciones locales en C# 7.0

«And if my shadow is all that survives
I’m still alive …»
Meat Loaf, “Alive” (2006)

Aprovecho la suerte de que este blog sigue aún activo (¡muchas gracias por ello a mis buenos amigos de Plain Concepts!) para dar señales de vida después de hace bastante tiempo.

Revisando varios recursos online relacionados con las novedades incorporadas a C# 7.0 (incluyendo una excelente entrega de la serie Plain TV, que sigo regularmente), me pareció sumamente curioso que prácticamente ninguno de los vídeos consultados utilizaran, al presentar las funciones locales (local functions, véase descripción en MSDN), lo que para mí es el ejemplo por excelencia de uso de las mismas. Después de darle vueltas al tema por un buen rato, he llegado a la conclusión de que probablemente todo es consecuencia de varias décadas de enseñanza de la programación utilizando lenguajes que no ofrecen (u ofrecían) la posibilidad de anidar funciones (léase Java, C++ y el propio C#). Allá por los años 80, la Universidad de La Habana y muchos otros centros docentes utilizaban como vehículo de iniciación el lenguaje Pascal, que siempre ofreció esa posibilidad; no creo que ninguno de mis antiguos alumnos se haya «escapado» de haber visto algún ejemplo de utilización de esta característica para acotar el ámbito (visibilidad) de un  procedimiento/función.

El patrón de utilización de funciones anidadas al que me refiero se presenta comúnmente al programar algoritmos basados en la estrategia «divide y vencerás», y lo ejemplificaré aquí a través de una implementación del método QuickSort1 para ordenar un array de enteros en su sitio (in place). Al aplicar esta estrategia, aparecen funciones auxiliares cuyo ámbito natural es la rutina como parte de la cual han sido concebidos; muy frecuentemente estas funciones son recursivas. En el caso de QuickSort, cuya implementación muestro más abajo, esta posibilidad de anidamiento se presenta a varios niveles:

  • Al nivel más externo, la necesidad de ofrecer una implementación «limpia» nos exige exponer un método que solo requiera como parámetro el array a ordenar. Pero la naturaleza misma del algoritmo, como se describe a continuación, nos exige disponer de otra versión que reciba los índices inferior y superior sobre los que se va a actuar en el marco de una llamada cualquiera. Una función anidada es la solución ideal, dado que esta última versión no necesita ser expuesta al exterior (ni  siquiera otros métodos de la clase).
  • La idea central del algoritmo consiste en colocar de manera económica uno de los valores en el array en su posición final, con todos los valores menores que él a su izquierda y los mayores a su derecha (un proceso que se conoce como partición), para luego aplicar recursivamente la ordenación a los dos sub-arrays situados, respectivamente, a la izquierda y la derecha de ese elemento pivote. En nuestra implementación, la versión «interna» de QuickSort hace a su vez uso de una función local a ella, Partition, para hacer la partición del fragmento de array recibido.
  • Esta idea de descomposición funcional «hacia adentro» podría extenderse tanto como fuera necesario; a riesgo de lucir pedante, en el ejemplo he creado dentro de Partition una función Swap para intercambiar los valores de dos elementos del array, dados sus índices.

Observe el lector que el uso de funciones locales no solo produce un encapsulamiento más correcto del código, sino que además permite capturar en la función anidada los parámetros y variables locales de la función que la contiene, lo que se traduce tanto en economía de expresión como en mejoras en el rendimiento.

namespace Geeks
{
   public static class Utilities
   {
      public static void QuickSort(this int[] a)
      {
         void QuickSort(int first, int last)
         {
            int Partition()
            {
               void Swap(int p, int q)
               {
                  int tmp = a[p];
                  a[p] = a[q];
                  a[q] = tmp;
               }
 
               int pivot = a[first], index = first;
               Swap(index, last);
               for (int i = first; i < last; i++) 
                  if (a[i] < pivot) Swap(index++, i);
               Swap(index, last);
               return index;
            }

            if (first < last)
            {
               int pivotIndex = Partition();
               QuickSort(first, pivotIndex - 1);
               QuickSort(pivotIndex + 1, last);
            }
         }

         QuickSort(0, a.Length - 1);
      }
   }

   class Program
   {
      static void Main(string[] args)
      {
         var x = new int[] { 75824 };
         x.QuickSort();
      }
   }
}

1 Una descripción independiente del lenguaje de QuickSort puede verse aquí, y su implementación utilizando anidamiento aquí.


Referencia musical: Por allá por la segunda mitad de la década de los 70, cuando aún era un adolescente, un artista con el estrambótico nombre de Meat Loaf llamó poderosamente mi atención gracias a un peculiar estilo operístico, que combinaba el pop/rock tradicional con elementos de rock sinfónico. Con el tiempo, el disco “Bat Out of Hell” (1977) ha llegado a convertirse en uno de los diez más vendidos de todos los tiempos, y ha dado lugar a dos secuelas (también bastante exitosas) en 1993 y 2006. El tema «Alive» pertenece al tercer disco de la trilogía.

Acerca de «Solving Combinatory Problems with LINQ»

«I saw you, I knew you, I touched you
When the world was young …»
Jimmy Page & Robert Plant, «When the World Was Young» (1998)

Hace unos días, en el contexto de una conversación con un colega de trabajo, me acordé del artículo «Solving Combinatory Problems with LINQ«, que en su día me publicó gentilmente MSDN y aún sigue por ahí (también lo he copiado aquí, con el código listo para copiar y pegar). Sin duda alguna, y por una diferencia abismal (gracias, por supuesto, a Microsoft), es el texto más leído que haya escrito yo jamás, y en su momento dio lugar a discusiones como ésta.

Revisando el artículo al cabo de todo este tiempo, se me antoja ciertamente un poco naïve; me consuela la idea de que, al fin y cabo, eran tiempos en que «el mundo era joven» y LINQ acababa de nacer.


Referencia musical: Durante la década de los 90, los líderes del inolvidable Led Zeppelin, Jimmy Page y Robert Plant, publicaron dos discos que hicieron las delicias de los numerosos fans de la banda: «No Quarter» (1994) y «Walking into Clarksdale» (1998). «When the World Was Young» pertenece al segundo de esos trabajos.

Applying LINQ to new data types

[This article was originally published in MSDN C# Developer Center, February 2008]

Given the enormous expressive power that LINQ (Language Integrated Query) puts in the hands of developers, it is not surprising that we have started to search for ways to “LINQ-enable” more and more data types and technologies. As Dinesh Kulkarni humorously put it in this post, soon we will have even “LINQ to Coffee Machine” :-). .NET Framework 3.5 and C# 3.0 offer us several ways to achieve that goal.

In many occasions, all that is needed in order to be able to apply integrated queries to objects of a certain type (of our own, or belonging to the .NET Framework class library) is to define for the type one or more entry points which could serve as the generators to which all the basic object query machinery incorporated into .NET Framework (LINQ to Objects) can be applied. This has been, for instance, the approach used in LINQ to XML, where a set of iterators (implemented as extension methods) have been defined that produce subsets of the nodes that compose an XML document as IEnumerable<T> sequences; once you call any one of these methods, you obtain an XML node generator to which the predefined implementations of the standard query operators Where(), OrderBy(), etc. can be applied. LINQ to XML also introduces several specialized query operators (“extensions”), but that’s another story.

As an example of simple “LINQ activation” of classes, here we present the implementation of an extension that will allow us to use integrated query syntax over data received through a named pipe. Pipes are a well-known inter-process communication mechanism that has been in use since very long in the DOS/Windows world; but only with .NET Framework 3.5 we will be able to use them in our managed code applications without having to recur to platform invocation. Pipes can be anonymous, conceived to be used to communicate a parent process and a child process residing on the same machine; or named, which offer much more power and can be used over a network. Specifically, named pipes offer message-oriented communications.

The following program implements a server which sends UTF8-encoded string messages to its listener through a pipe named CS3:

using System;
using System.Text;
using System.IO;
using System.IO.Pipes;

namespace Demos
{
    class Server
    {
        static void Main(string[] args)
        {
            const string PipeName = "CS3";

            using (NamedPipeServerStream pipeStream =
              new NamedPipeServerStream(PipeName, PipeDirection.InOut,
                  1, PipeTransmissionMode.Message, PipeOptions.None))
            {
                pipeStream.WaitForConnection();

                // sending messages
                UTF8Encoding encoding = new UTF8Encoding();
                for (int i = 1; i <= 1000; i++)
                {
                    string msg = i.ToString();
                    byte[] bytes = encoding.GetBytes(msg);
                    pipeStream.Write(bytes, 0, bytes.Length);
                }
            }
        }
    }
}

The client, on the other end, composes received messages:

namespace Demos
{
    class Client
    {
        static void Main(string[] args)
        {
            const string Server = ".";
            const string PipeName = "CS3";

            using (NamedPipeClientStream pipeStream =
              new NamedPipeClientStream(Server, PipeName, PipeDirection.InOut))
            {
                pipeStream.Connect();
                pipeStream.ReadMode = PipeTransmissionMode.Message;

                Decoder decoder = Encoding.UTF8.GetDecoder();

                const int BufferSize = 256;
                byte[] bytes = new byte[BufferSize];
                char[] chars = new char[BufferSize];
                int nBytes = 0;
                StringBuilder msg = new StringBuilder();
                do
                {
                    msg.Length = 0;
                    do
                    {
                        nBytes = pipeStream.Read(bytes, 0, BufferSize);
                        if (nBytes > 0)
                        {
                            int nChars = decoder.GetCharCount(bytes, 0, nBytes);
                            decoder.GetChars(bytes, 0, nBytes, chars, 0, false);
                            msg.Append(chars, 0, nChars);
                        }
                    } while (nBytes > 0 && !pipeStream.IsMessageComplete);
                    decoder.Reset();
                    if (nBytes > 0)
                    {
                        // we've got a message - process it
                        Console.WriteLine(msg.ToString());
                    }
                } while (nBytes != 0);
            }
            Console.ReadLine();
        }
    }
}

What if the client process needed to filter, sort, group, etc. the received strings? Traditional coding approaches to implement these tasks could be applied, of course, but a more far-sighted approach would be to define a “LINQ entry point” in the form of an extension method for the NamedPipeClientStream class, so that it can be used as source for language integrated queries:

namespace PlainConcepts.Linq
{
    public static partial class Extensions
    {
        public static IEnumerable<string> GetMessages(
            this NamedPipeClientStream pipeStream)
        {
            pipeStream.Connect();
            pipeStream.ReadMode = PipeTransmissionMode.Message;

            Decoder decoder = Encoding.UTF8.GetDecoder();

            const int BufferSize = 256;
            byte[] bytes = new byte[BufferSize];
            char[] chars = new char[BufferSize];
            int nBytes = 0;
            StringBuilder msg = new StringBuilder();
            do
            {
                msg.Length = 0;
                do
                {
                    nBytes = pipeStream.Read(bytes, 0, BufferSize);
                    if (nBytes > 0)
                    {
                        int nChars = decoder.GetCharCount(bytes, 0, nBytes);
                        decoder.GetChars(bytes, 0, nBytes, chars, 0, false);
                        msg.Append(chars, 0, nChars);
                    }
                } while (nBytes > 0 && !pipeStream.IsMessageComplete);
                decoder.Reset();
                if (nBytes > 0)
                {
                    // we've got a message - yield it!
                    yield return msg.ToString();
                }
            } while (nBytes != 0);
        }
    }
}

Note that thanks to the use of an iterator, queries built around this extension method will take advantage of lazy evaluation, so that messages will be retrieved on demand, as they are needed by the client application.

With such an extension method at hand, we will be able to process messages received from a named pipe stream this way:

namespace Demos
{
    using PlainConcepts.Linq;

    class Client
    {
        static void Main(string[] args)
        {
            const string Server = ".";
            const string PipeName = "CS3";

            using (NamedPipeClientStream pipeStream =
              new NamedPipeClientStream(Server, PipeName, PipeDirection.InOut))
            {
                var input = from s in pipeStream.GetMessages()
                    where string.Compare(s, "3") < 0
                    orderby s
                    select s;
                foreach (var s in input)
                    Console.WriteLine(s);
            }
            Console.ReadLine();
        }
    }
}

Processing messages this way can save us a lot of coding, making at the same time our code much more elegant and readable.

Simplificando la programación asíncrona

«Parallel our sights
And we will find, that we need to be where we belong
Parallel our heights
Display our rights and wrongs, and always keep it strong.»
(«Parallels», Yes, 1977)

Después de un buen tiempo sin escribir, «rumiando» a cada rato contra la incorporación a C# 4 del tipado dinámico y toda la parafernalia asociada, a los que sigo aún sin encontrarles la «razón de estar» (ciertos anuncios recientes parecen hasta darme la razón), la presentación de la nueva Async CTP durante la PDC 2010 ha venido a insuflarme espíritu y confianza en que no todo está perdido y quedan muchas cosas interesantes aún por venir en relación con mi lenguaje favorito. Pero el lector no debería hacerme mucho caso en lo relativo a dynamic: creo que desde hace algún tiempo me estoy haciendo conservador (aunque, pienso, sin traspasar aún la delgada línea que separa «conservador» de «reaccionario» ;-). Es algo que pensé que jamás podría ocurrirme a mí, educado en la falacia bajo el signo de la «revolución interminable»; probablemente cosas de la edad :-).

En cuanto a la recién publicada Async CTP, pienso que se trata de una propuesta excelente, que simplificará en gran medida la programación asíncrona, cuya complejidad de implementación con la versión actual de C# es alta, algo de lo que puede dar fe cualquiera que se haya enfrentado al desarrollo de una aplicación medianamente «concurrente». A través de la introducción de dos nuevas palabras reservadas, async y await, la programación asíncrona en futuras versiones de C# se hará casi tan clara y natural como la síncrona, dejando al compilador la tarea de generar prácticamente todo el código de fontanería asociado al tratamiento de los callbacks y aprovechando al máximo las bondades que ofrece la Task Parallel Library (TPL) introducida en .NET 4.0.

Para descargar la CTP y acceder a una gran cantidad de documentación y ejemplos (incluyendo un vídeo introductorio de Anders Hejlsberg), visite http://msdn.microsoft.com/en-us/async.

NOTA: Las opiniones reflejadas en este artículo son mías propias, y no han sido revisadas ni aprobadas por la empresa en la que trabajo.


Pop/Rock tip: Para esta ocasión, he utilizado un tema de uno de los «padres» del rock progresivo, Yes; una banda que dura ya más de 40 años, durante los cuales, a pesar de los numerosos cambios de personal, ha sido capaz de mantener en todo momento un altísimo nivel interpretativo. Mis álbumes favoritos de Yes son los pertenecientes al primer ciclo de la banda, uno de cuyos máximos exponentes es «Going for the One» (1977); álbum al que pertenece el tema «Parallels».

More fun with LINQ

«Standing on a bridge, watch the water passing under me
It must’ve been much harder when there was no bridge just water…»
(«Funny the Way it is», Dave Matthews Band, 2009)

Ayer se publicó en el área de Community Content de MSDN mi artículo corto «Solving Combinatory Problems with LINQ«, un divertimento basado en LINQ al estilo de otros que ya he publicado tanto en «C# 3.0 y LINQ» como aquí. Estos puzzles siempre me traen a la memoria los problemas que poníamos a nuestros alumnos hace veintitantos años en la Universidad de La Habana, y me hacen pensar en cómo el progreso hace fácil lo que un tiempo atrás era bastante más difícil, que es precisamente a lo que se hace alusión en la referencia musical de hoy.


Pop/Rock tip: De entre los grupos contemporáneos de música popular, uno de los que más me gustan por su calidad, originalidad y eclecticismo es Dave Matthews Band. Su música es una interesante combinación de rock, jazz, soul, bluegrass e incluso música clásica, y en sus conciertos acostumbran a improvisar alrededor de sus canciones al estilo de las jam sessions tan típicas del jazz. «Funny the Way it is» fue el primer single del disco «Big Whiskey and the GrooGrux King» (2009), que vi por primera vez (y no dejé escapar 🙂 en un Starbucks, a mediados del año pasado.

Buscando "El símbolo perdido" con LINQ

Últimamente he estado leyendo en los ratos libres «El símbolo perdido«, la última obra de Dan Brown, el autor de «El código Da Vinci» y otros best sellers. Como parte de los hechos a los que se enfrenta el protagonista durante sus peripecias para descifrar un oculto misterio, el autor nos presenta un cuadrado ultra-mágico que aparece en el cuadro «Melencolia I» (1514), del pintor alemán Alberto Durero:

16 3 2 13
5 10 11 8
9 6 7 12
4 15 14 1

La «magia» de este cuadrado (más información aquí) radica en el hecho de que no solo todas las filas, todas las columnas y las dos diagonales suman 34; adicionalmente, los cuatro «cuadrantes» del cuadrado también suman 34, así como las cuatro casillas centrales. Por último, las dos casillas centrales de la última fila son 15 y 14, que concatenadas producen ‘1514’, el año en el que Durero pintó el cuadro.

En cierto momento de la lectura, pensé: ¿y por qué no buscar un cuadrado similar, pero que tenga en esas casillas inferiores ’20’ y ’10’ (en homenaje, claro está, a Visual Studio 2010 :-)? La siguiente pregunta surgió casi inmediatamente: ¿y por qué no utilizar LINQ, que es parte del producto y de seguro nos permitirá especificar el problema de una manera natural, para resolverlo?

Al final de este post incluyo el código que escribí en pocos minutos para resolver una simplificación del problema que me pareció ya bastante difícil: simplemente buscar un cuadrado tal, que todas las filas, columnas y diagonales sumen lo mismo, con ’20’ y ’10’ en las casillas centrales inferiores. Pensé que encontrar una solución tal podía requerir un enorme tiempo de cálculo en el peor de los casos dada la complejidad algorítmica del programa. Pero como últimamente mi viejo amigo Rainmaker (llamado así, por supuesto, en honor a un tema de Kansas) se pasa los días ocioso y solito en casa, ¿por qué no intentarlo y de paso alegrarle un poco la vida?

Al final, la simplificación resultó ser mucho menos dura de lo que pensaba, y a los pocos minutos el programa estaba escupiendo respuestas con cierta frecuencia. A continuación, la primera de las soluciones encontradas:

1 2 15 29
14 21 5 7
23 4 17 3
9 20 10 8

Por supuesto, este cuadrado no cumple con todas las condiciones del cuadrado de Durero. Desde aquí invito a los lectores a completar mi programa con las condiciones que faltan o crear el suyo propio y buscar un cuadrado que cumpla todas las condiciones (si es que existe).

Sobre la utilización de LINQ para resolver problemas de este tipo ya escribí en su momento (http://geeks.ms/blogs/ohernandez/archive/2007/04/25/191-debemos-aprender-una-nueva-forma-de-escribir-bucles.aspx), aunque sigo teniendo algunas dudas existenciales al respecto :-). Un experimento que se me antoja interesante sería el de comparar el rendimiento de implementaciones como ésta al utilizar LINQ y PLINQ en máquinas con múltiples núcleos. Pero ese experimento, por mi parte, tendrá que esperar…

(Vea la continuación aquí)

Código utilizado:

using System;
using System.IO;
using System.Linq;

namespace LostSymbol
{
class Program
{
private const int MAX = 100;

static void Main(string[] args)
{
const int firstSeed = 20;
const int secondSeed = 10;

var res =
/* first row /
from a11 in Enumerable.Range(1, MAX)
where a11 != firstSeed && a11 != secondSeed
from a12 in Enumerable.Range(1, MAX)
where a12 != firstSeed && a12 != secondSeed &&
a12 != a11
from a13 in Enumerable.Range(1, MAX)
where a13 != firstSeed && a13 != secondSeed &&
a13 != a11 && a13 != a12
from a14 in Enumerable.Range(1, MAX)
where a14 != firstSeed && a14 != secondSeed &&
a14 != a11 && a14 != a12 && a14 != a13
let sum = a11 + a12 + a13 + a14
/
second row /
from a21 in Enumerable.Range(1, MAX)
where a21 != firstSeed && a21 != secondSeed &&
a21 != a11 && a21 != a12 && a21 != a13 && a21 != a14
from a22 in Enumerable.Range(1, MAX)
where a22 != firstSeed && a22 != secondSeed &&
a22 != a21 &&
a22 != a11 && a22 != a12 && a22 != a13 && a22 != a14
from a23 in Enumerable.Range(1, MAX)
where a23 != firstSeed && a23 != secondSeed &&
a23 != a21 && a23 != a22 &&
a23 != a11 && a23 != a12 && a23 != a13 && a23 != a14
let a24 = sum – (a21 + a22 + a23)
where a24 > 0 && a24 <= MAX &&
a24 != firstSeed && a24 != secondSeed &&
a24 != a21 && a24 != a22 && a24 != a23 &&
a24 != a11 && a24 != a12 && a24 != a13 && a24 != a14
/
third row /
from a31 in Enumerable.Range(1, MAX)
where a31 != firstSeed && a31 != secondSeed &&
a31 != a11 && a31 != a12 && a31 != a13 && a31 != a14 &&
a31 != a21 && a31 != a22 && a31 != a23 && a31 != a24
let a32 = sum – (a12 + a22 + firstSeed)
where a32 > 0 && a32 <= MAX &&
a32 != firstSeed && a32 != secondSeed &&
a32 != a31 &&
a32 != a11 && a32 != a12 && a32 != a13 && a32 != a14 &&
a32 != a21 && a32 != a22 && a32 != a23 && a32 != a24
let a33 = sum – (a13 + a23 + secondSeed)
where a33 > 0 && a33 <= MAX &&
a33 != firstSeed && a33 != secondSeed &&
a33 != a31 && a33 != a32 &&
a33 != a11 && a33 != a12 && a33 != a13 && a33 != a14 &&
a33 != a21 && a33 != a22 && a33 != a23 && a33 != a24
let a34 = sum – (a31 + a32 + a33)
where a34 > 0 && a34 <= MAX &&
a34 != firstSeed && a34 != secondSeed &&
a34 != a31 && a34 != a32 && a34 != a33 &&
a34 != a11 && a34 != a12 && a34 != a13 && a34 != a14 &&
a34 != a21 && a34 != a22 && a34 != a23 && a34 != a24
/
fourth row /
let a41 = sum – (a11 + a21 + a31)
where a41 > 0 && a41 < MAX &&
a41 != firstSeed && a41 != secondSeed &&
a41 != a11 && a41 != a12 && a41 != a13 && a41 != a14 &&
a41 != a21 && a41 != a22 && a41 != a23 && a41 != a24 &&
a41 != a31 && a41 != a32 && a41 != a33 && a41 != a34 &&
a41 + a32 + a23 + a14 == sum     /
right-to-left diagonal /
let a42 = firstSeed
let a43 = secondSeed
let a44 = sum – (a14 + a24 + a34)
where a44 > 0 && a44 < MAX &&
a44 != firstSeed && a44 != secondSeed &&
a44 != a41 &&
a44 != a11 && a44 != a12 && a44 != a13 && a44 != a14 &&
a44 != a21 && a44 != a22 && a44 != a23 && a44 != a24 &&
a44 != a31 && a44 != a32 && a44 != a33 && a44 != a34 &&
a41 + firstSeed + secondSeed + a44 == sum &&
a11 + a22 + a33 + a44 == sum     /
left-to-right diagonal */
select new
{
a11, a12, a13, a14,
a21, a22, a23, a24,
a31, a32, a33, a34,
a41, a42, a43, a44,
};

using (StreamWriter sw = new StreamWriter(«C:\TEMP\Solutions.txt«))
{
sw.WriteLine(«Start : » + DateTime.Now.ToString(«yyyy/MM/dd hh:mm:ss»));
int n = 0;
foreach (var r in res)
{
sw.WriteLine(«Solution » + n);
sw.WriteLine(«=============»);
sw.WriteLine(«{0} {1} {2} {3}», r.a11, r.a12, r.a13, r.a14);
sw.WriteLine(«{0} {1} {2} {3}», r.a21, r.a22, r.a23, r.a24);
sw.WriteLine(«{0} {1} {2} {3}», r.a31, r.a32, r.a33, r.a34);
sw.WriteLine(«{0} {1} {2} {3}», r.a41, r.a42, r.a43, r.a44);
sw.WriteLine();
sw.Flush(); n++;
}
sw.WriteLine(«Finish: » + DateTime.Now.ToString(«yyyy/MM/dd hh:mm:ss»));
}
Console.ReadLine();
}
}
}