Труба вперед в С#
Продолжая мое исследование выражения идей 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); }