Lambda expressions: Dangerous Madness

R

raylopez99

Took a look at all the fuss about "lambda expressions" from Jon
Skeet's excellent book "C# in Depth". Jon has an example, reproduced
below (excerpt) on lambda expressions.

My n00b take: it's like SQL and those 'high level' languages that try
to be too clever by half and pack a lot of syntax in as short a space
as possible. Like Egyptian hieroglyphics, like Chinese, like those
non-alphabet languages, it's just way too much information in too
short a space. Sure, writing a program with this syntax will reduce
your source code from 100 pages to 10, but at a cost of readability.

But what do I know? I only have a couple months real experience in
the language. Don't listen to me.

RL

"To try and outstrip each other in the arms race, or to expect to win
a nuclear war, is dangerous madness." - Leonid Brezhnev

this is equivalent: from C#1.0
ArrayList products = Product.GetSampleProducts(); //fills arraylist
(fanciful example)

foreach (Product X in products) {
if (X.price > 10m) { Console.WriteLine(X);} } [suffix m =
decimal]

now redo in C#2 mode:

ArrayList products = Product.GetSampleProducts(); //fills arraylist

Predicate <Product> products test = delegate (Product p) {return
p.Price > 10m;} ;
List<Product> matches = products.FindAll(test);

Action<Product> print = delegate(Product p) {Console.WriteLine(p);};
matches.ForEach (print);

which is actually equivalent to (lambda expressions):
ArrayList products = Product.GetSampleProducts(); //fills, as before

foreach (Product product in products.Where (p =>p.Price > 10))
{Console.WriteLine(product);}

//Jon humorously states "the combination of the lambda expression
putting the test in just the right place and a well-named method means
we can almost read the code out loud and understand it without even
thinking" HA HA HA! Right. I guess beauty is in the eye of the
beholder.

RL
 
J

