Ben*_*jol 24 c# f# functional-programming
继续我在C#中表达F#想法的调查,我想要一个管道前进算子.对于包含在IEnumerable中的任何东西,我们已经拥有它,因为你可以.NextFunc()到你心中的内容.但是,例如,如果最后有任何类似折叠的缩减,则无法将其结果输入函数.
这里有两种扩展方法,我想知道是否有其他人试过这个,如果这是个好主意(编辑:现在有了Earwicker的可能包括在内):
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; }
Run Code Online (Sandbox Code Playgroud)
然后你可以写下这样的东西:
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));
};
Run Code Online (Sandbox Code Playgroud)
(我知道,过滤器==在C#中的位置,并且折叠==聚合,但我想要自己动手,我本可以完成WriteAllLines,但这不是重点)
编辑:根据Earwicker的评论进行更正(如果我理解正确的话).
Dan*_*ker 26
我没有使用原始管道,但我尝试将所有引用都引入到Maybe monad中:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
然后假设您有一个对象模型,它允许您按名称查找RecordCompany,然后在该RecordCompany中查找Band,Band的成员,并且其中任何一个都可能返回null,因此这可能会抛出NullReferenceException:
var pixiesDrummer = Music.GetCompany("4ad.com")
.GetBand("Pixes")
.GetMember("David");
Run Code Online (Sandbox Code Playgroud)
我们可以解决这个问题
var pixiesDrummer = Music.GetCompany("4ad.com")
.IfNotNull(rc => rc.GetBand("Pixes"))
.IfNotNull(band => band.GetMember("David"));
Run Code Online (Sandbox Code Playgroud)
嘿presto,如果这些转换中的任何一个返回null,pixiesDrummer将为null.
如果我们能够做出运算符重载的扩展方法,那不是很好吗?
public static TOut operator| <TIn, TOut>(TIn v, Func<TIn, TOut> f)
Run Code Online (Sandbox Code Playgroud)
然后我可以像这样把我的过渡lambdas连在一起:
var pixiesDrummer = Music.GetCompany("4ad.com")
| rc => rc.GetBand("Pixes")
| band => band.GetMember("David");
Run Code Online (Sandbox Code Playgroud)
如果System.Void被定义为一个类型并且Action真的只是Func <...,Void>,那也不是很好吗?
更新: 我在博客上写了一下这背后的理论.
更新2:原始问题的另一种答案,大致是"你如何在C#中表达F#管道转发运算符?"
管道输送是:
let (|>) x f = f x
Run Code Online (Sandbox Code Playgroud)
换句话说,它允许您以相反的顺序编写函数及其第一个参数:参数后跟函数.它只是一个语法助手,有助于提高可读性,允许您使用任何函数的中缀表示法.
这正是C#中的扩展方法.没有它们,我们必须写:
var n = Enumerable.Select(numbers, m => m * 2);
Run Code Online (Sandbox Code Playgroud)
有了它们,我们可以写:
var n = numbers.Select(m => m * 2);
Run Code Online (Sandbox Code Playgroud)
(忽略这样一个事实,即他们也让我们省略了类名 - 这是一个奖励,但也可以用于非扩展方法,就像在Java中一样).
所以C#已经以不同的方式解决了同样的问题.
因此,对于管道,我认为不需要检查 null 并且不调用管道函数。在许多情况下,函数参数可以很容易地采用 null 并由函数处理它。
这是我的实现。我有Pipe和PipeR。预先警告一下,这PipeR不是管道正确的,而是仅适用于目标处于柯里化的相反位置的情况,因为替代重载允许对参数进行有限的假柯里化。
假柯里化的好处是,您可以在提供参数后通过管道输入方法名称,从而比使用 lambda 产生更少的嵌套。
new [] { "Joe", "Jane", "Janet" }.Pipe(", ", String.Join)
Run Code Online (Sandbox Code Playgroud)
String.Join 在最后一个位置有 IEnumerable,所以这是有效的。
"One car red car blue Car".PipeR(@"(\w+)\s+(car)",RegexOptions.IgnoreCase, Regex.IsMatch)
Run Code Online (Sandbox Code Playgroud)
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);
}
Run Code Online (Sandbox Code Playgroud)