代码之家  ›  专栏  ›  技术社区  ›  Adrian Zanescu

C#lambda-curry用例

  •  12
  • Adrian Zanescu  · 技术社区  · 16 年前

    我读书 This article 我发现它很有趣。

    对于那些不想阅读整篇文章的人来说,总结一下。作者实现了一个名为Curry的高阶函数,如下所示(由我重构,没有他的内部类):

     public static Func<T1, Func<T2, TResult>> 
                 Curry<T1, T2, TResult>(this Func<T1, T2, TResult> fn)
     {
         Func<Func<T1, T2, TResult>, Func<T1, Func<T2, TResult>>> curry = 
         f => x => y => f(x, y);
         return curry(fn);
     }
    

    这使我们能够接受像F(x,y)这样的表达式 如。

    Func<int, int, int> add = (x, y) => x + y;
    

    并以F.Curry(x)(y)的方式调用它;

    这部分我理解了,我觉得它以一种极客的方式很酷。我没能理解的是这种方法的实际用例。何时何地需要这种技术,可以从中获得什么?

    提前感谢。

    编辑: 在最初的3个响应之后,我明白,在某些情况下,当我们从curried创建一个新函数时,一些参数不会被重新评估。 我用C#做了这个小测试(请记住,我只对C#实现感兴趣,对咖喱理论不感兴趣):

    public static void Main(string[] args)
    {
        Func<Int, Int, string> concat = (a, b) => a.ToString() + b.ToString();
        Func<Int, Func<Int, string>> concatCurry = concat.Curry();
        Func<Int, string> curryConcatWith100 = (a) => concatCurry(100)(a);
    
        Console.WriteLine(curryConcatWith100(509));
        Console.WriteLine(curryConcatWith100(609));
    }
    
        public struct Int
        {
            public int Value {get; set;}
    
            public override string ToString()
            {
                 return Value.ToString();
            }
    
            public static implicit operator Int(int value)
            {
                return new Int { Value = value };
            }
        }
    

    在连续两次调用curryConcatWith100时,值100的ToString()求值被调用两次(每次调用一次),所以我看不到这里的求值有任何好处。我错过了什么吗?

    6 回复  |  直到 16 年前
        1
  •  18
  •   Cameron MacFarland    16 年前

    Currying用于将具有x参数的函数转换为具有y参数的函数,因此可以将其传递给需要具有y参数函数的另一个函数。

    例如, Enumerable.Select(this IEnumerable<T> source, Func<TSource, bool> selector) 接受一个有1个参数的函数。 Math.Round(double, int) 是一个有2个参数的函数。

    你可以用咖喱来“储存” Round 函数作为数据,然后将该curried函数传递给 Select 像那样

    Func<double, int, double> roundFunc = (n, p) => Math.Round(n, p);
    Func<double, double> roundToTwoPlaces = roundFunc.Curry()(2);
    var roundedResults = numberList.Select(roundToTwoPlaces);
    

    这里的问题是,还有匿名代表,这使得讨好变得多余。事实上,匿名代表 一种咖喱。

    Func<double, double> roundToTwoPlaces = n => Math.Round(n, 2);
    var roundedResults = numberList.Select(roundToTwoPlaces);
    

    甚至只是

    var roundedResults = numberList.Select(n => Math.Round(n, 2));
    

    Currying是一种解决特定问题的方法,考虑到某些函数式语言的语法。使用匿名委托和lambda运算符,语法如下。NET要简单得多。

        2
  •  12
  •   Community CDub    5 年前

    首先考虑fn(x,y,z)更容易。这可以通过使用fn(x,y)进行curried,给你一个只接受一个参数z的函数。任何需要单独使用x和y完成的事情都可以通过返回的函数所持有的闭包来完成和存储。

    现在,您可以使用z的各种值多次调用返回的函数,而无需重新计算所需的x和y部分。

    编辑:

    实际上,咖喱有两个原因。


    参数缩减

    正如Cameron所说,将一个需要2个参数的函数转换为一个只需要1个的函数。使用参数调用此curried函数的结果与使用2个参数调用原始函数的结果相同。

    由于C#中存在Lambdas,这具有有限的价值,因为无论如何这些都可以提供这种效果。虽然你使用的是C#2,但你问题中的Curry函数具有更大的价值。

    分期计算

    咖喱的另一个原因正如我之前所说。当向curried函数提供最终参数时,允许复杂/昂贵的操作分阶段进行并多次重复使用。

    这种类型的curry在C#中是不可能的,它确实需要一种函数式语言来实现它的任何函数。


    结论

    通过你提到的Curry进行参数缩减在C#2中很有用,但在C#3中由于Lambdas而大大降低了值。

        3
  •  0
  •   Ric Tokyo    16 年前

    从某种意义上说,咖喱是一种技巧 启用自动部分应用。

    更正式地说,咖喱是一种技巧 将函数转化为函数 只接受一个 争论。

    反过来,当调用该函数时 返回另一个接受的函数 只有一个论点。 . .所以 直到“原始”功能 能够被执行。

    从一个线程 codingforums

    我特别喜欢这个问题的解释和篇幅 page .

        4
  •  0
  •   gimpf    16 年前

    一个例子:你有一个函数 compare(criteria1, criteria2, option1, option2, left, right) 。但是当你想提供功能时 compare 对于某种对列表进行排序的方法,那么 compare() 必须只接受两个论点, compare(left, right) 使用curry,您可以根据需要绑定criteria参数来对列表进行排序,最后,这个高度可配置的函数会像其他普通函数一样呈现给排序算法 compare(left,right) .

    详细信息:。NET委托使用隐式currying。类的每个非静态成员函数都有一个隐式 this 引用,不过,当你编写委托时,你不需要手动使用一些curry来绑定 功能。相反,C#关心语法糖,自动绑定它,并返回一个只需要剩下参数的函数。

    在C++中,boost::bind等用于相同的目的。和往常一样,在C++中,一切都更加显式(例如,如果你想将实例成员函数作为回调传递,你需要显式绑定 ).

        5
  •  0
  •   Jaider    13 年前

    我有一个愚蠢的例子: 不整洁版本:

    void print(string name, int age, DateTime dob)
    {
        Console.Out.WriteLine(name);
        Console.Out.WriteLine(age);
        Console.Out.WriteLine(dob.ToShortDateString());
        Console.Out.WriteLine();
    }
    

    咖喱功能:

    public Func<string, Func<int, Action<DateTime>>> curry(Action<string, int, DateTime> f)
    {
        return (name) => (age) => (dob) => f(name, age, dob);
    }
    

    用途:

    var curriedPrint = curry(print);
    curriedPrint("Jaider")(29)(new DateTime(1983, 05, 10)); // Console Displays the values
    

    玩得开心

        6
  •  -1
  •   codybartfast    16 年前

    这是另一个如何使用Curry函数的例子。根据某些条件(例如一周中的某一天),您可以在更新文件之前决定应用什么存档策略。

        void ArchiveAndUpdate(string[] files)
        {
            Func<string, bool> archiveCurry1 = (file) =>
                Archive1(file, "archiveDir", 30, 20000000, new[] { ".tmp", ".log" });
    
            Func<string, bool> archiveCurry2 = (file) =>
                Archive2("netoworkServer", "admin", "nimda", new FileInfo(file));
    
            Func<string, bool> archvieCurry3 = (file) => true;
    
            // backup locally before updating
            UpdateFiles(files, archiveCurry1);
    
            // OR backup to network before updating
            UpdateFiles(files, archiveCurry2);
    
            // OR do nothing before updating
            UpdateFiles(files, archvieCurry3);
        }
    
        void UpdateFiles(string[] files, Func<string, bool> archiveCurry)
        {
            foreach (var file in files)
            {
                if (archiveCurry(file))
                {
                    // update file //
                }
            }
        }
    
        bool Archive1(string fileName, string archiveDir, 
            int maxAgeInDays, long maxSize, string[] excludedTypes)
        {
            // backup to local disk
            return true;
        }
    
        bool Archive2(string sereverName, string username, 
            string password, FileInfo fileToArchvie)
        {
            // backup to network
            return true;
        }