Jon Skeet [C# MVP]

raylopez99 said:
Took a look at all the fuss about "lambda expressions" from Jon
Skeet's excellent book "C# in Depth". Jon has an example, reproduced
below (excerpt) on lambda expressions.

My n00b take: it's like SQL and those 'high level' languages that try
to be too clever by half and pack a lot of syntax in as short a space
as possible. Like Egyptian hieroglyphics, like Chinese, like those
non-alphabet languages, it's just way too much information in too
short a space. Sure, writing a program with this syntax will reduce
your source code from 100 pages to 10, but at a cost of readability.

I'd say they greatly add to readability, actually. Yes, you need to get
used to them first, but that's no reason to avoid them.

I think there's actually a shock value in how much you're able to
express in so little code - when you're used to verbose code, it can be
alarming to see how compact it can really be when you have suitable
language features. That's a perception issue though - not a
maintainability issue.
But what do I know? I only have a couple months real experience in
the language. Don't listen to me.

Many powerful features look too hard to start with. Give them a chance
- when you're familiar with them, lambda expressions are *wonderful*.
What would you rather write:

products.OrderBy(product => product.Price)

or

products.OrderBy(PriceComparison)

.... later on in code (second version only) ...

int PriceComparison(Product first, Product second)
{
return first.Price.CompareTo(second.Price);
}

? I know which one *I* find clearer and easier to modify when
necessary.
//Jon humorously states "the combination of the lambda expression
putting the test in just the right place and a well-named method means
we can almost read the code out loud and understand it without even
thinking" HA HA HA! Right. I guess beauty is in the eye of the
beholder.

I wasn't kidding. Read the code out loud - it does exactly what it
says. That's great for readability.
 
P

Peter Morris

products.OrderBy(product => product.Price)

or

result.Rows.Sort(
delegate(StockLevelRow row1, StockLevelRow row2)
{
return row1.BrandName.CompareTo(row2.BrandName);
}
);


No thanks, I'll take the lambda :)
 
M

Marc Gravell

For code maintenance, less is usually better... I completely agree with
Jon that it makes the code much more transparent.

As an example, for a side-project I'm working on some code that does a
reasonable amount of work with delegates (and similar functionality) -
but I also want the code to compile on C# 2. It is so, so hard going
bacck to delegates; all that "delegate {ArgType x, ...}" etc truly
interrupts my thought process. I'm not thinking "delegate ..." - I'm
thinking about the interesting bit to the right. I've chosen to write
the delegates using lambdas, and then when I have them working,
back-port them to anon-methods (for C# 2compatibility) - this takes me a
lot less time than working any other way.

I've also done a lot of work with "expressions"; now, expressions are
horribly complex beasts. If you've had to write them the long-hand way,
you will understand both *why* they are so compilicated, but also how
painful it is to do. Getting the compiler to do it for you from a very
terse and easy to follow line is just breathtaking, and I am truly
amazed at how well it works.

Marc
 
C

Christopher Ireland

Peter said:
No thanks, I'll take the lambda :)

Makes you wonder why on earth you'd want to implement anonymous delegates in
a compiler without also implementing lambda expressions at the same time. I
understand that's the way one language has gone recently .. what was it
called .. del, delp ... nah, I can't remember <g>.

--
Thank you,

Christopher Ireland

"If a man who cannot count finds a four-leaf clover, is he lucky?"
Stanislaw J. Lec
 
R

raylopez99

or

   result.Rows.Sort(
     delegate(StockLevelRow row1, StockLevelRow row2)
     {
      return row1.BrandName.CompareTo(row2.BrandName);
     }
    );

No thanks, I'll take the lambda :)

To each their own. I like the longer delegate version better. The
"CompareTo" is a hoary feature that goes back to the days of Pascal (I
recall seeing it in a programming book).

And, as Jon points out in his book (I may be mistaken about this
however, but I saw it briefly and I think I'm right), the second
'verbose' version _actually_ does a sort, where as the first "lambda"
version is a sort of "virtual sort" (no pun intended) where the actual
data structure is not sorted after calling the lambda version. Now
that may be exactly what you want, but, it may not be. So ultimately
you might end up doing the verbose sort anyway.

RL
 
P

Pavel Minaev

And, as Jon points out in his book (I may be mistaken about this
however, but I saw it briefly and I think I'm right), the second
'verbose' version _actually_ does a sort, where as the first "lambda"
version is a sort of "virtual sort" (no pun intended) where the actual
data structure is not sorted after calling the lambda version.  Now
that may be exactly what you want, but, it may not be.  So ultimately
you might end up doing the verbose sort anyway.

As written, this is correct, but List<T>.Sort() is eager, and can be
called with a lambda as well. And, of course, one can also explicitly
work around LINQ deferred execution by using ToList() on the resulting
IEnumerable.

Personally, I find that I use lambdas whenever I'm dealing with pure
single-expression functions, and anonymous delegates when my functions
have side-effects (and the main purpose of them is those side-effects,
not the value returned - typically, those are event handlers, or
signalling callbacks). So I do this:

list.Sort((x, y) => x - y);

But I also do this:

button.Click +=
delegate(object sender, EventArgs e)
{
...
}

stream.BeginRead(
buffer,
offset,
count,
delegate(IAsyncResult ar)
{
...
},
null);

So, functional style -> lambdas & type inference, imperative style ->
anonymous delegates & explicit types. I can't clearly formulate my
reasoning for doing so, but it boils down to readability - I find
expression lambdas with side-effects, especially when they return void
(such as x => Console.WriteLine(x)) concealing the side-effect-unfree
nature of the code, and I don't like the syntax of statement lambdas
at all.
 
J

Jon Skeet [C# MVP]

raylopez99 said:
To each their own. I like the longer delegate version better. The
"CompareTo" is a hoary feature that goes back to the days of Pascal (I
recall seeing it in a programming book).

And, as Jon points out in his book (I may be mistaken about this
however, but I saw it briefly and I think I'm right), the second
'verbose' version _actually_ does a sort, where as the first "lambda"
version is a sort of "virtual sort" (no pun intended) where the actual
data structure is not sorted after calling the lambda version. Now
that may be exactly what you want, but, it may not be. So ultimately
you might end up doing the verbose sort anyway.

Well, there are alternatives there anyway. For instance, I have a class
in MiscUtil which builds a Comparison<T> from a Func<T,TKey> - which
means you can still use a concise syntax even when sorting in place.
 
B

Barry Kelly

Christopher said:
Makes you wonder why on earth you'd want to implement anonymous delegates in
a compiler without also implementing lambda expressions at the same time.

Lambda *expressions* in C# are just that, expressions. Lambda
statements, on the other hand, can have limitations in terms of type
inference. You can end up with something very similar to an anonymous
method, in the worst case:

(ParamType1 arg1, ParamType 2 arg2) =>
{
foo();
bar();
baz();
return frobnitz(arg1, arg2);
}

In this case, I don't think one has gained much by moving from the
anonymous delegate syntax to the lambda statement syntax.
I
understand that's the way one language has gone recently .. what was it
called .. del, delp ... nah, I can't remember <g>.

Yes, Delphi. I work on the Delphi compiler, and I've implemented
anonymous methods just recently. We didn't implement lambdas because:

(a) it's too much work for one release - our parser relies heavily on
full type information being available, and needs to be rejigged to be
more compatible with a type-inference approach, and

(b) the direction we want to go in for native code is using code blocks
as arguments for parallelism purposes, more than LINQ.

-- Barry
 
B

Barry Kelly

raylopez99 said:
To each their own. I like the longer delegate version better. The
"CompareTo" is a hoary feature that goes back to the days of Pascal (I
recall seeing it in a programming book).

CompareTo is just a function that can return three values - less-than,
equal-to, greater-than.

The lambda-version projects out the field which is to be sorted on, but
And, as Jon points out in his book (I may be mistaken about this
however, but I saw it briefly and I think I'm right), the second
'verbose' version _actually_ does a sort, where as the first "lambda"
version is a sort of "virtual sort" (no pun intended) where the actual
data structure is not sorted after calling the lambda version.

For LINQ to objects, it doesn't make any difference whether you specify
the argument to Sort as a lambda or an anonymous delegate. LINQ works
the way it does because it has taken advantage of the monadic pattern to
implement delayed computation.

A LINQ sort function that works using CompareTo, rather than a
projection, could easily be written, BTW, and it could also work lazily
rather than eagerly. However it wouldn't map well to databases, which
expect to be able to transform the expression tree provided in Sort()
etc. into an 'order by' clause in SQL, which expects a projection from
the query set.

-- Barry
 
B

Barry Kelly

Pavel said:
signalling callbacks). So I do this:

list.Sort((x, y) => x - y);

Beware of overflow: this is not a good comparison function. E.g. see:

using System;
class App
{
static void Main()
{
int a = int.MaxValue - 3;
int b = int.MinValue + 3; // so a > b, right?
Console.WriteLine("a = {0}, b = {1}, a > b = {2}", a, b, a > b);
Console.WriteLine(a - b); // so this is positive, right?
// a - b = -7 => oops!
}
}
stream.BeginRead(
buffer,
offset,
count,
delegate(IAsyncResult ar)
{
...
},
null);

This manual continuation-passing style cries out for a machine
translation. I've still got this one on my todo list...

-- Barry
 
B

Barry Kelly

Marc said:
I've also done a lot of work with "expressions"; now, expressions are
horribly complex beasts. If you've had to write them the long-hand way,
you will understand both *why* they are so compilicated, but also how
painful it is to do. Getting the compiler to do it for you from a very
terse and easy to follow line is just breathtaking, and I am truly
amazed at how well it works.

I'm not. The compiler has already done the work of the code to tree
transformation for compilation purposes; converting that into an
equivalent object construction expression is trivial.

-- Barry
 
J

Jon Skeet [C# MVP]

This manual continuation-passing style cries out for a machine
translation. I've still got this one on my todo list...

Have a look at what the CCR team did with iterator blocks for CPS.
It's really neat...

Jon
 
P

Pavel Minaev

This manual continuation-passing style cries out for a machine
translation. I've still got this one on my todo list...

What kind of translation do you mean here?
 
C

Carlos

The main problem I have with the main post is that RL is missing the point
of Lambda Expressions. If you want to maintain state while passing a
function pointer you clearly have to look at a functional style of
programming and lambda expressions are a clear sign of a (math) functional
programming language, just like true recursiveness and things like tuples
but I guess that would result in *dangerous madness* as well.

What frightens me is that people seem to have forgotten the roots of
programming with User Interfaces, the structure is simple, you have WM
Messages, we catch them in event handlers and do something with them. Then
you have your code, this code needs to be clean and correct, to test its
correctness you will need to have defined what goes in and what comes out.
Lambda expressions not only help readability, but they force you to use a
single method call *only* where you need it, no more *F12 - Go to definition
I hope I find the implementation in the right place*...Functional elements
in your declarative code are just, well, good practice.
 
R

raylopez99

The main problem I have with the main post is that RL is missing the point
of Lambda Expressions. If you want to maintain state while passing a
function pointer you clearly have to look at a functional style of
programming and lambda expressions are a clear sign of a (math) functional
programming language, just like true recursiveness and things like tuples
but I guess that would result in *dangerous madness* as well.

Well Carlos, bone up on these topics and get back to us with a summary
of what you lerned:

http://en.wikipedia.org/wiki/Call-with-current-continuation

http://en.wikipedia.org/wiki/Continuation ("Continuations are the
functional expression of the GOTO statement, and the same caveats
apply. While many believe that they are a sensible option in some
special cases such as web programming, use of continuations can result
in code that is difficult to follow. In fact, the esoteric programming
language Unlambda includes call-with-current-continuation as one of
its features solely because of its resistance to understanding.")

and last but not least, showing great minds (like Ray Lopez's) think
alike, a blog entry from 2005 on why Lambda expressions in C# suck:
http://blogs.msdn.com/abhinaba/archive/2005/09/17/469568.aspx

But don't let me dissuade you from using Lambda expressions (aka
glorified GOTO statements). As pointed out in the above, it gives
programmers a steeper learning curve, which means people already
familiar with lambda expressions will have a leg up on the competition
that doesn't understand how to read these expressions.

And that makes the programmer more powerful (to his employers). At
the cost of clarity and program efficiency of course, but since when
has that ever been a goal?

RL <1,2,3> <--there's a tuple for you.
 
P

Pavel Minaev

http://en.wikipedia.org/wiki/Call-with-current-continuation

http://en.wikipedia.org/wiki/Continuation ("Continuations are the
functional expression of the GOTO statement, and the same caveats
apply. While many believe that they are a sensible option in some
special cases such as web programming, use of continuations can result
in code that is difficult to follow. In fact, the esoteric programming
language Unlambda includes call-with-current-continuation as one of
its features solely because of its resistance to understanding.")

What does call/cc have to do with lambdas (closures)? They are two
orthogonal things. You can have lambdas in a language without call/cc
(C# is an example), and you can have a language with call/cc but no
closures (I don't know of any examples of such, but it's perfectly
possible).

If anything, the "yield" keyword in C# has more to do with
continuations (though it's still far from call/cc). And, of course,
any program can be rewritten in continuation passing style, so,
techincally, you could say that "return" is just syntactic sugar for
invoking the method's continuation - sounds scary enough to avoid
"return" in your code? If so, I have a booklet on the dangers of
dihydrogen monoxide to give you :)
and last but not least, showing great minds (like Ray Lopez's) think
alike, a blog entry from 2005 on why Lambda expressions in C# suck:http://blogs.msdn.com/abhinaba/archive/2005/09/17/469568.aspx

This phrase from the blog post says it all:

"Functional programming is for the academia and not from the industry
and should be left as such."

Like I said before, it's the same "in my days we used GOTO and we
liked it!" mentality, which is a sure way to become close-minded, and,
eventually, obsolete your skillset. Yes, things change (particularly
so in IT industry), new approaches are constantly being developed, and
you can't keep going on just on what you've learned back in the uni.
Keep an open mind, learn to keep up, or become irrelevant soon.
Complaining about it is rather pointless.
But don't let me dissuade you from using Lambda expressions (aka
glorified GOTO statements).  As pointed out in the above, it gives
programmers a steeper learning curve, which means people already
familiar with lambda expressions will have a leg up on the competition
that doesn't understand how to read these expressions.
And that makes the programmer more powerful (to his employers).  At
the cost of clarity and  program efficiency of course, but since when
has that ever been a goal?

Ah, I see - the good old "overpaid elite" conspiracy theory. Though
it's the first time I see it in a C# context - it's usually aimed at C+
+, and can be seen on comp.std.c++ and comp.lang.c++ every now and
then. And, of course, just as relevant here (which is to say, not at
all).
RL <1,2,3> <--there's a tuple for you.

Weird - my C# compiler says "RL: unknown identifier".
 
J

Jon Skeet [C# MVP]

But don't let me dissuade you from using Lambda expressions (aka
glorified GOTO statements).

<snip>

That statement alone shows that you don't know enough about lambda
expressions (or continuation passing) to judge their merits. Hopefully
when you understand more about them you'll be in a better position to
discuss them sensibly.

Jon
 
B

Barry Kelly

Pavel said:
What kind of translation do you mean here?

The typical reason for using async code is to increase thread
utilization by reducing the number of threads that are blocked on I/O.

Async works by converting a linear code flow into a set of callbacks
from the OS (or any given runtime equivalent). By returning to the OS
rather than blocking on I/O, the OS is free to recycle the thread and
avoid paying multiple times for its expensive assets (various stack
affinities, physical stack allocation, task switching and scheduling
cost, virtual address space allocation, etc.).

However, async code only dictates the implementation; it's perfectly
possible to write code in a linear, seemingly blocking-style, and have a
compiler or runtime convert that code into equivalent async code.

Such a compiler would work by converting the code into
continuation-passing style at async method calls, as well as along all
edges in the call graph that may lead to async method calls.

Continuation-passing style is a program interpretation model whereby:

* Functions never return; rather, the remainder of the calling function
- the bit that would have been returned to - is passed as an argument to
the called function
* When a function wants to "return", it simply calls the continuation
parameter it was passed in
* Implementation wise, then, calling a function is effectively a way of
performing a goto with parameter passing, since the function will never
return
* This also implies that there is no need for a stack. The effect of a
call stack - functions being able to recurse and return freely like you
would expect - is caused by closure. The continuation function that was
passed as a parameter, and that is "returned" to at the end, is a
parameter like any other and thus eligible for symbol capture like any
other symbol referenced in a closure.
* The actual stack links are stored inside the closures; each
continuation will itself contain a continuation that it returns to, etc.
* Turning typical stack-oriented imperative code into semantically
equivalent CPS code is a mechanical transformation.

Here's the key link: the callback argument passed to all Begin- style
asynchronous methods is a continuation, by definition. It's the thing
that the Begin method will call when it's done - i.e. "returned".

It follows that a program that can be automatically converted into CPS
can use an async implementation strategy, but using a blocking-style
API.

A cheap and cheerful way to implement continuations in an interpreted
virtual machine environment is to store stack frames on a
garbage-collected heap, and use a tuple (current-code-array-index,
current-stack-frame) to represent a continuation.

Erlang uses something like this to achieve the scalability it does: all
actors (processes) that seem to have "threads" of their own are actually
implemented in something like the above fashion; all blocking operations
like network calls etc. are asynchronous but look synchronous. It works
because the VM is able to easily and cheaply suspend - i.e. "block" the
actor while it dispatches the operation, performed in an async style, to
a pool of worker threads.

C# 2.0's iterator (yield) construct can be used to implement this kind
of continuation too, though it's uglier. The internal state of the
auto-generated enumerator is the equivalent of
"current-code-array-index"; however, the only current-stack-frame you
get is whatever you pass in to the iterator constructor (i.e. the
function, returning IEnumerable/<T>, that contains the yield
statements).

-- Barry
 
B

Barry Kelly

raylopez99 said:
Well Carlos, bone up on these topics and get back to us with a summary
of what you lerned:

http://en.wikipedia.org/wiki/Call-with-current-continuation

http://en.wikipedia.org/wiki/Continuation
But don't let me dissuade you from using Lambda expressions (aka
glorified GOTO statements).

Continuations are lambda expressions, but not all (or even many) lambda
expressions are continuations. The concepts are distinct. Also,
continuation-passing style is distinct from having call/cc as a language
construct.
As pointed out in the above, it gives
programmers a steeper learning curve,

If a learning curve is a graph of proficiency against time, then a
steeper curve is a *better* thing! :)
At
the cost of clarity and program efficiency of course

Clarity is in the eye of the beholder, and efficiency is not as simple
as you think - features like C#'s lambda expressions, which support an
alternative representation as an expression tree, give a lot more scope
for imaginative optimization.

-- Barry
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top