Inoltra il tubo in C #

Continuando le mie ricerche sull’esprimere idee F # in C #, volevo un operatore pipe forward. Per tutto ciò che è racchiuso in un object IEnumerable, lo abbiamo già, come è ansible .NextFunc () per il contenuto del tuo cuore. Ad esempio, se alla fine hai una riduzione simile a una piega, non puoi inserire il risultato in una funzione.

Ecco due metodi di estensione, mi chiedevo se qualcun altro avesse provato questo, e se è una buona idea o no (EDIT: ora con Earwicker’s Maybe incluso):

public static void Pipe(this T val, Action action) where T : class { if (val!=null) action(val); } public static R Pipe(this T val, Func func) where T : class where R : class { return val!=null?func(val):null; } 

Puoi quindi scrivere qualcosa come:

 Func readlines = (f) => File.ReadAllLines(f); Action writefile = (f, s) => File.WriteAllText(f, s); Action 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 so, Filter == Where in C #, e Fold == Aggregate, ma volevo girare il mio, e avrei potuto scrivere WriteAllLines, ma non è questo il punto)

EDIT: correzioni come da commento di Earwicker (se ho capito bene).

Non mi sono preoccupato di un pipe crudo, ma ho provato a fare tutti i riferimenti nella Forse monade:

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

Quindi supponiamo di avere un modello di oggetti che ti consenta di cercare un RecordCompany per nome, e quindi di cercare una Band all’interno di RecordCompany, un Membro della Band, e ognuno di questi potrebbe restituire null, quindi questo potrebbe generare una NullReferenceException:

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

Noi possiamo aggiustarlo:

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

Ehilà, se qualcuna di queste transizioni restituisce null, pixiesDrummer sarà nullo.

Non sarebbe bello se potessimo fare metodi di estensione che sono sovraccarichi dell’operatore?

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

Quindi potrei mettere insieme i miei lambda di transizione in questo modo:

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

Inoltre, non sarebbe bello se System.Void fosse definito come un tipo e Action fosse davvero solo Func <..., Void>?

Aggiornamento: ho bloggato un po ‘sulla teoria alla base di questo .

Aggiornamento 2: una risposta alternativa alla domanda originale, che è approssimativamente “Come esprimeresti l’operatore di pipe-forward di F # in C #?”

Il tubo in avanti è:

 let (|>) xf = fx 

In altre parole, consente di scrivere una funzione e il suo primo argomento nell’ordine opposto: argomento seguito dalla funzione. È solo un aiuto sintattico che facilita la leggibilità, consentendo di utilizzare la notazione infix con qualsiasi funzione.

Questo è esattamente ciò che i metodi di estensione sono per in C #. Senza di loro, dovremmo scrivere:

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

Con loro, possiamo scrivere:

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

(Ignorate il fatto che ci lasciano anche omettere il nome della class – questo è un bonus ma potrebbe anche essere reso disponibile per i metodi non di estensione come in Java).

Quindi C # risolve già lo stesso problema in un modo diverso.

Quindi per Piping non penso ci sia da aspettarsi un controllo per null e non chiamare la funzione di piping. L’argomento della funzione in molti casi potrebbe facilmente prendere un null e farlo gestire dalla funzione.

Ecco la mia implementazione. Ho Pipe e PipeR . Sappiate che il PipeR non è un tubo giusto, ma solo per i casi in cui il bersaglio si trova nella posizione opposta per il curriculum, perché i sovraccarichi alternati consentono una limitata falsificazione dei parametri.

La cosa bella del finto currying è che è ansible redirect il nome del metodo dopo aver fornito i parametri, producendo così meno nesting rispetto a un lambda.

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

String.Join ha l’object IEnumerable nell’ultima posizione, quindi funziona.

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

Regex.IsMatch ha l’objective nella prima posizione in modo che PipeR .

Ecco il mio esempio di implementazione:

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

Anche se non è esattamente la stessa cosa, potresti essere interessato al mio framework Push LINQ . Fondamentalmente, quando IEnumerable richiede alla parte interessata di estrarre dati da una fonte, Push LINQ consente di inviare dati attraverso un’origine e le parti interessate possono iscriversi agli eventi corrispondenti a “un altro elemento è appena passato” e “i dati sono finiti “.

Marc Gravell e io abbiamo implementato la maggior parte degli operatori di query LINQ standard, il che significa che è ansible scrivere espressioni di query su origini dati e fare cose divertenti come lo streaming grouping, più aggregazioni, ecc.

Il tuo metodo Pipe assomiglia molto al Thrush Combinator. La mia implementazione è molto semplice.

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