Труба вперед в С#

Продолжая мое исследование выражения идей F # в С#, мне нужен оператор прямой пересылки. Для чего-либо, обернутого в IEnumerable, мы уже имеем его, как вы можете .NextFunc() для вашего сердечного контента. Но, например, если у вас есть сворачивание в конце, вы не можете передать результат этого в функцию.

Вот два метода расширения, я задавался вопросом, попробовал ли кто-нибудь еще это, и если это хорошая идея или нет (EDIT: теперь с Earwicker Maybe):

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; }

Затем вы можете написать что-то вроде:

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));
    };

(Я знаю, Filter == Where in С# и Fold == Aggregate, но я хотел свернуть свое, и я мог бы сделать WriteAllLines, но это не так)

EDIT: исправления в соответствии с комментарием в Earwicker (если я правильно понял).

Ответ 1

Я не потрудился с сырой трубой, но я попытался сделать все ссылки в монаде 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);
    }
}

Тогда предположим, что у вас есть объектная модель, которая позволяет вам искать RecordCompany по имени, а затем искать группу в пределах этого RecordCompany, члена группы, и любой из них может возвращать значение null, поэтому это может вызвать исключение NullReferenceException:/p >

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

Мы можем исправить это:

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

Hey presto, если какой-либо из этих переходов возвращает null, pixiesDrummer будет null.

Не было бы здорово, если бы мы могли использовать методы расширения, которые являются перегрузками операторов?

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

Тогда я мог бы объединить мои переходные лямбды следующим образом:

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

Также было бы здорово, если System.Void был определен как тип, и Action действительно был просто Func <..., Void > ?

Обновление: Я немного рассказывал об этой теории.

Обновление 2: Альтернативный ответ на исходный вопрос, который примерно "Как бы вы выразили оператор прямой линии F # в С#?"

Перемещение по трубе:

let (|>) x f = f x

Другими словами, он позволяет вам написать функцию и ее первый аргумент в обратном порядке: аргумент, за которым следует функция. Это просто синтаксический помощник, который помогает с удобочитаемостью, позволяя вам использовать нотацию infix с любой функцией.

Это именно то, что в С# есть методы расширения. Без них мы должны были бы написать:

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

С ними мы можем написать:

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

(Игнорируйте тот факт, что они также позволяют нам опустить имя класса - это бонус, но также могут быть доступны для методов без расширения, как в Java).

Итак, С# уже решает ту же проблему по-другому.

Ответ 2

Итак, для Piping Я не думаю, что есть ожидание проверки на null и не вызов функции piped. Аргумент функции во многих случаях может легко принять нуль и обработать его функцией.

Вот моя реализация. У меня есть Pipe и PipeR. Будьте осторожны, PipeR не является правильным, а только для случаев, когда цель находится в противоположном положении для каррирования, поскольку альтернативные перегрузки позволяют ограничить фальшивое отображение параметров.

Приятная вещь о фальшивом карринге заключается в том, что вы можете протрубить имя метода после предоставления параметров, тем самым создавая меньше вложенности, чем с lambda.

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

String.Join имеет IEnumerable в последней позиции, так что это работает.

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

Regex.IsMatch имеет цель в первой позиции, поэтому PipeR работает.

Вот пример реализации:

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);
}

Ответ 3

Хотя это не совсем то же самое, вас может заинтересовать мой Push LINQ. В основном, где IEnumerable<T> требуется, чтобы заинтересованная сторона извлекала данные из источника, Push LINQ позволяет вам передавать данные через источник, а заинтересованные стороны могут подписаться на события, соответствующие "другому элементу, который только что прошел" и "данные закончены",.

Марк Гравелл и я внедрили большинство стандартных операторов запросов LINQ, что означает, что вы можете писать выражения запросов в отношении источников данных и делать забавные вещи, такие как потоковая группировка, множественные агрегации и т.д.

Ответ 4

Метод вашей трубы выглядит очень похож на комбинатор дроби. Моя реализация очень проста.

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