dotnet / vblang

The home for design of the Visual Basic .NET programming language and runtime library.
289 stars 66 forks source link

[Proposal] A dot field lambda syntax #563

Open VBAndCs opened 4 years ago

VBAndCs commented 4 years ago

Suppose we have this list: items = new List(Of (Id As Integer, Text As String)()

I suggest using this short lambda syntax: Dim x = items.Select(.Id)

instead of this: Dim x = items.Select(Function(item) item.Id)

More formulas involving other fields are possible: Dim y = items.Select(.Id & ", " & .Text)

Note that can break old codes that uses With blocks, so, we can use one of two solutions:

  1. With block has the precedence over short lambdas.
  2. The compiler refuses such interference and gives syntax error, so that short lambdas can't be used inside With blocks.

For me, I didn't use the With Block for 2 decades and I have no problem of choosing either of the above. But if you don't prefer neither, another way of thinking, is to use another notation instead of ., such as: Dim x = items.Select(:Id) Dim x = items.Select(@Id)

We already have a familiar alternative that is ready to be put in use, that is the dictionary lockup symbol: Dim x = items.Select(!Id)

VB lambda syntax is unreasonably too long, and feel bad every time I use them, unlike C#'s: var x = items.Select(item => item.Id); which is short but still confusing! I previously suggest an alternative syntax for both languages: x = items.Select(Fn(item) => item.Id) Which is a compromise between length and readability. II already implemented this ssynatx as a part of my ZML razor syntax. But, I see now that in many situations, we don't even want such whole decoration. We just want to access a field or combine two fields in a direct syntax, so, why can't we shorten lambdas to a dot field result syntax: x = items.Select(.Id) and let the compiler generate the whole fancy decorated syntax? Fore me, x = items.Select(.Id) is more readable and understandable than: Dim x = items.Select(Function(item) item.Id)

See this function, which I am using in one of my projects, and forced me to think of a solution:

    Public Sub SetItemsSorce(items As List(Of (Id As Integer, Text As String)))
        SetItemsSorce(
                items.Select(Function(item) item.Id).ToList,
                New ObservableCollection(Of String)(items.Select(Function(item) item.Text))
            )
    End Sub

The part New ObservableCollection(Of String)(items.Select(Function(item) item.Text)) is not as a vb-style code as should. I wish use the short lambdas like this: New ObservableCollection (Of String)(items.Select(.Text) And the whole sub becomes:

    Public Sub SetItemsSorce(items As List(Of (Id As Integer, Text As String)))
        SetItemsSorce(
                items.Select(.Id).ToList,
                New ObservableCollection(Of String)(items.Select(.Text))
            )
    End Sub

Another thing I asked for, is to allow aliasing tuples and the generic syntax:

    Imports myList = List(Of (Id As Integer, Text As String))
    Public Sub SetItemsSorce(items As myList)
        SetItemsSorce(
                items.Select(.Id).ToList,
                New ObservableCollection(Of String)(items.Select(.Text))
            )
    End Sub

For me, this is how a true VB code should appear.

Echo-8-ERA commented 4 years ago

I don't like this. Like the majority of your proposals, this is just needlessly increasing the complexity of the language just to save a few keystrokes. This is especially damning when you consider that verbosity and clarity is VB's primary strength.

pricerc commented 4 years ago

or use the 'language integrated' query syntax.

Dim x = From i In items Select i.id
Dim y = From i In items Select i.id & ", " & i.Text

which is less typing than current lambda syntax, and clearer.

Having said that, I would have some sympathy with and implied 'with':

Dim x = From i In items Select .id
Dim y = From i In items Select .id & ", " & .Text

provided the members are unambiguous, otherwise they would need to be qualified.

I haven't been a fan of With blocks since they prevented me from using edit-and-continue debugging (which probably dates me). And I don't see this as a necessarily 'breaking' change - if the . properties are within an existing With block, then the With block 'wins'; otherwise the introduction of LINQ with From could trigger an 'implied' With for the duration of that statement.

However, I'm not sure how well it would work with lambda syntax - I think it would get very cluttered very quickly and I'm not sure it would suit My ideas about what VB should look like :smile:

As for:

Imports myList = List(Of (Id As Integer, Text As String))

you can achieve that almost exactly with

Class MyList : Inherits List(Of (Id As Integer, Text As String)) : End Class

I couldn't quite figure out what your SetItemsSorce methods were trying to do - they don't compile as-is, it looks like there's an overload missing (one that takes an observable collection as a parameter). But I think

Dim texts = New ObservableCollection(Of String)(From i In items Select i.Text)

is quite easy to read. Or if you have the appropriate extension method in a library somewhere:

Dim texts = (From i In items Select i.Text).ToObservableCollection()
VBAndCs commented 4 years ago

this is just needlessly increasing the complexity of the language just to save a few keystrokes

All these proposals aim to make the language more readable and simple. Give the 2 syntax forms to beginners and take a survey. You think that current lambda syntax is easy and more readable? Strange! The language as is, is too complex for beginners, lost many of them over last decade, and is now frozen to death. Thinking otherwise will send it to oblivion for good.

LinQ which is less typing than current lambda syntax

LinQ is a bit easier but is never less typing than lambdas syntax! Every syntax has its advantages: LinQ is more readable in complex queries. Lambdas with more than one param can be better in some situations, especially when has a full body and complex statements. The dot syntax covers the common ground where we deal with one record (one param) and only need to get a one filed or a calculated value on it. This is a dominant case in Razor syntax, and a daily case in normal code, where writing LinQ inside with blocks will only make it worst!

VBAndCs commented 4 years ago

Note: I avoid .EF core fluent API as long as I can send the field name as a string. It is insanely influent API (even in C#) and naming it fluent with this lambda syntax is a funny joke. The dot syntax is better than sending the string name (with no intellisense and possible typos), and still one char less ((.Name) vrs("Name")` I so enough of single-line single-param single-field-result lambdas, and don't want to use this syntax anymore.

Echo-8-ERA commented 4 years ago

this is just needlessly increasing the complexity of the language just to save a few keystrokes

All these proposals aim to make the language more readable and simple. Give the 2 syntax forms to beginners and take a survey. You think that current lambda syntax is easy and more readable? Strange! The language as is, is too complex for beginners, lost many of them over last decade, and is now frozen to death. Thinking otherwise will send it to oblivion for good.

In what world is

    Public Sub SetItemsSorce(items As List(Of (Id As Integer, Text As String)))
        SetItemsSorce(
                items.Select(.Id).ToList,
                New ObservableCollection(Of String)(items.Select(.Text))
            )
    End Sub

obvious in its intent? The only indication that .Id is a lambda and not a field/property access, is that your method lacks a With statement. The only way it will ever become obvious if one is intimately familiar with the syntax in question. That is not a sign of a beginner friendly language.

There's also the fact that your proposal only covers a single use case for inline functions. If you ever need to do anything else? Whoops, you're back to using the current syntax. Or are you going to make a new proposal for each and every time you need an inline method that your previous ones don't cover?

On the other hand, while the current inline method syntax is clunky, at least it is clear you are writing one and that it works for any conceivable purpose.

VBAndCs commented 4 years ago

The only indication that .Id is a lambda and not a field/property access, is that your method lacks a With statement.

Read the whole proposal. This is not the only suggested syntax, so, go with what you see suitable.

Besides, nowadays app code is impossible to understand outside the editor, because the API is independent on the lang, and each API has its purpose and usage. The intent is fully clear from the Select signature that expects a lambda, so, if you send even there is a with block, you will get a compiler error, unless the .ID returns a delegate! Also, ypu should read my prev reply cerfully, as lambdas not only represent delegates but also expression trees! This is heavly used in Razor and EF, and need simplification if we ever wish to take VB there.

your proposal only covers a single use case for inline functions

And this is more than enough, as it covers a wide range of usage. I have no issue with writing full lambda functions, as it looks like a normal function but defined in place without a name. It can't be more clearer. Also, there is no way to avoid lambdas that have more than one param. The only thing I want is to shorten Function to Fn, and use any sympol to distinguish the return value, and I vote for =>. And yes, I want less keystrokes to save a year or tow of my life and a total of a million years or tow of VB programmers lives, along with saving the planet a lot of pollution and waste of resources, both can kill thousands of people due to just few unnecessary keystrokes! A few keystrokes is understandable as a Microsoft team argument to work less and spend less. But is should not be a programmer's argument by all means!

VBAndCs commented 4 years ago

a practical example:

Lambda-based LinQ:

accounts.UnionWith(TransAcs.
   Select(Function(a) a.ToAccount).
   Where(Function(a) a.ToAccount.Number > 0)
)

LinQ:

accounts.UnionWith(
   From a In TransAcs
   Where a.ToAccount.Number > 0
   Select a.ToAccount
)

My proposed syntax:


accounts.UnionWith(TransAcs.
   Select(.ToAccount).
   Where(.ToAccount.Number > 0)
)
pricerc commented 4 years ago

LinQ which is less typing than current lambda syntax

LinQ is a bit easier but is never less typing than lambdas syntax!

Really? I literally gave an example in my comment. An example from your original post, and the LINQ-syntax version. Explain to me how the lambda syntax is less typing?

Dim x = items.Select(Function(item) item.Id)
Dim x = From i In items Select i.id

My proposed syntax:

accounts.UnionWith(TransAcs.
   Select(.ToAccount).
   Where(.ToAccount.Number > 0)
)

So this might work for a LINQ query where there is only one parameter. But lambdas can be used in other scenarios where there is more than one parameter, in which case it won't work, lambdas must work the same wherever they are used.

And what happens if someone puts that inside a With block?

AdamSpeight2008 commented 4 years ago

How would this work alongside with block structures?

VBAndCs commented 4 years ago

LinQ functions expect lambdas, so, no .Member that belongs to With will be valid in this context, except as an assigned value:

With Obj
    Q.Where(.Name = .Value)
End With

Which can mean: Q.Where(Function(x) x.Name = Obj.Value but if the x has a value, then it takes precedence (as happens now when two with interferes: Q.Where(Function(x) x.Name = x.Value Currently, this will not break old code, sense no current code can pass just a value to a lambda. In new code, we should follow guidelines not to make such interference. In fact, I am thinking of another suggestion for a while. I saw a Self keyword in a programming language called Ring, that refers to the object affecting the inner block, and I think it will be useful in VB in many places, such as here:

With Obj
    Q.Where(.Name = Self.Value)
End With

And we discussed before declaring an alias for the with block. I can add another idea:

With Obj
    Q.Where(.Name = With.Value)
End With

but practically, self, the with alias, and with. are not that important, as with block is rarely used today. The only critical with is that of anonymous types, and I don't see it possible practically to use linQ inside them, so, we can't have this confliction here.

VBAndCs commented 4 years ago

This another sample I ran into. Here I had to us both Linq and lambdas, as LinQ doesn't have a union keyword:

Dim TransAcs = db.Transfers.Where(
    Function(trans) trans.LastEdit = onDate OrElse
                    trans.Date = onDate)

Return opAcs.Union(feeAcs).
       Union(TransAcs.Select(Function(trans) trans.FromAccount)).
       Union(TransAcs.Select(Function(trans) trans.ToAccount)).
       OrderBy(Function(ac) ac.ID).
       Skip(SkipCount).Take(PageSize)

If I wrote it with a dot lambdas:

Dim TransAcs = db.Transfers.Where(
    .LastEdit = onDate OrElse trans.Date = onDate)

Return opAcs.Union(feeAcs).
       Union(TransAcs.Select(.FromAccount)).
       Union(TransAcs.Select(.ToAccount)).
       OrderBy(.ID).
       Skip(SkipCount).Take(PageSize)
VBAndCs commented 4 years ago

Furthermore, we can compact Union(TransAcs.Select(.FromAccount)) to just Union(TransAcs.FromAccount) as Select is a default method!

Dim TransAcs = db.Transfers.Where(
    .LastEdit = onDate OrElse trans.Date = onDate)

Return opAcs.Union(feeAcs).
       Union(TransAcs.FromAccount).
       Union(TransAcs.ToAccount).
       OrderBy(.ID).
       Skip(SkipCount).Take(PageSize)