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

C#lambda-咖喱用例

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

    我读 This article 我觉得很有趣。

     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个响应之后,我了解到,在某些情况下,当我们从当前函数创建新函数时,一些参数不会重新计算。 我在C#中做了这个小测试(请记住,我只对C#的实现感兴趣,而不是一般的curry理论):

    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 年前

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

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

    你可以用咖喱来“储存”食物 Round 函数作为数据,然后将当前函数传递给 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));
    

    咖喱是一种解决特定函数式语言语法问题的方法。使用匿名委托和lambda运算符,.NET中的语法要简单得多。

        2
  •  12
  •   Community kfsone    4 年前

    首先考虑FN(x,y,z)更容易。这可以通过使用fn(x,y)得到一个只接受一个参数z的函数来实现。需要单独使用x和y执行的任何操作都可以通过返回函数保留的闭包来完成和存储。

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

    咖喱有两个原因。


    参数缩减

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

    在C#中存在lambda时,这具有有限的价值,因为它们无论如何都可以提供这种效果。尽管您使用的是C#2,但您问题中的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) .

    详细信息:。净委托使用隐式咖喱。类的每个非静态成员函数都有一个隐式 this 引用,但在编写委托时,不需要手动使用一些curry来绑定 对函数进行修改。相反,C#关心语法糖,自动绑定它,并返回一个只需要剩下参数的函数。

    在C++中,Boo::Bin等被用于相同的。和往常一样,C++中的所有东西都更明确一些(例如,如果您想将实例成员函数传递回回调),则需要显式绑定

        5
  •  0
  •   Jaider    12 年前

    我举了一个愚蠢的例子:

    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 年前

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