DEV Community

Discussion on: What the heck is Currying anyway?

Collapse
 
slavius profile image
Slavius • Edited

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 • Edited

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
 
thebuildguy profile image
Tulsi Prasad • Edited

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.