C# vs JavaScript: Lambdas

This post belongs to the C# vs JavaScript series.
by Nicklas Envall

Lambda expressions derive from λ (lambda) calculus. Concisely put, lambda expressions are a compact way to create anonymous functions. These functions get treated as values that may be assigned to variables or passed to other functions.

Assigning variables to functions and passing them around is very common in JavaScript. Not only because of its dynamic nature. But also because Scheme influenced JavaScript, a language heavily based on λ calculus. As Crockford explains in the book, Good Parts, "JavaScript is the first lambda language to go mainstream. Deep down, JavaScript has more in common with Lisp and Scheme than with Java. It is Lisp in C's clothing."

As a result, passing around functions and creating lambda expressions in JavaScript is easier and more natural than C#. However, did you know that you can achieve something similar in C#? In C#, we do have lambda expressions. But to understand how to use them in C#, you must start by looking at delegates. So, this article will look at how to get started with lambda expressions both in C# and JavaScript.

Using lambdas in C#

A delegate is an object that knows how to call a method. A delegate type defines the method's return type and parameter type that the delegate instances can call. To create a delegate instance, you assign a method to a variable declared as a delegate:

public class Program
{
	delegate int MyDelegate (int x, int y);
	static int Add (int x, int y) { return x + y; }

	public static void Main()
	{
		MyDelegate d = Add;
		int result = d(2, 4);
        // result = 6
	}
}

Multicasting is also possible and achieved by continuing to add methods to your variable. The added methods get invoked in their added order. Note that delegates are immutable, so a new delegate instance is created and assigned to the variable.

public class Program
{
	delegate void MyDelegate ();
	static void MethodOne () { Console.WriteLine("MethodOne"); }
	static void MethodTwo () { Console.WriteLine("MethodTwo"); }

	public static void Main()
	{
		MyDelegate d = MethodOne;
		d += MethodTwo;
		d();
        // MethodOne
        // MethodTwo
	}
}

But we don't have to create new delegate types every time. We have Func<>, Action<>, and Predicate<>, which are pre-defined delegate types provided by the System namespace. They cover different scenarios, so we can reuse those instead of creating our types.

public class Program
{
	static bool IsEqual (int x, int y) { return x == y; }

	public static void Main()
	{		
		Func<int, int, bool> isEqual = IsEqual;
        isEqual(1, 2); // False
        isEqual(2, 2); // True 
	}
}

In C#, a lambda expression is used to create an anonymous function, and that function is used instead of a delegate instance. In reality, it's just another way of specifying a delegate because the compiler converts it to a delegate instance or expression tree. The compiler internally makes sure that the code of the lambda expression gets put in a private method. But, the syntax is more compact and, in essence, looks like the following example:

(parameters) => expression
(parameters) => { statements }

Consequently, we can rewrite our isEqual function shown in the Func example like this:

public class Program
{
	public static void Main()
	{		
		Func<int, int, bool> isEqual = (x, y) => x == y;
		isEqual(1, 2); // False
		isEqual(2, 2); // True
	}
}

In addition, there's another way to create an anonymous function, and that's with anonymous delegates. It's sometimes confusingly referred to as anonymous methods, and is an older and pre-lambda expression way of creating anonymous functions with the delegate keyword. Unlike lambda expressions, they must always have a block statement:

public class Program
{
	public static void Main()
	{		
		Func<int, int, bool> isEqual = delegate (int x, int y) { return x == y; };
		isEqual(1, 2); // False
		isEqual(2, 2); // True
	}
}

Now, as a result of our newly acquired knowledge, some of the static methods in the Array class should make more sense. Especially the "find methods" that requires an array and a predicate. What's a predicate? A Lambda expression that takes a value and returns a bool is called a predicate. Here are a couple of examples:

var numbers = new int[] { 1, 2, 3, 2 };

// Array.Find<T>(T[], Predicate<T>)
Array.Find(numbers, num => num == 2); // 2
Array.Find(numbers, num => num == 3); // 3
Array.Find(numbers, num => num == 6); // 0

// Array.FindLast<T>(T[], Predicate<T>)
Array.FindLast(numbers, num => num == 2); // 2

// Array.FindAll<T>(T[], Predicate<T>)
Array.FindAll(numbers, num => num == 2); // Int32[] { 2, 2 }

// Array.Exists<T>(T[], Predicate<T>)
Array.Exists(numbers, num => num == 2); // True

Using lambdas in JavaScript

Creating lambda expressions in JavaScript is easy and feels natural. One main reason for this is that JavaScript functions are by default "first-class citizens." Functions being first-class means we can treat them like any other data type and do things like:

  • Assigning them to variables.
  • Passing them to functions.
  • Storing them in arrays.

It's possible because JavaScript functions are objects, function objects. They get created with the function keyword or so-called arrow functions. So, a lambda expression may look like this in JavaScript:

function(x) {
  return x * 2;
};

Contrary to what you might think, lambdas are not as simple as lambdas = arrow functions. There are two approaches to creating anonymous functions in JavaScript:

  1. The function keyword while omitting the name.
  2. Arrow function expression.

In the book "Programming JavaScript Applications," author Eric Elliot writes that it's not even as simple as lambdas = anonymous functions. Instead, Elliot says that "a lambda is a function that is used as data." But let's not get stuck on that. I think it's important to know that in JavaScript, we use lambdas, for example, to:

  • Pass functions as arguments to other functions.
  • Return functions as data from other functions.
  • Pass callbacks that get invoked inside other functions.
  • React to events with callbacks.

Consequently, we often use higher-order functions (HOFs) when working with lambdas. A HOF is a function that receives functions or returns functions. For example, Array.prototype.map is a HOF that takes a callback function, and that callback later gets invoked for each element. Each element gets used as the argument for each call, and each returned value creates a new array together.

const numbers = [1, 2, 3, 4, 5];
const squaredNumbers = numbers.map(function (x) {
  return x * x;
});
squaredNumbers; // [ 1, 4, 9, 16, 25 ]

As we see, using the function keyword is in some sense similar to using the delegate keyword to create anonymous functions in C#. Additionally, arrow function expressions provide us with pretty much identical syntax like lambda expressions in C#. It's simply a more compact way of creating anonymous functions:

const numbers = [1, 2, 3, 4, 5];
const squaredNumbers = numbers.map((x) => x * x);
squaredNumbers; // [ 1, 4, 9, 16, 25 ]

Furthermore, there are more things to arrow functions, such as how it relates to lexical scoping. But, this is a focused and practical example.