2008-12-03 13 views
15

Continuando my investigation de expresar ideas de F # en C#, quería un operador de reenvío de pipa. Para cualquier cosa envuelta en un IEnumerable, ya la tenemos, como puedas .NextFunc() para tu corazón. Pero, por ejemplo, si tiene una reducción tipo pliegue al final, no puede alimentar el resultado de eso en una función.Reenvíos de tuberías en C#

Aquí son dos métodos de extensión, Me preguntaba si alguien más había intentado esto, y si es una buena idea o no (EDIT: ahora con Earwicker's Maybe incluido):

public static void Pipe<T>(this T val, Action<T> action) where T : class 
{ if (val!=null) action(val); } 

public static R Pipe<T, R>(this T val, Func<T, R> func) where T : class where R : class 
{ return val!=null?func(val):null; } 

A continuación, puede escribir algo como:

Func<string, string[]> readlines = (f) => File.ReadAllLines(f); 
Action<string, string> writefile = (f, s) => File.WriteAllText(f, s); 

Action<string, string> RemoveLinesContaining = (file, text) => 
    { 
     file.Pipe(readlines) 
      .Filter(s => !s.Contains(text)) 
      .Fold((val, sb) => sb.AppendLine(val), new StringBuilder()) 
      .Pipe((o) => o.ToString()) 
      .Pipe((s) => writefile(file, s)); 
    }; 

(lo sé, Filtro == Cuando en C#, y plegar == agregada, pero quería rodar la mía, y que podría tener WriteAllLines hecho, pero ese no es el punto)

EDITAR: correcciones según el comentario de Earwicker (si he entendido bien).

+0

Creo que esto se llama componer, no de tuberías. – leppie

+0

No, pero están relacionados, mira aquí: http://blogs.msdn.com/chrsmith/archive/2008/06/14/function-composition.aspx – Benjol

+0

Par de errores: necesitas una restricción 'donde T: clase 'o no se puede comparar con nulo. Además, solo has puesto el cheque de nulo en la versión de Func. En un mundo ideal, solo necesitaría escribir la versión de Func: consulte 'void' en http://stackoverflow.com/questions/27731/whats-wrong-with-c#303071 –

Respuesta

19

no me he molestado con una tubería de crudo, pero he tratado de hacer todas las referencias a la mónada Maybe:

public static class ReferenceExtensions 
{ 
    public static TOut IfNotNull<TIn, TOut>(this TIn v, Func<TIn, TOut> f) 
            where TIn : class 
            where TOut: class 
    { 
     if (v == null) 
      return null; 

     return f(v); 
    } 
} 

a continuación, supongamos que tiene un modelo de objetos que permite las operaciones de búsqueda de un RecordCompany por su nombre y, a continuación, las operaciones de búsqueda de una banda dentro de ese RecordCompany, un miembro de la Banda, y cualquiera de estos puede devolver nulo, por lo que esto podría arrojar un NullReferenceExceptio n:

var pixiesDrummer = Music.GetCompany("4ad.com") 
         .GetBand("Pixes") 
         .GetMember("David"); 

Podemos arreglar esto:

var pixiesDrummer = Music.GetCompany("4ad.com") 
         .IfNotNull(rc => rc.GetBand("Pixes")) 
         .IfNotNull(band => band.GetMember("David")); 

Hey presto, si cualquiera de esas transiciones devolver null, pixiesDrummer será nulo.

¿No sería genial si pudiéramos hacer métodos de extensión que son sobrecargas del operador?

public static TOut operator| <TIn, TOut>(TIn v, Func<TIn, TOut> f) 

Entonces pude tubería juntos mis lambdas de transición como esta:

var pixiesDrummer = Music.GetCompany("4ad.com")  
        | rc => rc.GetBand("Pixes") 
        | band => band.GetMember("David"); 

también ¿no sería grande si System.Void se define como un tipo de acción y en realidad era sólo Func < .. ., Void>?

Actualización:I blogged a little about the theory behind this.

Actualización 2: Una respuesta alternativa a la pregunta original, que es aproximadamente "¿Cómo expresaría el operador F-pipe-forward en C#?"

Tubo de avance es:

let (|>) x f = f x 

En otras palabras, se le permite escribir una función y su primer argumento en el orden inverso:. Argumento seguido por la función Es sólo un ayudante sintáctica que ayuda a la legibilidad , que le permite hacer uso de la notación infija con cualquier función

Esto es exactamente lo que los métodos de extensión están en C# Sin ellos, tendríamos que escribir:..

var n = Enumerable.Select(numbers, m => m * 2); 

Con ellos, podemos escribir:

var n = numbers.Select(m => m * 2); 

(ignorar el hecho de que también vamos a omitir el nombre de clase - que es una ventaja pero también podrían estar disponibles para los métodos no-extensión, ya que es en Java).

Así que C# ya resuelve el mismo problema de una manera diferente.

+0

Bien, supongo que podría incluir su lógica en mi pipe ... – Benjol

+0

Me hace un gran honor, señor. –

+0

Si define un Seleccionar() para Maybe puede escribir "from Maybe rc en Music.GetCompany() let band = rc.GetBand() seleccione band.GetMember()". –

1

Aunque no es exactamente lo mismo, puede que le interese mi framework Push LINQ. Básicamente, donde IEnumerable<T> requiere que el interesado extraiga datos de una fuente, Push LINQ le permite enviar datos a través de una fuente, y las partes interesadas pueden suscribirse a eventos correspondientes a "otro elemento acaba de pasar" y "los datos han terminado" .

Marc Gravell y me han aplicado la mayoría de los operadores de consulta estándar de LINQ, lo que significa que puede escribir expresiones de consulta contra fuentes de datos y hacer cosas divertidas, como streaming de agrupación, múltiples agregaciones etc.

+3

¡Guau! Supongo que este comentario es pre RX framework :) – bradgonesurfing

+1

@bradgonesurfing: Sé que comencé a desarrollar Push LINQ antes de saber sobre Rx. Sin embargo, no sé cuánto sabía por diciembre de 2008 :) –

1

Su método de tubería se parece mucho al Thrush Combinator. My implementation es muy simple.

public static T Into<T>(this T obj, Func<T, T> f) 
{ return f(obj); } 
2

Por lo tanto, para tuberías, no creo que exista la expectativa de verificar la nulidad y no llamar a la función de canalización. El argumento de la función en muchos casos podría tomar un nulo y hacer que la función lo maneje.

Aquí está mi implementación. Tengo Pipe y PipeR. Se advirtió, el PipeR no es un tubo correcto, sino solo para los casos en los que el objetivo está en la posición opuesta para currying, porque las sobrecargas alternativas permiten un currying falso de parámetros.

Lo bueno de la falsificación del currículum es que puede canalizar el nombre del método después de proporcionar los parámetros, lo que produce menos anidamiento que con una lambda.

new [] { "Joe", "Jane", "Janet" }.Pipe(", ", String.Join) 

String.Join tiene el IEnumerable en la última posición, así que esto funciona.

"One car red car blue Car".PipeR(@"(\w+)\s+(car)",RegexOptions.IgnoreCase, Regex.IsMatch) 

Regex.IsMatch tiene el objetivo en la primera posición de manera PipeR obras.

Aquí está mi ejemplo implementaion:

public static TR Pipe<T,TR>(this T target, Func<T, TR> func) 
{ 
    return func(target); 
} 

public static TR Pipe<T,T1, TR>(this T target, T1 arg1, Func<T1, T, TR> func) 
{ 
    return func(arg1, target); 
} 

public static TR Pipe<T, T1, T2, TR>(this T target, T1 arg1, T2 arg2, Func<T1, T2, T, TR> func) 
{ 
    return func(arg1, arg2, target); 
} 

public static TR PipeR<T, T1, TR>(this T target, T1 arg1, Func<T, T1, TR> func) 
{ 
    return func(target, arg1); 
} 

public static TR PipeR<T, T1, T2, TR>(this T target, T1 arg1, T2 arg2, Func<T, T1, T2, TR> func) 
{ 
    return func(target, arg1, arg2); 
} 
Cuestiones relacionadas