代码之家  ›  专栏  ›  技术社区  ›  Benjol

管道在C中向前#

  •  20
  • Benjol  · 技术社区  · 16 年前

    持续的 my investigation 为了用C语言表达F思想,我需要一个管道转发操作员。对于任何用IEnumerable包装的东西,我们已经有了它,正如您所能的那样.nextfnc()满足于您的心。但是,例如,如果在末尾有任何类似于折叠的缩减,则不能将其结果输入函数。

    这里有两种扩展方法,我想知道是否还有其他人尝试过这种方法,以及它是否是一个好主意(编辑:现在使用 Earwicker's 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 c,fold==aggregate,但我想自己滚,我本可以写所有行,但这不是重点)

    编辑:根据Earwicker的评论进行更正(如果我理解正确的话)。

    4 回复  |  直到 16 年前
        1
  •  20
  •   Daniel Earwicker    14 年前

    我对生管不感兴趣,但我试着把所有的参考资料都写进了可能的单子:

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

    然后假设您有一个对象模型,允许您按名称查找一个记录公司,然后查找该记录公司内的一个带区,该带区的一个成员,并且其中任何一个都可能返回空值,因此这可能引发空引用异常:

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

    嘿,presto,如果这些转换中的任何一个返回空值,pixiesdrummer将为空。

    如果我们可以做一些扩展方法作为运算符重载,这不是很好吗?

    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>,这不是很好吗?

    更新: I blogged a little about the theory behind this .

    更新2: 原始问题的另一个答案大致是“您将如何用C表示f pipe forward操作符?”

    管道前进是:

    let (|>) x f = f x
    

    换句话说,它允许您以相反的顺序编写一个函数及其第一个参数:参数后跟函数。它只是一个帮助阅读的语法助手,允许您在任何函数中使用中缀符号。

    这正是C中的扩展方法。如果没有它们,我们将不得不写:

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

    有了它们,我们可以写:

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

    (忽略了它们也让我们省略类名的事实——这是一个奖励,但也可以用于非扩展方法,就像它在Java中一样)。

    所以C已经以不同的方式解决了相同的问题。

        2
  •  3
  •   jbtule    8 年前

    所以对于管道,我认为不需要检查空值,也不需要调用管道函数。在许多情况下,函数参数很容易接受一个空值,并由函数处理。

    这是我的实现。我有 Pipe PipeR . 被预先警告, 吹笛者 不是pipe-right,而是仅针对目标位置相反的情况进行计算,因为交替重载允许有限的参数假计算。

    假货币的好处在于,在提供参数之后,您可以输入方法名,从而比使用lambda生成的嵌套更少。

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

    join在最后一个位置上有IEnumerable,所以可以这样做。

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

    Regex.IsMatch 目标在第一个位置,所以 吹笛者 作品。

    下面是我的示例实现:

    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
  •  1
  •   Jon Skeet    16 年前

    虽然不完全一样,但你可能对我的 Push LINQ 框架。基本上在哪里 IEnumerable<T> 需要相关方从源中提取数据,push-linq允许您推送数据 通过 一个源和相关方可以订阅对应于“另一个元素刚刚过去”和“数据已经完成”的事件。

    MarcGravell和我已经实现了大多数标准的Linq查询操作符,这意味着您可以针对数据源编写查询表达式,并做一些有趣的事情,比如流分组、多个聚合等。

        4
  •  1
  •   Jonas Elfström    13 年前

    你的Pipe方法看起来非常像画眉组合器。 My implementation 其中很简单。

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