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. Jeff Cogswell

    Dan McKinley has a good point about inline functions. In C++, the function isn’t declared inline; it’s declared elsewhere *as* inline. The compiler is free to choose whether to really use it as inline. Lambda functions really are declared inline, however.

    Anyway, that’s not really important. My experience is that lambda functions can result in some really cryptic code. (Check out the boost.org implementation of lambdas in C++. They work great but are hard to read.) That’s why I vote for #2 for VB and #1 for C#. They "looK" more VB- and C#-ish.

    Reply
  2. Jeff Cogswell

    By the way, some people have suggested new keywords, such as lamdbda or func. Personally, I’d stay away from that. Why introduce new keywords when you have existing keywords that suffice (i.e. Function)? This is mainly a VB thing, however, as the C# version doesn’t involve so much a keywords as something that looks like an operator.

    Reply
  3. Pingback: Panopticon Central

  4. Pingback: Panopticon Central

  5. Taiwo Ayedun

    Def(x) = x + 1

    Def(x) As x + 1

    "Def" is short for Define; but perhaps the use of "Def" can be optional?

    So we can have:

    Dim ys = xs.Where(Def(x) = x < 5) or Dim ys = xs.Where((x) = x < 5)

    OR

    Dim ys = xs.Where((Def(x) As x < 5) or Dim ys = xs.Where((x) As x < 5)

    Obviously, "=" is faster to type than "As"…

    Normally, a parenthesized expression evaluates to a value in VB and, since you can’t assign to a value, intellisense/compiler should be able to disambiguate as soon as "=" or "As" is parsed after the parenthesized parameter(s) – i.e., in the case where "Def" is allowed to be omitted.

    Note: I am aware that VB already supports "Def <Type>" to define default data types for variables; again the usage here is slightly different because "Def" is followed by parenthesis.



    Taiwo

    Reply
  6. Brett Riester

    You propose:

    Sub Main()

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

    ApplyTransform(a, Function(x) x + 1)

    End Sub

    I’m not knowledgeable with C++ or the quite how lamba expressions are distinguished from anonymous delegates. But, after reading all of the reply posts and thinking about it, here is what I propose:

    Sub ApplyTransform(array() As Integer, DelegateParameter transform(x))

    For i As Integer = 0 To array.Length – 1

    array(i) = transform(array(i))

    Next

    End Sub

    Sub Main()

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

    ApplyTransform(a, Def Fn ?(x) := {x += 1 :: Return x})

    End Sub

    Or, using Named Parameters:

    Sub Main()

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

    ApplyTransform(a, Def Fn transform(x) := {x += 1 :: Return x})

    End Sub

    I think neither nor => should be used because their too "cryptic" and "non-VB-like". The is used for integral division and => shouldn’t be used because it looks too much like equal-or-less-than operator.

    My new DelegateParmater keyword is sort of like the ParameterArray keyword, in that it tells you that what is to follow it; an (anonymous) delegate. With a little more work on the part of the compiler/IDE this could probably be determined at compile-time/design-time, but this way at least it’s more readable to the developer.

    Also I admit that Def Fn shouldn’t be necessary, but it is more readable, and harkens back to the old days of VB.

    Finally, I’m not deadset on the :: seperator. Perhaps it could be ; instead. But ; would probably upset the C# camp.

    Reply
  7. Brett Riester

    You propose:

    Sub Main()

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

    ApplyTransform(a, Function(x) x + 1)

    End Sub

    I’m not knowledgeable with C++ or the quite how lamba expressions are distinguished from anonymous delegates. But, after reading all of the reply posts and thinking about it, here is what I propose:

    Sub ApplyTransform(array() As Integer, DelegateParameter transform(x))

    For i As Integer = 0 To array.Length – 1

    array(i) = transform(array(i))

    Next

    End Sub

    Sub Main()

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

    ApplyTransform(a, Def Fn ?(x) := {x += 1 :: Return x})

    End Sub

    Or, using Named Parameters:

    Sub Main()

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

    ApplyTransform(a, Def Fn transform(x) := {x += 1 :: Return x})

    End Sub

    I think neither nor => should be used because they’re too "cryptic" and "non-VB-like". The is used for integral division and => shouldn’t be used because it looks too much like equals-or-greater-than operator.

    My new DelegateParmater keyword is sort of like the ParameterArray keyword, in that it tells you what is to follow it; an (anonymous) delegate (in this case). With a little more work on the part of the compiler/IDE this could probably be determined at compile-time/design-time, but this way at least it’s more readable to the developer.

    Also I admit that Def Fn shouldn’t be necessary, but it is more readable, and harkens back to the old days of VB.

    Finally, I’m not deadset on the :: seperator. Perhaps it could be ; instead. But ; would probably upset the C# camp.

    Reply
  8. Jafar

    I like 2 the most but would still want to reduce the Function keyword to "Fun." That is unless you plan to make VB’s intellisense aggressive enough to show up as soon as you start typing a token (as it does for C#).

    Reply
  9. LL

    I would much rather see the syntax include the actual usage for documentation purposes:

    a = xs.Where(Lambda x: x < y)

    or for multiple arguments

    ma.Map2(Lambda x,y: sqr(x) – y < 15)

    Reply
  10. Pingback: Matthew Doig's Softblog

  11. caprio

    Readability and re-usability of code is sweet, if that takes a little bit more coding, soi. Lambda expressions are great if you are coding uh .. lambda expressions I guess.

    Reply
  12. Pingback: ??????VB4???????????VBA??????(??

  13. Pingback: Another Code Site

  14. Pingback: Another Code Site

  15. Mark Ward

    If you want cryptic (fast/cute) syntax – program in C#. If you want Easy to Read/Understand, don’t try to coerse VB into C syntax.

    What, prey tell, is wrong with something like:

    ApplyTransform(a, Lambda Function x() = x += 1)
    or
    ApplyTransform(a, Lambda Sub x() = x.SomeMethod)

    Then, for multi-line Lambda expressions, the synxax could be:

    ApplyTransform(a, _
    Lambda
    Function x() = x += 1
    Return x
    End Lambda)

    This, it seems to me, provides Intellisense with specific (immediate) information that a Lambda Expression is to follow. It also acts symilarly to an IF statement (If <expression> then <Statement>) – on one line, or (If <expression> then (line break) <statements> (line break) EndIf). I’d even go along with "EndLambda", if that makes the parser happier (of course that adds TWO reserved words to the language, as opposed to just "Labmda").

    Reply

Leave a Reply

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