loading...

Discussion on: What the heck is Currying anyway?

Collapse
slavius profile image
Slavius

Normal function in IL:

IL_0000: ldarg.0      // a
IL_0001: ldarg.1      // b
IL_0002: add
IL_0003: ldarg.2      // c
IL_0004: add
IL_0005: ldarg.3      // d
IL_0006: add
IL_0007: ret

// function call
IL_0000: ldc.i4.1
IL_0001: ldc.i4.2
IL_0002: ldc.i4.3
IL_0003: ldc.i4.4
IL_0004: call  int32 CurryDemo.BenchmarkNormalVsCurry::NormalAdd(int32, int32, int32, int32)

Curry function in IL:

IL_0000: newobj  instance void CurryDemo.BenchmarkNormalVsCurry/'<>c__DisplayClass1_0'::.ctor()
IL_0005: dup
IL_0006: ldarg.0      // a
IL_0007: stfld  int32 CurryDemo.BenchmarkNormalVsCurry/'<>c__DisplayClass1_0'::a

IL_000c: ldftn  instance class [System.Runtime]System.Func`2<int32, class [System.Runtime]System.Func`2<int32, int32>> CurryDemo.BenchmarkNormalVsCurry/'<>c__DisplayClass1_0'::'<CurryAdd>b__0'(int32)
IL_0012: newobj  instance void class [System.Runtime]System.Func`2<int32, class [System.Runtime]System.Func`2<int32, class [System.Runtime]System.Func`2<int32, int32>>>::.ctor(object, native int)
IL_0017: ret

// function call
IL_0000: ldc.i4.1
IL_0001: call         class [System.Runtime]System.Func`2<int32, class [System.Runtime]System.Func`2<int32, class [System.Runtime]System.Func`2<int32, int32>>> CurryDemo.BenchmarkNormalVsCurry::CurryAdd(int32)
IL_0006: ldc.i4.2
IL_0007: callvirt     instance !1/*class [System.Runtime]System.Func`2<int32, class [System.Runtime]System.Func`2<int32, int32>>*/ class [System.Runtime]System.Func`2<int32, class [System.Runtime]System.Func`2<int32, class [System.Runtime]System.Func`2<int32, int32>>>::Invoke(!0/*int32*/)
IL_000c: ldc.i4.3
IL_000d: callvirt     instance !1/*class [System.Runtime]System.Func`2<int32, int32>*/ class [System.Runtime]System.Func`2<int32, class [System.Runtime]System.Func`2<int32, int32>>::Invoke(!0/*int32*/)
IL_0012: ldc.i4.4
IL_0013: callvirt     instance !1/*int32*/ class [System.Runtime]System.Func`2<int32, int32>::Invoke(!0/*int32*/)

Benchmark:

// * Summary *

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.450 (2004/?/20H1)
AMD Ryzen 7 3800X, 1 CPU, 16 logical and 8 physical cores
.NET Core SDK=3.1.401
  [Host]        : .NET Core 3.1.7 (CoreCLR 4.700.20.36602, CoreFX 4.700.20.37001), X64 RyuJIT
  .NET Core 3.1 : .NET Core 3.1.7 (CoreCLR 4.700.20.36602, CoreFX 4.700.20.37001), X64 RyuJIT

Job=.NET Core 3.1  Runtime=.NET Core 3.1

|        Method |       Mean |     Error |    StdDev |
|-------------- |-----------:|----------:|----------:|
| TestNormalAdd |  0.0203 ns | 0.0093 ns | 0.0083 ns |
|  TestCurryAdd | 33.1419 ns | 0.2941 ns | 0.2607 ns |

// * Hints *
Outliers
  BenchmarkNormalVsCurry.TestNormalAdd: .NET Core 3.1 -> 1 outlier  was  removed (1.57 ns)
  BenchmarkNormalVsCurry.TestCurryAdd: .NET Core 3.1  -> 1 outlier  was  removed, 2 outliers were detected (33.77 ns, 35.37 ns)

// * Legends *
  Mean   : Arithmetic mean of all measurements
  Error  : Half of 99.9% confidence interval
  StdDev : Standard deviation of all measurements
  1 ns   : 1 Nanosecond (0.000000001 sec)

// ***** BenchmarkRunner: End *****

the program:

    class Program
    {
        static void Main(string[] args)
        {
            var summary = BenchmarkRunner.Run<BenchmarkNormalVsCurry>();
        }
    }

    [SimpleJob(RuntimeMoniker.NetCoreApp31)]
    [RPlotExporter]
    public class BenchmarkNormalVsCurry
    {
        public static int NormalAdd(int a, int b, int c, int d)
        {
            return a + b + c + d;
        }

        public static Func<int, Func<int, Func<int, int>>> CurryAdd(int a) =>
            (b) =>
                (c) =>
                    (d) => a + b + c + d;

        [Benchmark]
        public void TestNormalAdd() => NormalAdd(1, 2, 3, 4);

        [Benchmark]
        public void TestCurryAdd() => CurryAdd(1)(2)(3)(4);
    }
Collapse
xtofl profile image
xtofl

Fantastic that you emphasize this. While FP code constructs unclutter the code, without proper tool support you just move the clutter into the runtime.

Indeed, JavaScript (nor C#) was never designed to go full throttle on functional programming concepts. Therefor the JIT compiler doesn't optimize any of it.

Languages like Haskell, F#, Erlang, Lisp, are built for this, and will handle it in a better way.

Collapse
heytulsiprasad profile image
Tulsi Prasad Author

Thanks man, for putting this out here!

I'm definitely interested to know what happens behind all the functions and about compilation. However, I'm not quite experienced at it, but hopefully this will be helpful to others and even me in future.