SparkSharp, Spark in C# (2) Implementando Map y Reduce

Published on Author lopez

Anterior Post

El proyecto está en:

https://github.com/ajlopez/SparkSharp

Como es usual, lo estoy desarrollando siguiendo el flujo de TDD (Test-Driven Development), así que el código va evolucionando a medida que encuentro nuevas soluciones a pequeños casos de uso planteados por los tests. Lo que muestro hoy puede cambiar mañana, al necesitarse en nuevos tests nueva funcionalidad o nuevas implementaciones. Por ejemplo, en el proyecto original de Apache Spark los dataset se crean desde métodos factorías en algo llamado Spark Context. Pero esa funcionalidad todavía no la necesité. Así que por ahora mis tests apuntan a crear simples objetos Dataset, directamente con el operador new, y a consumirlos.

Actualmente, nacida en algún refactor, está la clase abstracta BaseDataset. Parte del código:

public abstract class BaseDataset<T> : IEnumerable<T>
{
    public abstract IEnumerable<T> Elements { get; }

    public BaseDataset<S> Map<S>(Func<T, S> map)
    {
        return new EnumDataset<S>(this.ApplyMap(map));
    }

    public S Reduce<S>(Func<S, T, S> reduce)
    {
        S result = default(S);

        foreach (var elem in this)
            result = reduce(result, elem);

        return result;
    }
    
    // ...

    private IEnumerable<S> ApplyMap<S>(Func<T, S> map)
    {
        foreach (var elem in this)
            yield return map(elem);
    }
    
    // ...
}

Vemos que la implementación de enumerar los elementos que contiene queda delegada a la clase concreta. Pero en el código de arriba dejé la implementación base de los métodos Map y Reduce. Gracias a C#, estos métodos pueden recibir un lamba, o una Func (una función como objeto) como veremos en algún test.

Vean como el ApplyMap usa el yield de C# para devolver cada elemento, y el foreach sólo se vuelve a ejecutar cuando el consumidor del IEnumerable necesita el próximo item. Tanto el uso de lambdas como de yield han simplificado la implementación de estas ideas. Esta es una prueba de lo que se necesita en un lenguaje para cumplir mejor con alguna necesidad. Como digresión, comento que me parece que la historia de C# ha sido bastante acertada, incorporando estas ideas, mientras que en el mundo Java, se ha dado el caso de lenguajes como Scala que, siendo un gran lenguaje, me parece que trata de sumar demasiadas cosas.

No se ejecutan tests sobre la clase abstracta (que de nuevo, vean la historia de commits, nació como un refactor), sino sobre alguna concreta. Siendo EnumDataset una clase concreta (a examinar en próximos posts), sea un test típico del Map:

[TestMethod]
public void MapIncrement()
{
    EnumDataset<int> ds = new EnumDataset<int>(new int[] { 1, 2, 3 });
    BaseDataset<int> mapds = ds.Map(i => i + 1);
    var enumerator = mapds.GetEnumerator();

    for (int k = 1; enumerator.MoveNext(); k++)
        Assert.AreEqual(k + 1, enumerator.Current);

    Assert.AreEqual(3, mapds.Count());
}

Y un test del Reduce:

[TestMethod]
public void ReduceSum()
{
    EnumDataset<int> ds = new EnumDataset<int>(new int[] { 1, 2, 3 });
    var result = ds.Reduce<int>((x, y) => x + y);

    Assert.IsNotNull(result);
    Assert.AreEqual(6, result);
}

Próximos temas: más métodos de BaseDataset, datasets concretos, datasets con clave, etc…

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez