Lambda expressions, Part I: Syntax

As I alluded to in my earlier entry, one of the features we’re working on for LINQ/Orcas is lambda expressions. Now, I might be tempted to say “You know, we should really call them ‘inline functions’ instead of ‘lambda expressions,’ because I think ‘inline function’ is a little clearer and less of a computer science-y term.” but you can rest assured that I learned my lesson. Lambda expressions they are. (Do you detect a trace of bitter sarcasm there? Perhaps just a little.)

For those not well versed in the arcana of the lambda calculus, a lambda expression is, uh, basically an inline function. Take this case, where you’ve got a function that takes another function and applies it to an array:

    Delegate Function TransformDelegate(input As Integer) As Integer

    Sub ApplyTransform(array() As Integer, transform As TransformDelegate)
        For i As Integer = 0 To array.Length - 1
            array(i) = transform(array(i))
        Next
    End Sub

Now, normally you’d have to create a whole function just to be able to use it:

    Function AddOne(x As Integer) As Integer
        Return x + 1
    End Function

    Sub Main()
        Dim a() As Integer = {1, 2, 3}
        ApplyTransform(a, AddressOf AddOne)
    End Sub

But this is kind of silly, right? What you’d really like to do is create an inline function that you could immediately pass to something that took a delegate. Then you could just express right there in the code what you want to do. Something like:

    Sub Main()
        Dim a() As Integer = {1, 2, 3}
        ApplyTransform(a, Function(x) x + 1)
    End Sub

This is all a lambda expression is, really, just an inline function that you can pass to something that takes a delegate type. However, lambda expressions can be quite powerful and form one of the major underpinnings of LINQ. For example, you’ll notice that the LINQ Where function takes a delegate type. So instead of using the higher level From … Where syntax, you could also write things like:

    Dim ys = xs.Where(Function(x) x < 5)

This calls the “Where” function on “xs,” passing in the inline function “x < 5”. The Where function then just applies that inline function to the elements of the collection to determine what to filter in or out of the collection. (You’ll notice I omitted all the types from the inline function; lambda expressions use type inference so that you don’t have to give all the types, but that’s a whole other blog entry.)

One of the major things we’re still finalizing, though, is the exact syntax of lambda expressions in VB (because, of course, the syntax is always the hardest thing to get right). C# is using a “fat arrow, no introducing token” syntax that looks something like this:

    var ys = xs.Where(x => x < 5)

This is certainly one possibility. It has both the advantage of being extremely concise and the disadvantage of being extremely concise. So it’s quick to use but kind of cryptic. Consistency between the languages is nice, but another possibility is the one I used in my initial examples:

    Dim ys = xs.Where(Function(x) x < 5)

This is more wordy and less concise than the “fat arrow, no introducing token” format, but it’s got the advantage that it’s consistent with the rest of the VB language and makes it really obvious what you’re doing (i.e. creating a function). There are some complications to the design having to do with how to distinguish these kinds of “expression-only” lambdas from multi-line lambdas, but we can gloss over those for the moment. (Also note that this is the syntax that some languages such as, I believe, Python use for their lambda expression syntax).

The final option we’ve been kicking around is somewhere in the middle of the two:

    Dim ys = xs.Where(x => x < 5)

The fat arrow has reappeared, but now there’s a backslash that introduces the lambda expression. The backslash is supposed to suggest the lambda character (?), but uses a character that is actually on people’s keyboards. The reason for even considering this hybrid syntax is that having an introducing token means we can give a better Intellisense/IDE experience, since the leading “” token allows us to recognize immediately that you’re writing a lambda expression instead of having to guess until we get to the “=>”.

I’m kind of curious what people think. Given the three options:

  1. C#-style, “x => x + 1”
  2. VB-style, “Function(x) x + 1”
  3. Hybrid-style, “x => x + 1”

I’m a little torn between the clarity of #2 and the conciseness of #3, but I may just be smoking crack on #3. There’s nothing intrinsically wrong with #1, but even after staring at it for a long time, it just doesn’t feel, you know, like VB…

68 thoughts on “Lambda expressions, Part I: Syntax

  1. Rik Hemsley

    As I write quite a lot of Ruby, this looks like key => value to me:

    x => x + 1

    For the same reason, I’d prefer to see:

    var ys = xs.Where { |x| x < 5 }

    Dim ys = xs.Where( |x| x < 5 )

    If I had to choose from your options, though, I’d take 1 for C# and 2 for VB.

    Reply
  2. Pingback: Linq in Action News

  3. Pingback: Fabrice's weblog

  4. Pingback: DotNetKicks.com

  5. Branco Medeiros

    Because the purpose of the the lambda expressions(regarded they use in LINQ) is to allow for short snippets of code to be provided, not full fledged methods, I think that the proposed "Function(Parmeters?) Expression" syntax is overkill and otherwise extremely limited. A more compact declaration would be in order, one that kept the flavor of the language, something none of the other ones provided seem to achieve.

    A possible approach would be to redefine the "(" ")" as you did with generics (not that I *love* that solution, but one gets used to it very quickly). Something in the likes of:

    Dim Ys = List.Where((X: Return X < Y))

    Or, when parameter type was needed to disambiguiate:

    Dim Ys = List.Where((X As Integer: Return X < Y))

    As you can see, the Return keyword would be required, so you wouldn’t have to invent a new way of returning values from the lambdas (as the proposed syntax seems to imply). Notice also that putting the whole thing inside a "specialized" pair of parenthesis would make easy to deal with multiple statements even when using ":" as statement separator (i.e., you wouldn’t have to introduce a new statement separator just for lambdas, such as ";"):

    Dim Ys = List.Where((X: Y+=1: Return X < Y))

    In other words:

    LambdaExpression = "(" ParmeterList? ":" Statement (":" Statement)* ")"

    Reply
  6. Jason

    I also vote for number 2 since VB has become more consistent after graduating to .Net. We already have a C#, let’s keep VB looking like VB.

    BTW, thanks Paul for asking for the community’s feedback.

    Reply
  7. Daniel Grunwald

    x will implicitly be Integer, not Object/Variant, right?

    I vote for # 2, keep VB VB-style. And it’s the easiest to implement in a parser (I’ll have to update SharpDevelop’s code-completion).

    Reply
  8. Dewayne Christensen

    I hate #3. I think my vote would go for #2, as it looks a lot like C#’s anonymous delegates, which I love. I guess now I’m gonna have to go read to see what the difference is between a lambda function and an anonymous delegate. I thought they were the same thing.

    But your overview of the little buggers missed what I consider to be their killer feature–the ability to reference local variables from the defining context, e.g.:

    void f()

    {

    bool expired = false;

    Timer timer = new Timer(5000);

    timer.Elapsed += new ElapsedEventHandler(

    delegate(object sender, ElapsedEventArgs e)

    { expired = true; }

    );

    timer.Start();

    while (!expired)

    Console.WriteLine("yo!");

    }

    I can keep ‘expired’ completely local to the function that uses it, rather than having to define it as an instance or global variable.

    Reply
  9. Daniel Moth

    Reading all the other comments, I now feel very strange as #3 looks like the obvious best choice to me!

    #1 doesn’t help me with intellisense immediatelly and #2 is too verbose.

    Oh well, goes to show that my voice is not representative of the majority and I should be ignored…

    Reply
  10. Gorkem PACACI

    Forgive me, if I’m completely out of context;

    Why we can’t just say:

    Dim a() as Integer = {1,2,3}

    Dim b() as Integer = a.Where(X < 3);

    assuming capital X as a language term, like "value" in c# properties.

    Reply
  11. Dan McKinley

    You should recognize that "inline function" comes with baggage for many people–lambda is most definitely NOT "uh, basically an inline function" if you are someone familiar with inline functions as they appear in C++.

    If you want to be precise in explaining this to the uninitiated, you should say that lambda functions are DECLARED inline.

    Semantics, I guess, but these things can be important. I have personally had to engage in arguments with developers who were telling people not to use "Friend" access–because they thought it was the same thing as C++’s "friend!"

    Anyway, glad to hear you’re going with "lambda."

    I am interested in hearing to what extent the VB version of this will support closures. I don’t see any of your examples taking advantage of that.

    Reply
  12. Cameron Beccario

    I agree, option #2 is best. No new tokens, no strange syntax to learn like "=>" or "", easily readable, and easy for Intellisense to key-off the expression since "Function" occurs within a method body.

    So… supporting lambda expressions means VB will need a ternary operator. 🙂 That would be nice.

    Reply
  13. Aaron

    #2 is good, but why not replace Function with Fun or Lambda or something shorter? And instead of parenthesis, maybe a colon after the arg list?

    Like this:

    Dim ys = xs.Where( Fun x: x < 5)

    I dont know, I am more of a C# guy. #3 is definitly the worst…. but you know, it seems really funny that everything C# does gets put into VB. They are almost the exact same language, I dont know why they keep both around. Perhaps they will diverge in the future, but its been 4+ years now….

    Reply
  14. Daniel Lyons

    Close, but no cigar on the syntax. Actually, that’s taken somewhat from Haskell, where the syntax would be more like this: x -> x < 5

    Python’s lambda looks like this: lambda x: x < 5

    Reply
  15. Joe

    "The backslash is supposed to suggest the lambda character (?),"

    Not sure why you added the question mark above. You don’t need me to explain it to you.

    For anyone else who is interested: The lambda character can be drawn with 2 strokes – one of which could be a backslash – the other being the small "leg" which comes from the middle of the backslash down to the bottom. it is angled a bit to the left.

    #2 is the choice for VB.

    Reply
  16. Scott

    I’d actually prefer style #2 in C#!. But I like the Python version too. Ruby’s inline is nice.

    Honestly, I use the < and > so much I hate having to hit the shift key to use them. I wish they were full fledged keys.

    Reply
  17. Anthony D. Green, MCPD

    "but you know, it seems really funny that everything C# does gets put into VB. They are almost the exact same language, I dont know why they keep both around."

    Oh you must mean the way C# begged to have EnC this version or they way they’re vamping up their intellisense to a more VBish level, or perhaps the way they realized the value of a class of static members and tried to squeeze VB modules into their C# hole? Maybe you’re talking about those property things which were in VB long before C# was thought of? The way that C# can now be used across a broad array of MS products the way VBA did or the way you can make server side webpages with it the way you can with VB Script.

    I guess you’re talking about the way they have to put "var" into their language to signal a variable declaration whereas VB just uses the same Dim it’s been using since Darthmouth BASIC.

    Perhaps the easy to use drag and drop GUI designer experience that I’ve seen in VB consistently. How about all those code snippets C# got eventually. Mixed visibility accessors on properties which VB had pre-.NET.

    Yeah, I can definately see how one-sided the relationship is.

    I recognize that many of these things have come to be par for the course in civilized development but it wasn’t because C# has democratizing the industry and blazing trails. I think interesting things happen on both sides of the fence and the languages try to develop their own personalities while recognizing valuable features. And both of them are learning from all the other kickass languages on the planet such as Haskel and Python and even… Ja…v…. that other language by Sun.

    Reply
  18. Anthony D. Green, MCPD

    The ? is cool and all but I just don’t see people going through the effort everytime they need to filter a list. #2 is defininately the most VBish of the 3 but it is a bit bulky and not very logical if you wanted to represent an expression which does not return a value a la Action(Of T).

    I’m thinking, borrowing from mathematics.

    Dim Ys = Xs.Where(f(x) : x > 6)

    – or –

    Dim Ys = Xs.Where(f(x) := x > 6)

    ‘Reusing the named argument assignment operator.

    I think there is some confusion (and certainly overlap) in these expressions and anonymous methods and more care needs to be taken to clarify their roles or somehow merge them into something cooler.

    At an implementational level how do these expressions differ from CIL method body snippets passed around by reference? Which would basically make them delegates to dynamically generated, disposable, heap allocated methods (which rocks!).

    Reply
  19. Jonathan Allen

    > Dim Ys = Xs.Where(f(x) := x > 6)

    > ‘Reusing the named argument assignment operator.

    I don’t like it because it changes the definition of the := operator.

    Reply
  20. Aaron

    "I recognize that many of these things have come to be par for the course in civilized development but it wasn’t because C# has democratizing the industry and blazing trails."

    OK ok, yeah you are right there, its not one sided. My main point is still valid though: why do we have two languages (three if you count J#) that do the exact same thing with only slightly different syntax ("Dim" instead of "var")? Seems quite silly to me.

    Reply
  21. DiegoV

    Regarding the bitter sacarsm, I though of it at first, but then I looked at your smile and forgot about it 😉

    Of the three, I think #2 is clearly a better fit for VB…

    However, what about:

    ?(x):=x+1

    I think the ":=" has the advantage of being already part of the language and having somewhat related semantics. "?(x)" gives the idea that there is an annonymous function over x, and I think it is still an unambiguous token you can use for intellisense, and well, from here you can start…

    Also, how does it look like when you pass a lambda to a named parameter?

    In the notation #2, I think it would look like:

    apply(foo, bar:=function(x) x+1)

    With the syntax I propose, you could make a shortcut and say:

    apply(foo, bar(x):=x+1)

    That is, you are kind of giving an alias to the function, wich is the name of the parameter instead of keeping it annonymous.

    This gives me ideas… Are you supporting a shortcut syntax for the declaration of the parameter. Something like

    apply(array() As Integer, Delegate transform(input As Integer) As integer)

    Having to declare the delegate outside is really cumbersome.

    There are a few remaining things that bother me, but well, I don’t know how multi-line lambadas look like.

    Sorry for introducing some noise (maybe I am smoking crack too).

    Reply
  22. DiegoV

    I see now that others proposed the same. To Jonathan Allen, precisely, it reuses the named parameter syntax, and by doing it it allows a short but clear syntax when you have to pass the lambada to a named parameter like

    applyTransform(a, transform(x):=x+1)

    Reply
  23. Jonathan Allen

    Actually Diego, I like your version.

    With Anthony’s version, the meaning of := was completely changed. But by using the parameter name as the function name, your version keeps the original semantics of :=.

    One problem I have with it is that you always have to type the parameter name, but I guess intellisense should take care of that.

    On the plus side, it should make it easier to predict the name the compiler will give to the function. That will help when debugging.

    In conclusion, I would like to see Diego’s syntax as an alternate to the Function(x) syntax.

    Reply
  24. DiegoV

    "I think there is some confusion (and certainly overlap) in these expressions and anonymous methods and more care needs to be taken to clarify their roles or somehow merge them into something cooler. "

    Interestingly, I think that cooler thing you refer too is precisely lambdas. Lambdas are like anonymous delegates in steroids.

    Which again leads me to think…

    In C#3, one difference between lambdas and anonymous delegates is that the former get a cleaner syntax, by eliminating the keyword “delegate”.

    VB does not support anonymous delegates and is jumping directly to lambdas. Moreover, while delegates were new when introduced in .NET 1.0, as things evolve and more advanced features are incorporated, the keyword "delegate" is becoming kind of passe.

    I think VB has a choice to be a cleaner language: It keeps using “delegate”, and hence use it consistently, or it stops using it for new constructs.

    But I se Paul presents choice is #2, instead of something like “delegate(x) x+1”, then maybe this should be reflected also in the declaration of the currying function. Instead of:

    Delegate Function TransformDelegate(input As Integer) As Integer

    Sub ApplyTransform(array() As Integer, transform As TransformDelegate)

    .

    I think this should look like (forget what I said earlier)

    Sub ApplyTransform(array() As Integer, transform(input As Integer) As Integer)

    .

    Reply
  25. DiegoV

    "I think there is some confusion (and certainly overlap) in these expressions and anonymous methods and more care needs to be taken to clarify their roles or somehow merge them into something cooler. "

    Interestingly, I think that cooler thing you refer too is precisely lambdas. Lambdas are like anonymous delegates in steroids.

    Which again leads me to think…

    In C#3, one difference between lambdas and anonymous delegates is that the former get a cleaner syntax, by eliminating the keyword “delegate”.

    VB does not support anonymous delegates and is jumping directly to lambdas. Moreover, while delegates were new when introduced in .NET 1.0, as things evolve and more advanced features are incorporated, the keyword "delegate" is becoming kind of passe.

    I think VB has a choice to be a cleaner language: It keeps using “delegate”, and hence use it consistently, or it stops using it for new constructs.

    But I se Paul presents choice is #2, instead of something like “delegate(x) x+1”, then maybe this should be reflected also in the declaration of the currying function. Instead of:

    Delegate Function TransformDelegate(input As Integer) As Integer

    Sub ApplyTransform(array() As Integer, transform As TransformDelegate)

    .

    I think this should look like (forget what I said earlier)

    Sub ApplyTransform(array() As Integer, transform(input As Integer) As Integer)

    .

    Reply
  26. DiegoV

    "I think there is some confusion (and certainly overlap) in these expressions and anonymous methods and more care needs to be taken to clarify their roles or somehow merge them into something cooler. "

    Interestingly, I think that cooler thing you refer too is precisely lambdas. Lambdas are like anonymous delegates in steroids.

    Which again leads me to think…

    In C#3, one difference between lambdas and anonymous delegates is that the former get a cleaner syntax, by eliminating the keyword “delegate”.

    VB does not support anonymous delegates and is jumping directly to lambdas. Moreover, while delegates were new when introduced in .NET 1.0, as things evolve and more advanced features are incorporated, the keyword "delegate" is becoming kind of passe.

    I think VB has a choice to be a cleaner language: It keeps using “delegate”, and hence use it consistently, or it stops using it for new constructs.

    But I se Paul presents choice is #2, instead of something like “delegate(x) x+1”, then maybe this should be reflected also in the declaration of the currying function. Instead of:

    Delegate Function TransformDelegate(input As Integer) As Integer

    Sub ApplyTransform(array() As Integer, transform As TransformDelegate)

    .

    I think this should look like (forget what I said earlier)

    Sub ApplyTransform(array() As Integer, transform(input As Integer) As Integer)

    .

    Reply
  27. DiegoV

    "One problem I have with it is that you always have to type the parameter name, but I guess intellisense should take care of that."

    Well, I happen to like named parameters a lot for the same reason you like them, but my idea was that the name was optional as always. You could still do something like:

    applyTransform(a, ?(x):=x+1)

    Then the parameter would be possitional.

    Thanks again.

    Reply
  28. Justin Michel

    I tried to post a comment to this last week, but apparently it was ignored.

    My basic idea is to use the ByRef and ByVal keywords to work with the parameters of the lambda expression. This syntax lets you write the expression directly, without an awkward declaration side such as "f(x):=", "x =>", "x >=", or "Function(x)".

    It easily lets you support ByVal and ByRef parameters and Subs or Functions.

    Dim ys = xs.Where(ByVal(0) < 5)

    ApplyTransform(a, ByVal(0) + 1)

    Reply
  29. Cale Gibbard

    "Function" has too much overhead in terms of characters — there are a large number of potentially useful lambda expressions where the right hand side of the arrow is very short and the use of "Function" to introduce a lambda term seems a bit excessive. Personally, I’d probably steal the syntax directly from Haskell, and go with "x -> x + 1", but if you like the thicker arrow style, that’s not a big deal. If you’re really intent on going with a keyword, I’d abbreviate here and go with Fun in place of Function.

    Using a single backslash seems like too little syntax for people who aren’t used to using lambda expressions in their everyday programming, but assuming that VB programmers will actually use the feature to do functional programming, "Function" will quickly seem like too much typing.

    Reply
  30. DiegoV

    Justin, I must admit that when I first saw your post I though you were kidding 😉 I had to go to your blog to understand what you were really talking about. And I think it is very interesting indeed.

    It is kind of difficult for me to get used to but I guess one needs to make a leap anyway to really grok functional programming.

    I would call your approach "Anonymous Parameters" 😉

    Reply
  31. Jonathan Allen

    ‘"Function" will quickly seem like too much typing. ‘

    I’ve have some issues with that claim.

    First and foremost, we should care far more about ease of reading than ease of writing. Only after addessing the question of which is easier to understand should we question which is easier to write. And I do mean "easier to write", as intellisense can account for a lot.

    Second, is anyone here really so bad at typing that we need to optimize the length of every token?

    Reply
  32. Anthony D. Green, MCPD

    You know, that

    ?(x) := x + 1 syntax is growing on me. It’s not about the typing but what I think when I see that. Visual Basic is about the distance between what is seen and what is thought.

    TypeOf … Is, IsNot Nothing, MustInherit, etc.

    That says to me that I’m defining an unnamed function "?(x)" rather than "f(x)" and I still like the reuse of the := operator. "?(x) : x + 1" I could go with too, it’s a bit cleaner and still mathematical. The ? is an almost unused character currently so it would make a short an unambiguous was to trigger intellisense. Since technically this is used for code snippets now perhaps "?(" would be better.

    Regarding these expressions versus anonymous methods (they aren’t really anonymous delegates), the greatest usage for me for them doesn’t coincide with that I’d use lambdas for. Anonymous methods would be most valuable for brief callback methods and calling Control.Invoke. Sadly Control.Invoke can’t infer a concrete delegate type so they are actually next to useless in this context.

    Reply
  33. Scott Stewart

    Option 2 "Function(x)" seems most like VB. However, I would probably give some further thought to the Lambda keyword?

    As others have stated, I’d also love to see anonymous delegates in the next vb.net? Yes, I know it’s not the same conversation, but just figured while we had a chance for one suggestion why not another.

    While on the subject of tangent suggestions, and this is really a tangent, I’d love to someday see a way to infer private backing fields to properties as a killer vb feature. Similar to what happens behind the scenes with Events today. So, currently you declare an event like "Public Event Foo As EventHanlder’, and behind the scenes a private multicast delegate called FooEvent is created and you have complete programmatic access to FooEvent even though it is never explicitly declared in your vb.code. I’d love to see something similar (maybe driven with an optional attribute) to auto infer the backing field on a property? So, you declare something like <AutoProp> Property Foo As String, and immediately have access to a FooField as string. Like I said.a bit of tangent.

    Reply
  34. DiegoV

    Anthony, thanks again.

    I must say I am still more fond of the ":=" part of the syntax than of the question mark. What first bothered me about the three options that Paul described was the lack of the separation between the "signature" and the body of the function. Anyway, I would like to see how the multi-line lambadas that Paul mentioned look like. Lacking a view of the big picture it is difficult to tell.

    All in all, it is nice that Paul asks us for our input, and all this discussion and speculation is beautiful. However I suspect that this same options where discussed (and discarded) long ago by the VB team, and that all but the finner details are already settled. But, well, you never know 🙂

    Reply
  35. Rolf Bjarne Kvinge

    I’d prefer #2 over #3 (#3 does certainly not look vb-ish to me), though maybe using something else than ‘Function’ (maybe Lambda, or ?, which is far more intuitive to me than , looks like unary division…).

    Reply
  36. davidG

    I’ll add my vote for ?(x):=x+1, but if it came down to a choice between 1, 2 or 3 I’d definitley go for 2 as the others just don’t look right, and 1 doesn’t play as nice with intellisense.

    Reply
  37. Csaba

    I’ll add my vote for

    { ?(x) Return x+1 } = or

    since it can expanded as a silly mathematical exp)

    { ?(x) : if x > 0 then y = sqrt(x) : z= 0 else y=0 :z =X*X : Return Max(z,y) }

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *