Click here to Skip to main content
13,706,637 members
Click here to Skip to main content
Add your own
alternative version

Stats

23.1K views
39 bookmarked
Posted 16 Apr 2015
Licenced CPOL

A Flock of Lambdas

, 21 Aug 2018
Rate this:
Please Sign up or sign in to vote.
What's going on with lambda expressions, anonymous functions, and all that stuff?

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:

<!-- Somewhere on the Web Page -->
<pre id="output"></pre>

Then in the JavaScript before our test we add a simulated console display function.

//******* Begin Test Harness Code *************
function display()
{
    var args = Array.prototype.slice.call(arguments, 0);
    document.getElementById('output').innerHTML += args.join(" ") + "\n";
}
//******* End Test Harness Code *************

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

Initial publication.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Joe Programmer 1
United States United States
No Biography provided

You may also be interested in...

Comments and Discussions

 
QuestionSeems like a(nother) solution to a problem that nobody has Pin
Bitbeisser23-Aug-18 18:52
memberBitbeisser23-Aug-18 18:52 
QuestionLambdas hidden in Algol-60 - Call by Name Pin
23-Aug-18 12:32
member23-Aug-18 12:32 
QuestionHi Joe what about blocks in Smalltalk Pin
mldavis9923-Aug-18 8:50
membermldavis9923-Aug-18 8:50 
QuestionAwwww.... Pin
studleylee23-Aug-18 8:41
memberstudleylee23-Aug-18 8:41 
QuestionExcellent Article Pin
Michael Waters23-Aug-18 7:24
memberMichael Waters23-Aug-18 7:24 
QuestionClosure Pin
Zoltán Zörgő22-Aug-18 7:25
memberZoltán Zörgő22-Aug-18 7:25 
GeneralMy vote of 5 Pin
LightTempler22-Aug-18 4:09
memberLightTempler22-Aug-18 4:09 
GeneralRe: My vote of 5 Pin
Michael Waters23-Aug-18 7:25
memberMichael Waters23-Aug-18 7:25 
GeneralRe: My vote of 5 Pin
LightTempler24-Aug-18 8:47
memberLightTempler24-Aug-18 8:47 
GeneralRe: My vote of 5 Pin
Michael Waters27-Aug-18 6:31
memberMichael Waters27-Aug-18 6:31 
QuestionF# alternative definition for Loop10 Pin
Mark_Shield22-Apr-15 2:21
memberMark_Shield22-Apr-15 2:21 
AnswerRe: F# alternative definition for Loop10 Pin
Joe Programmer 122-Apr-15 13:07
memberJoe Programmer 122-Apr-15 13:07 
QuestionUse Action<int> Pin
Chris at M19-Apr-15 15:24
memberChris at M19-Apr-15 15:24 
AnswerRe: Use Action<int> Pin
Joe Programmer 120-Apr-15 8:35
memberJoe Programmer 120-Apr-15 8:35 
GeneralRe: Use Action<int> Pin
Chris at M22-Apr-15 21:30
memberChris at M22-Apr-15 21:30 
AnswerRe: Use Action<int> Pin
Marc Lewandowski23-Aug-18 9:59
professionalMarc Lewandowski23-Aug-18 9:59 
GeneralNice article Pin
Member 1125615518-Apr-15 2:12
memberMember 1125615518-Apr-15 2:12 
GeneralRe: Nice article Pin
irneb19-Apr-15 20:15
memberirneb19-Apr-15 20:15 
GeneralRe: Nice article Pin
Joe Programmer 120-Apr-15 8:37
memberJoe Programmer 120-Apr-15 8:37 
GeneralRe: Nice article Pin
Joe Programmer 120-Apr-15 9:10
memberJoe Programmer 120-Apr-15 9:10 
GeneralRe: Nice article Pin
Michael Waters23-Aug-18 8:00
memberMichael Waters23-Aug-18 8:00 
GeneralRe: Nice article Pin
Michael Waters23-Aug-18 8:06
memberMichael Waters23-Aug-18 8:06 
QuestionUseful Summary Pin
Bill Gord17-Apr-15 9:26
professionalBill Gord17-Apr-15 9:26 
GeneralA Flock of Lamdas Pin
Member 1055815517-Apr-15 7:55
professionalMember 1055815517-Apr-15 7:55 
QuestionΛ flock. Pin
cigwork16-Apr-15 19:41
membercigwork16-Apr-15 19:41 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web01-2016 | 2.8.180920.1 | Last Updated 22 Aug 2018
Article Copyright 2015 by Joe Programmer 1
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid