Introduction
I've been seeing the subject of lambdas more and more lately on the Internet, especially when I'm looking for information about C# and/or LINQ. "Lambda" abstractions refer to unnamed (anonymous) functions that may accept specified arguments and may return a specified value, but are not formally declared in the source code. These can be handy in situations where you have a function that needs to receive another function as an argument, but don't need to reuse the other function. For instance, suppose you write a "Do X Ten Times" function where X can be any other function; you have encapsulated the "Do Ten Times" part so you don't have to recreate it for every X. You can create a named function for each X you want to do and pass them as arguments when needed, but suppose a particular X is very trivial and not meant to be called from elsewhere in the program: in this case embedding a small function right there without declaring it can be useful.
In recent years, the capability to use anonymous functions has been added to popular programming languages including the .NET languages. As a result, reading articles about it can give the impression that it is something shiny and new to computing in general; this struck me as ironic since the last time I had heard of them was so long ago I could barely even remember. Nevertheless, they are back, and the best chance of understanding what is going on with them is to dust off some of that old information and compare it to the new implementations. I'm going to treat this as a research investigation.
Blast from the Past
Some quick searches reveal that lambda expressions and anonymous functions are rooted in a "lambda calculus" invented by Alonzo Church in 1936, and have been a feature of programming languages as old as Lisp in 1958. I took a class one time that included programming in Lisp, and that's where I heard of lambdas before. Let's dig up Lisp and build a small example. I used GNU CLISP which is an implementation of Common Lisp.
Simple Sample
Right now, I need the simplest possible sample problem to use as an example that can be recreated on various platforms. So, let's say we want to take any single-integer operation and loop it from 0 through 9. Then we can try out the loop with a function that displays double the input, and again with one that displays the square of the input. Add more if you like. Note that the inner functions will each produce a side effect but not return a value; this is just to make it easy to see that the functions are being executed. When testing our functions, we should see a display of:
0
2
4
6
8
10
12
14
16
18
for the first case and:
0
1
4
9
16
25
36
49
64
81
for the second case.
In Lisp
As a Variable
So, how do we do this in Lisp? Let's take a variable n
and print its double with (print (+ n n))
(Note: This may look odd to non-Lisp programmers because the +
is a function name, as Lisp does not support infix operators, and function names go inside the parentheses along with each argument, separated by spaces). To pass a value to the parameter n
, we need to turn the expression into an anonymous function using the lambda
macro, resulting in (lambda (n) (print (+ n n)))
. Finally, we can assign this function to a variable fn
so we can use it as input to something else.
(setq fn (lambda (n) (print (+ n n))))
Now all we have to do is call the function referred by fn
from within a 0-9 loop:
(dotimes (i 9) (funcall fn i))
This will print the first sample result (followed by NIL since there is nothing to consume the final return value). To get the second result, just change the function to compute (* n n)
.
As an Argument
Another way to do this is to define the loop as a function (which we will call Loop10
) and pass in an anonymous function for each test case.
(defun Loop10 (fn)
(loop for i from 0 to 9 do
(funcall fn i)
)
)
(Loop10 (lambda (n) (print (+ n n))))
(Loop10 (lambda (n) (print (* n n))))
The important thing is that a lambda simply provides a way to pass arguments to a function without naming that function.
In C#
As a Variable
Now, to do this in C#. The following is the same as the first Lisp example where we assign a function to a variable, loop it, then reassign a different function to the same variable and loop it again.
delegate void lx(int x);
static void Main(String[] args)
{
lx fn = n => { Console.WriteLine(n + n); };
for (int i = 0; i < 10; i++) { fn(i); }
fn = n => { Console.WriteLine(n * n); };
for (int i = 0; i < 10; i++) { fn(i); }
Console.ReadLine();
}
It's a bit more verbose because C# assigns functions to delegates instead of untyped variables. (Note: The Console.ReadLine();
is just to make the program wait for us to read the output.)
The confusing part is the =>
symbol which indicates the anonymous function or lambda. It is considered "syntactic sugar" and supposedly makes the code easier to read, though I'm not sure I agree, considering that other syntactic sugar such as the C++ stream operators did not carry over into C#. On the positive side, C# lets you call the function by its variable name instead of resolving it with another function.
To untangle this expression, we have to read the =>
as if it were an infix operator with a code block acting as the right hand expression, and the argument or arguments as the left hand expression. The best strategy is just to remember that n =>
means lambda (n)
. Also note that the return type of the anonymous function is determined by the compiler based on the entire expression; in this case the expression does not return a value, so the return type is void
.
As an Argument
Now we can recreate the Loop10
function and pass lambdas to it as in the second Lisp example.
delegate void lx(int x);
static void Loop10(lx fn)
{
for (int i = 0; i < 10; i++)
{
fn(i);
}
}
static void Main(String[] args)
{
Loop10(n => { Console.WriteLine(n + n); });
Loop10(n => { Console.WriteLine(n * n); });
Console.ReadLine();
}
To save the added overhead of defining a delegate for the integer function, "Chris at M" recommended using the predefined delegate "Action<int>
" as shown below so that it is not unnecessarily verbose.
static void Loop10(Action<int> fn)
{
for (int i = 0; i < 10; i++)
{
fn(i);
}
}
static void Main(String[] args)
{
Loop10(n => { Console.WriteLine(n + n); });
Loop10(n => { Console.WriteLine(n * n); });
Console.ReadLine();
}
In JavaScript
JavaScript programs use anonymous functions all the time, especially when assigning scripts to DOM elements from outside. I've used them plenty of times in calls to jQuery functions. We need to simulate a JavaScript console to try out our example problem.
I adapted some code to use for a console from an example I found posted elsewhere. It works when I test it on jsfiddle. First, we need somewhere to place the output:
<!--
<pre id="output"></pre>
Then in the JavaScript before our test, we add a simulated console display function.
function display()
{
var args = Array.prototype.slice.call(arguments, 0);
document.getElementById('output').innerHTML += args.join(" ") + "\n";
}
As a Variable
Now that that's over with, we can try out our test cases using function variables.
var fn = (function(n){display(n + n);});
for (var i = 0; i < 10; i++) {fn(i);}
fn = (function(n){display(n * n);});
for (var i = 0; i < 10; i++) {fn(i);}
It looks like a regular function declaration except that the name is gone. It almost looks like it is a function named "function
". This syntax does get the point across and it isn't much different from a named declaration.
As an Argument
Again, we can recreate the Loop10
function and pass anonymous functions to it.
function Loop10(fn)
{
for (var i = 0; i < 10; i++)
{
fn(i);
}
}
Loop10(function(n){display(n + n);});
Loop10(function(n){display(n * n);});
In VB.NET
As a Variable
For completeness, we should try it in VB.NET. VB doesn't bother with syntactic sugar, so you may see more of a resemblance to the JavaScript example.
Private Delegate Sub Lx(n As Integer)
Sub Main()
Dim fn As Lx = Sub(n As Integer) Console.WriteLine(n + n)
For i As Integer = 0 To 9
fn(i)
Next
fn = Sub(n As Integer) Console.WriteLine(n * n)
For i As Integer = 0 To 9
fn(i)
Next
Console.ReadLine()
End Sub
Note, by the way, that our example function creates a side effect and does not return a value, so in VB.NET, we end up using the Sub
keyword instead of the Function
keyword in this particular situation.
As an Argument
Now we can recreate the Loop10
function and pass anonymous functions--or subroutines--to it yet again.
Private Delegate Sub Lx(n As Integer)
Private Sub Loop10(fn As Lx)
For i As Integer = 0 To 9
fn(i)
Next
End Sub
Sub Main()
Loop10(Sub(n As Integer) Console.WriteLine(n + n))
Loop10(Sub(n As Integer) Console.WriteLine(n * n))
Console.ReadLine()
End Sub
This syntax is a little more verbose than in other languages, but as with JavaScript, the syntax hasn't changed much from a named declaration.
In Python 3
As a Variable
OK, why stop now? Python is one of the most concise modern languages, so how do lambdas look in Python?
fn = lambda n: print(n + n)
for i in range(0,9):
fn(i)
fn = lambda n: print(n * n)
for i in range(0,9):
fn(i)
It looks like lambda
keyword is back! This translation is comparatively easy to read, so this may have made a good starting point if I wasn't already familiar with lambdas in Lisp.
As an Argument
I'm starting to get used to translating these. Now for the Loop10
function version.
def Loop10(fn):
for i in range(0,9):
fn(i)
Loop10(lambda n: print(n + n))
Loop10(lambda n: print(n * n))
The syntax is short and to the point without being cryptic. That's something I like about Python.
In D
As a Variable
I am a D newbie, but D has a function
keyword we can use.
import std.stdio;
int main(string[] argv)
{
void function(int x) fn;
fn = function void(int n) {writeln(n + n); };
for (int i = 0; i < 10; i++) {fn(i); }
fn = function void(int n) {writeln(n * n); };
for (int i = 0; i < 10; i++) {fn(i); }
readln();
return 0;
}
D has recently added support for the =>
syntax for lambdas, but the compiler I'm using does not choose a void function(int)
signature when using this syntax on an equivalent expression; perhaps it is indirectly referencing the function the way Lisp does. Using an auto
instead of a function
or delegate
to receive the output gets around the datatype problem, but for me the writeln
does not produce output for some reason--or the output is being directed somewhere else. Personally, I prefer the syntax we have here where we can supply an explicit return type, but if I or a reader finds a way to write this using the symbol shorthand, I'll add it to this article.
As an Argument
Here's the loop10
version.
import std.stdio;
void loop10(void function(int x) fn)
{
for (int i = 0; i < 10; i++)
{
fn(i);
}
}
int main(string[] argv)
{
loop10(function void(int n) {writeln(n + n);});
loop10(function void(int n) {writeln(n * n);});
readln();
return 0;
}
In F#
As a Variable
All this time, Lisp is the only true functional language I've used for this investigation. Functional languages are where lambdas are most prevalent, so we should try at least one more. Visual Studio comes with F#, but I have never used it, so I'm going into this cold. Let's see what I end up with.
let mutable fn = (fun n -> printfn "%d" (n + n))
for i=0 to 9 do
fn(i)
fn <- (fun n -> printfn "%d" (n * n))
for i=0 to 9 do
fn(i)
System.Console.ReadLine() |> ignore
This gets a little complicated because, as I've just discovered, F# does not really like variables, so we have to use the mutable
keyword and the <-
operator.
Apparently in F# lambdas are fun
. That word and this thing ->
separate the argument from the code body,
As an Argument
Fortunately, the Loop10
version uses arguments instead of variables, so it's easier to read. Actually, it's just about as concise as the Python example.
let Loop10 fn =
for i=0 to 9 do
fn(i)
Loop10(fun n -> printfn "%d" (n + n))
Loop10(fun n -> printfn "%d" (n * n))
System.Console.ReadLine() |> ignore
Conclusion
Some programming languages use syntax for anonymous functions that appear analogous to their named function declaration syntax, but others diverge from this model quite a bit. In the table below, I show an isolated example comparing each with the keywords that change between named and anonymous in bold.
Language | Named Declaration | Anonymous / Lambda |
Function Syntax Lisp | (defun PrintDouble (n) (print (+ n n)))) | (lambda (n) (print (+ n n))) |
C# | static void PrintDouble(int n) {Console.WriteLine(n + n);} | n => {Console.WriteLine(n + n);} |
JavaScript | function PrintDouble(n){display(n + n);} | function(n){display(n + n);} |
VB.NET | Private Sub PrintDouble(n As Integer)
Console.WriteLine(n + n)
End Sub
| Sub(n As Integer)
Console.WriteLine(n + n)
End Sub
|
Python 3 | def PrintDouble(n): print(n + n) | lambda n: print(n * n) |
D | void printDouble(int n) {writeln(n + n);} | function void(int n) {writeln(n + n);} |
F# | let PrintDouble n =
printfn "%d" (n + n)
| (fun n -> printfn "%d" (n + n)) |
Nothing major changes in the JavaScript and VB.NET versions except that the function name goes away. The Lisp, Python 3 and D versions just replace the function name and declaration keyword with a single keyword. C# and D with C# syntax replace the function name and declaration keywords with a glyph interposed in a new location, and force an implied return type. F# replaces the declaration keyword, name and binding operator with a new keyword and a new glyph. I assert that more differences mean more room for confusion.
Before comparing different platforms in this way, even though I had been introduced to lambda in Lisp, and I had used anonymous functions extensively in JavaScript, the inconsistent syntax in C# made it difficult to see the similarities. Comparing the same test cases in all three languages brought them together and clarified them for me. Further, I was able to extend this clarity to four more languages. I find comparing concepts on differing platforms can be beneficial to understanding (like the Rosetta Stone), and I highly recommend this approach; you can try it on other concepts using any combination of languages you find helpful.
History
- 16th April, 2015: Initial publication