|Lambdas and Delegates - Why Bother?|
|Written by Mike James|
|Thursday, 15 December 2016|
Programming style has evolved - and delegates and lambdas bring us round in a circle. Functions should be objects but if you don't like this idea then you have to invent delegates and lambdas.
You must have encountered the idea of either a lambda or a delegate if you have progressed even a little way down the road of becoming a programmer. Put simply, they both provide ways of writing a function that can be used in "clever" ways.
The biggest problem with these constructs is figuring out why they exist at all.
Oh yes, you can see that where they are used they are needed, and that they make things more elegant. But could there be another way of achieving the same results?
This is very much a question of "Where did that come from?"
Stream of consciousness programming
Back in the days when object-oriented programming hadn't been thought of, life was very simple. Programs were written as a long do-this-do-that list.
You can think of this as the "stream of consciousness" era of programming.
You sat down and wrote the program out as one long list. This may have been simple, but things didn't really work. The long list was impossible to understand after only a few minutes not looking at it. And for another programmer to fathom it - well just don't bother.
It also fitted in very nicely with the "goto" school of programmer and we all know how that ended up. By writing a program as a single block of undifferentiated code the temptation, no imperative, was to reuse code by doing a direct jump to it - a goto. Of course the down side of this opportunistic code reuse is that it results in a complex flow of control - so called spaghetti code.
None of this is good and it was a dark era of programming that was waiting for someone to shine some light on the problem.
Functions and modular programming
The solution to the problem was to invent the function (also known as a procedure or subroutine).
You didn't write programs as one long list any more. Instead you broke the program down into sub-tasks and wrote a function to implement each of the subtasks. Then you wrote the full program as a collection of function calls. Of course, if any sub-task turned out to be even slightly complicated, you treated it to the same division process and broke it down into a collection of functions.
This gave programmers a tree-like structure of functions, calling functions, calling functions, and so on. In principle this made the program easier to understand.
This was called modular programming and the most common form of it was "top-down-modular" programming, which suggested that the right way to build the hierarchy was to start at the top, work your way down till you reached the bottom and then write functions as you worked your way back up the implementation tree.
Modular programming first introduced the idea of encapsulation. You couldn't just jump into the middle of a function as suited your old programming habits you had to call a function, let it execute and then wait for it to return. In addition how the function did its work was a matter for the function and not you.
Data that was internal to the function remained internal to the function.
That is functions had local variables that belonged to them and only them. This allowed programmers to realize that there was a better way to implement a function and make changes without worrying about the users of the function. All they should care about is what goes in and what comes out - a huge simplification!
This, plus the idea of block-structured flow control, was about the high point in programming methods until objects came along and changed everything.
The idea of object-oriented programming is to provide a different way of organizing the code and the data.
Instead of code over there and data over here, code and data were bundled together as an object. The functions within an object, its methods, were easier to write because you didn't have to specify the data to be operated on. For example, if you want to write a function that sorts data then you create something like
where data is an array passed into the function to be sorted. With objects everything is different the data and the functions are bound together. You create an Array object and then implement a Sort method. To sort the array you write:
and the Sort method knows to sort the data of the object it belong to. Of course this is just syntactic sugar because you are writing
This is the transformation that many languages use to convert an object method call into a function call. The first advantage of using objects is that functions automatically "know" what data they are to work with.
We all know about objects now, but when they were introduced the idea was hard to swallow.
Instead of using top-down modular design, programs were broken up into objects with properties and methods. The properties represented the data and the methods the functions. Notice, however, that there wasn't, and still isn't, any theory of how you should structure an object apart from the idea of inheritance - which is not thought to be the best idea. This lack of a good theory becomes particularly obvious when you ask the question how do you convert all those function libraries that were created during the era of modular programming into objects?
Goodbye to functions
A bigger change when objects were introduced was the deprecation of functions.
Without really noticing we not only accepted objects but rejected functions. Of course, there were still functions but they were now called methods and tied to the object that owned them. This was, and is, a good way to work but occasionally you can see that it isn't quite enough to capture the way we think about computation.
The problem is that in the object-oriented method you can't have a function that doesn't belong to an object.
Functions can't float around free like they used to in the modular programming days.
Why is this a problem?
Well the answer is that there are lots of times that you naturally think of a function as nothing more than a function. For example, consider the sin function or any similar mathematical function. Most object-oriented languages let you just write something like:
as part of an expression.
But wait - what is a function doing roaming around on its own without an object to look after it?
The problem is that when it comes to this sort of utility function the object notation doesn't look as natural any more. What you should do is to realize that sin is a function that operates on a number object and so it really should be written like;
i.e. call the sin method on the numeric object 0.5. This is too much for many language designers and certainly too much for most programmers.
Objects with methods
An alternative solution is to introduce a Math object with methods like sin so you then have to write:
Yes, it might well be logical but it looks ridiculous and it doesn't do anything to capture the idea that we are working with a function that we consider fundamental in some sense. What is more, if you really think that this logic is good and should be enforced, what about the 0.5? It is a bit of data also out on its own without an object. Presumably the expression should be written:
which is now truly silly. Perhaps sin should be a property of the number object after all?
It isn't easy to resolve.
There are other places where the idea of a function seems right all on its own without an object.
In asynchronous programming you often want to define an event handler and so need to define a function outside of an object.
There are lots of situations where you want to specify a function as a parameter to a method call. For example:
to indicate that the sin function should be applied to the data in MyObject. You can try to write this as:
but this misses the fact that you are thinking of sin as an entity in its own right and what do you do about instances? Are you going to restrict function-passing to static objects?
One solution is to avoid having to pass functions as entities in their own right. Java for instance doesn't allow functions to be passed to methods. All method parameters are objects. So in this case if you want to pass a function you have to set up an object which has a method that does the job of the function. Then you don't pass the function you pass the object and the receiving method gets the appropriate method from that object. This is how Java implements event handlers for example. It is workable but it is very wordy in that you have to define a class, then an instance with the correct methods and pass the object. Over time Java has added little tweaks to make the whole business of passing an object when you really want to pass a function less wordy.
Functions as entities
The key to all of this is that we are really trying to do is to consider a function as an entity in its own right.
The obvious way of doing this is to allow a function to be an object.
If you allow functions to be objects you can simply allow or create the sin object which has a default method which is used by the evaluation operator ().
In this case you read
as an evaluation, i.e. a call to a default method of the sin object. Similarly, you can pass functions into methods and set event handlers, just as you can pass objects and make references to objects.
Treating a function as an object with a default method works very well in practice as it allows the beginner to ignore the object nature of the function until they are ready. It also reduces the amount of code you have to write to implement a simple function construct.
Delegates and Lambdas
This is neat, but you can avoid the idea of promoting functions to objects by allowing a function type.
A delegate is a function type within the type system.
Notice it is a type and not an object. To make it into an object you have to create an instance of the type in the usual way, for example:
Notice that the method specified in the constructor is a method that belongs to an object and the instance created simply "wraps" the object method. It is a roundabout way of promoting a method to be a function object.
The method wrapped by a delegate is still bound to an object.
When you want to execute the method inside the delegate you use the evaluation operator syntax:
evaluates the method by calling it with what ever parameters have been specified. In this sense method behaves like a default method of a myfunction object.
So where do lambdas fit it?
Once you have a delegate you really very quickly tire of defining types and methods that are only used after converting to function objects.
A lambda is simply a way of defining a function that doesn't belong to an object. That is, a lambda is a complete return to the old function idea in that it is a free-floating function that doesn't belong to an object. In fact a lambda doesn't even have a name - it's an anonymous function with just some input parameters and a body that specifies what to do with the inputs and what to return as a result.
A lambda is about the closest thing you can get to the essence of a function. You can use a lambda as part of the instantiation of a delegate and so create a function object that doesn't wrap a method that belongs to an object but to a pure function.
In other words, a delegate plus a lambda simply reinvents the idea of a function object.
Occasionally it can even look as if the lambda is a function object in its own right when it is passed as a parameter to a method, but this is just syntactic sugar as a delegate is usually involved to give the lambda some form in terms of signature and return type.
Overall it is so much easier to simply allow functions to be objects.
or email your comment to: firstname.lastname@example.org
|Last Updated ( Friday, 16 December 2016 )|