net - Tubo hacia adelante en C#
.net programming language (4)
Si bien no es lo mismo, es posible que esté interesado en mi marco de 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 yo hemos implementado la mayoría de los operadores estándar de consultas LINQ, lo que significa que puede escribir expresiones de consulta contra fuentes de datos y hacer cosas divertidas como la agrupación por secuencias, agregaciones múltiples, etc.
Continuando con mi investigación de expresar ideas de F # en C #, quería un operador de reenvío de tuberías. 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.
Aquí hay dos métodos de extensión, me pregunté si alguien más había intentado esto, y si es una buena idea o no (EDITAR: 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; }
Luego 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é, Filter == Where in C #, and Fold == Aggregate, pero quería lanzar el mío, y podría haber hecho WriteAllLines, pero ese no es el punto)
EDITAR: correcciones según el comentario de Earwicker (si he entendido bien).
No me he molestado con una tubería sin procesar, pero he intentado hacer todas las referencias en 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);
}
}
Entonces supongamos que tiene un modelo de objetos que le permite buscar una Compañía de registro por nombre, y luego buscar una Banda dentro de esa Compañía de registro, un Miembro de la Banda, y cualquiera de estos puede devolver nulo, por lo que podría arrojar una NullReferenceException:
var pixiesDrummer = Music.GetCompany("4ad.com")
.GetBand("Pixes")
.GetMember("David");
Podemos arreglarlo:
var pixiesDrummer = Music.GetCompany("4ad.com")
.IfNotNull(rc => rc.GetBand("Pixes"))
.IfNotNull(band => band.GetMember("David"));
Hey presto, si alguna de esas transiciones devuelve nulo, 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 podría conectar mis lambdas de transición así:
var pixiesDrummer = Music.GetCompany("4ad.com")
| rc => rc.GetBand("Pixes")
| band => band.GetMember("David");
Además, ¿no sería genial si System.Void se definiera como un tipo y Action fuera realmente solo Func <..., Void>?
Actualización: publiqué un poco sobre la teoría detrás de esto .
Actualización 2: una respuesta alternativa a la pregunta original, que es más o menos "¿Cómo expresarías el operador de F-pipe forward en C #?"
Pipe-forward es:
let (|>) x f = f x
En otras palabras, le permite escribir una función y su primer argumento en el orden opuesto: argumento seguido por función. Es solo un asistente sintáctico que ayuda con la legibilidad, lo que le permite utilizar la notación infija con cualquier función.
Esto es exactamente para lo que son los métodos de extensió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);
(Ignore el hecho de que también nos permiten omitir el nombre de la clase; eso es una ventaja, pero también podría estar disponible para métodos que no sean de extensión, como lo es en Java).
Entonces C # ya resuelve el mismo problema de una manera diferente.
Tu método de Pipe se parece mucho al Thrush Combinator. Mi implementación es muy simple.
public static T Into<T>(this T obj, Func<T, T> f)
{ return f(obj); }
Entonces, para Piping no creo que exista la expectativa de verificar nulo 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 prevenido, 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 curing 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 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 para que PipeR
funcione.
Aquí está mi implementación de ejemplo:
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);
}