Saturday 9 December 2017

Lambas vs Arrows

I pretty like that anonymous functions via arrow functions in Javascript use the same syntax that anonymous methods (that involve the creation of delegates) via lambda expressions and statements in C# (=> rather than -> in Java). Notice that the C# Expression Bodied Members about which I wrote some time ago, are a different story, they do not involve delegates and indeed are not anonymous.

Apart from the common syntax, there are some differences and similarities that I'd like to remark

C# lambdas

  • They can be async and contain await:

    Func<string, Task<string>> asyncFn = async (txt) => {
                    Console.WriteLine("before");
                    await Task.Delay(2000);
                    Console.WriteLine("after");
                    return txt.ToUpper();
                };
    

    but they can not contain a yield. So can not be used for Iterator methods. This code:

    //error CS1621: The yield statement cannot be used inside an anonymous method or lambda expression
                Func<IEnumerable<string>> IteratorFn = () => {
                    yield return "A";
                    yield return "B";
                };
    

    will throw this compilation error: error CS1621: The yield statement cannot be used inside an anonymous method or lambda expression

  • They can be immediately invoked, but you need to provide the type of the delegate that you are creating. So this will fail to compile:

    //basically the compiler can not work out the delegate type to be created
    string result = ((string txt) => txt.ToUpper())("hi");
    string result = ((string txt) => txt.ToUpper()).Invoke("hi");
    

    but this verbose code will work:

    string result = ((Func<string, string>)((txt) => txt.ToUpper()))("hi");
    result = new Func<string, string>((txt) => txt.ToUpper())("hi");
    Console.WriteLine(result);
    

    The reason why it fails in the first case is that the compiler can not infere the Delegate type that it has to create. It's well explained here.

  • Lambdas can be recursive. Thought they are anonymous we can recursively invoke them by using the name of the variable it has been assigned to and that has been trapped in a closure. The odd thing is that the variable has to be declared before, in a separate line from the assignment).
    So while this does not compile (Use of unassigned local variable Error):

     Func<string, int, string> recursiveExpand = (txt, n) => {
      if (n == 0)
                        return "";
                    if (n == 1)
                        return txt;
                    return txt + recursiveExpand(txt, --n);
                };
    

    This will compile fine

    Func<string, int, string> recursiveExpand = null;
                recursiveExpand = (txt, n) => {
                    if (n == 0)
                        return "";
                    if (n == 1)
                        return txt;
                    return txt + recursiveExpand(txt, --n);
                };
    

JavaScript arrows

  • Same as in C# they can be async and contain await:

    let asyncFn = async (txt) => {
     let res = await new Promise((resolve, reject) => setTimeout(() => {
      resolve(txt.toUpperCase());
     }, 3000));
     console.log("processing done");
     return res;
    };
    

    but they can not contain a yield. So can not be used for generator functions. In Javascript a generator function has to be defined with function*, you can not define it with *(). It seems the reason for this is simply that the ES6 designers did not feel this feature worth the effort it would need to be implemented.

  • Arrows can be immediately invoked. This code will run fine:

    let salesSeason = true;
    let obj = {
     name: "Soumission",
     price: (()=>{
      let basePrice = 400;
      if (salesSeason){
       return basePrice - (basePrice * 0.10); 
      }
      return basePrice;
     })()
    };
    console.log(JSON.stringify(obj));
    
  • You can define recursive arrows with no need to declare the function variable in a separate line.

    let recursiveExpand = (txt, n) => {
     if (n == 0)
      return "";
     if (n == 1)
      return txt;
     return txt + recursiveExpand(txt, --n);
    };
    console.log(recursiveExpand("A", 3));
    

No comments:

Post a Comment