dotnet / vblang

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

[Proposal] Default value expression in VB #272

Open xieguigang opened 6 years ago

xieguigang commented 6 years ago

There is a default value expression feature in C# language to produce default value for corresponding type. VB language is also have a Default keyword but it is working for the indexer property.

For the optional parameter in a function that its type is non-primitive, we can not declare it as a constant for set a default value. We usualy set its default value to Nothing and then using an If() expression or If ... Then statement to set the default value. But this have some drawbacks,

As @AnthonyDGreen comment that:

It breaks the flow of typing/thinking. It can also create a situation where a lot of ceremony starts accruing on the left like multiple CType or Await keywords very far from what they relate to.

Using the If() expression actually can caused such problem: break our thinking while we programming. The Fluent design language feature like Extension method can reduce or eliminate the backtracking. There are proposals that in a common theme about the Fluent design in VB language:

Proposal in this issue using default value expression for solving:

One of the VisualBasic language feature that people can distinguish VisualBasic with other programming language is that VB language is English words based (C family language like C# is symbols based). As one of the philosophy of VB language design is to design a programming more close to natural language for those beginers in programming. So proposal this new VB default value expression in syntax like: Or ... As Default If ....

The default value expression is a very common scenario, example as this code example list showns:

And more example is not listed.

Proposal Or ... As Default

Syntax

' Left: ValueExpression can be variable or function calls expression
' Right: The default value that will be used if the left is assert failure
' Optional If assert: assert left is equals to C# ``default(type)`` or user custom assert expression
ValueExpression Or default_value [As Default] [If assert = True]

Improvement

Drawbacks

People may be confused this feature with the Or logical operator, but this can be eliminated by using () bracket pair.

Default assert

The If expression can be ommited if using default(type) mechanism or using user custom expression.

Using default(type) assert

The If expression assert can be ommited in this Or ... As Default expression, If the assert is a C# default(type) assertion:

' Will changes
With If(dev Is Nothing, Console.Out, dev)
' to more natural language style
With dev Or Console.Out As Default ' [If dev Is Nothing] assert can be ommit

' Will changes
Dim css$ = If(schema Is Nothing, Schema.VisualStudioDefault, schema).CSSStyle
' to more natural language style
Dim css$ = (schema Or Schema.VisualStudioDefault).CSSStyle ' [If schema Is Nothing]

' Will changes
Dim name$ = args("/sheet")
csv = Xlsx.Open(.ByRef).GetTable(sheetName:=If(name = "", "Sheet1", name))
' to more natural language style
csv = Xlsx.Open(.ByRef).GetTable(sheetName:=args("/sheet") Or "Sheet1") ' As Default [If String.IsNullOrEmpty(anonymous_variable)]

User custom If assert for the default value

Fibonacci numbers example:

' Will change this long lambda
Dim fib As Func(Of Integer, Integer) = Function(x) If(x <= 1, x, fib(x - 1) + fib(x - 2))
' to more natural language style for describ an algorithm

' We can apply this default value feature to make this math lambda
' more closer to natural language in VisualBasic
'
' using user custom assert: If x <= 1
Dim fib(~x) = (fib(x - 1) + fib(x - 2)) Or x As Default If x <= 1

Factorial example:

' Will change this long lambda
Dim fac As Func(Of Integer, Integer) = Function(x) If(x <= 0, 1, fib(x - 1) * x)
' to more natural language style for describ an algorithm

' We can apply this default value feature to make this math lambda
' more closer to natural language in VisualBasic
'
' using user custom assert: If x <= 0
Dim fac(~x) = x * fac(x - 1) Or 1 As Default If x <= 0
pricerc commented 6 years ago

@xieguigang

I'm not sure that much of VB's design philosophy was about programming for beginners, especially not VB.Net.

But what I do think is one of the most important aspects of VB.Net is that it encourages legible programming - code that is easy to decode, and easy to maintain.

Those concepts are very subjective, so for example, you suggest that

' Will change this long lambda
Dim fib As Func(Of Integer, Integer) = Function(x) If(x <= 1, x, fib(x - 1) + fib(x - 2))
' to more natural language style for describ an algorithm
' We can apply this default value feature to make this math lambda
' more closer to natural language in VisualBasic
'
' using user custom assert: If x <= 1
Dim fib(~x) = (fib(x - 1) + fib(x - 2)) Or x As Default If x <= 1

I find your short version even more difficult to read and interpret than your 'long lambda'. I would argue that it would be easier to understand what the code is doing if you just used an actual function, which you could comment as appropriate, and more easily add guard clauses later on as necessary:

' Returns the (x + 1)'th number in the Fibonacci sequence 0,1,1,2,...
Function Fibonacci(n as Integer) as Integer
   ' Fibonacci is only valid for non-negative integers, and 0 and 1 are predefined
   ' TODO? throw an exception if a negative 'x' is supplied?
   ' this will return the input for negative numbers, 0 or 1.
   If x <= 1 Then Return x
   ' Return the sum of the two predecessors
   Return Fibonacci(x-1) + Fibonacci(x-2)
End Function

The performance of this function will be no different than either of your examples, but it will be easier for the programmer who comes after you to understand what's going on.

I guess that it might make me old-fashioned, but I'm not a fan of gratuitous use of lambdas/anonymous methods - in my experience, they're a right royal pain to debug if there's a problem, with the only benefit being fewer keystrokes when you initially write the code - there is certainly no performance benefit to either of the examples you described over just writing a function, and then there's the additional maintenance effort two years later when you first have to 'decode' your trivial but anonymous code...

I do worry sometimes that some 'beginner' programmers think that fewer characters equates to better performance, even though the opposite is often true (I also find myself wondering if this is part of the reason that C-esque languages are favoured over VB).

pricerc commented 6 years ago

@xieguigang

going back to your 'default' suggestion. While I like the concept, I did not find your overloaded use of 'Or' to be easy to follow, and I suspect you may have demonstrated potentially breaking changes.

Because a large part of my time is spent on SQL queries I find myself missing SQL constructs when I find myself in VB. In this case, it's COALESCE, I'd rather favour a more general purpose empty/null/nothing coalescing construct - an extension of If(), if you like, where the result is the first non-null value:

With coalesce(App.GetVariable(NameOf(R_HOME)), R_HOME)
Return Me.SaveTo(coalesce(path, _filePath, App.GetVariable("DefaultFilePath"),"FallBack.txt"))

To be really cool, it would have to tackle strings using IsNullOrWhiteSpace.

I'm not attached to the name 'Coalesce', but once you understand the concept, it is really easy to read.

bandleader commented 6 years ago

If I understand correctly, @xieguigang is talking about falsey-coalescence, like we can express concisely in JavaScript: someVariable || defaultValue

Which VB partially has with the binary If expression: If(someVariable, defaultValue), but @xieguigang has two problems with this:

  1. Currently If checks to see if it's "a reference or Nullable value that is not Nothing" -- it should also check that it isn't the default value for that type (i.e. empty string)
  2. If(expression, defaultValue) is not Fluent enough as you have to backtrack to add the If(

So @xieguigang is asking for an Or keyword that would work more similarly to JavaScript's || operator. expression Or defaultValue.

I do agree with the need for this. However, I propose using OrDefault as a keyword; it is more explicit as to what is happening, and also doesn't conflict with the existing Or operator.

(Additionally @xieguigang proposes allowing to pass a custom test expression instead of simply checking for default value. However, I don't have a good syntax for this.)

bandleader commented 6 years ago

I just want to add that this can currently be mostly solved with an extension method:

    <Runtime.CompilerServices.Extension>
    Public Function [Or](test As String, defaultValue As String)
        Return If(String.IsNullOrWhiteSpace(test), newValue, test)
    End Function

Then you can simply do myString.Or("Default Value"), which is very Fluent.

You can add overloads for each primitive type, as well as a generic overload for Object types that checks for null. You can also easily add a third parameter there for a lambda as a custom test.

However, this doesn't short-circuit if the test fails (i.e. the expression passed as defaultValue will be evaluated regardless -- as VB doesn't currently have Scala's call-by-name feature).

xieguigang commented 6 years ago

Yes, @bandleader get the point

The javascript allows user using operator || for set the default value, example as:

eatFruit = function(fruit) {
    fruitToEat = fruit || "strawberry"; 
    // ...
}

The R language is also can using a custom operator %OR% for set the default value, example as:

eatFruit = function(fruit) {
    fruitToEat = fruit %OR% "strawberry";
    # ...
}

As current VB language not allow set default value to the non-primitive type, so that we can not write a function like this example:

Function eatFruit(Optional fruit As fruit = New strawberry()) 
End Function

By using the default expression, that we can

Function eatFruit(Optional fruit As fruit = Nothing) 
    fruitToEat = If(fruit, New strawberry())
    fruitToEat = fruit Or New strawberry() ' [As Default] [If fruit Is Nothing]
    ' ...

    Call PeopleEat(fruit Or New strawberry())
    ' compare with
    Call PeopleEat(If(fruit, New strawberry()))
End Function

using fruit Or New strawberry() is more fluent than If(fruit, New strawberry()).

bandleader commented 6 years ago

@xieguigang How you do like fruit.Or(New strawberry()), as I mentioned above? (Because then we could simply add it into the class library instead of a VB language feature.)

pricerc commented 6 years ago

Given that we're discussing VB. Would not OrElse be a semantically better choice than Or ?

(Since the | of C-esque languages maps to 'Or', whereas || maps to 'OrElse')

xieguigang commented 6 years ago

Hi, @bandleader, @pricerc ,

Using an extension method can achieve the goal, like this generic default value function demonstrated:

<MethodImpl(MethodImplOptions.AggressiveInlining)>
<Extension>
Public Function [Or](Of T)(test As T, [default] As T, Optional assert As Func(Of T, Boolean) = Nothing) As T
   Return If(Not assert Is Nothing, If(assert(test), test, [default]), If(test, default))
End Function

But the extension method way have a apparent drawback:

The function parameter value isn't lazy, which means we must create the default value at first, then we are able to using the extension function. We may face a awful performance issues if the program takes a long time to create the default value...

@pricerc In my opinion, using Or will makes the VB code more closer to natural language than using the keyword OrElse.

:yum:

bandleader commented 6 years ago

@xieguigang But the extension method way have a apparent drawback: The function parameter value isn't lazy

Yes, I agree (and wrote that myself, above). Was just pointing it out. I do like the idea of a VB-native ||. (Note that we could instead just add Scala's call-by-name for parameters -- but doubt that will happen)

@xieguigang In my opinion, using Or will makes the VB code more closer to natural language than using the keyword OrElse.

But this is a short-circuiting operator, like OrElse. In any case, as I said, I don't think it makes sense to either of them (Or nor OrElse) for this one, as it's unclear and also creates ambiguities. I proposed (above) using OrDefault as the keyword -- it's explicit and clear as to what is happening, and also doesn't conflict with anything.

AnthonyDGreen commented 6 years ago

To be clear, C#'s default expression is literally the same as VB's Nothing literal, so the first ask: "Optional parameter default value for non-primitive type that can not declare as constant" is solved--you're supposed to write = Nothing.

As for the second problem, the natural read/write order sounds like the high order problem you're proposing be solved.

KathleenDollard commented 6 years ago

LDM discussed this and

This discussion does not seem to have reached a consensus, so leaving open for discussion.

bandleader commented 6 years ago

@AnthonyDGreen @KathleenDollard I'm not the OP, but I think the focus was checking for falseyness, not just nullness. The suggestion was for a falsey-coalescing operator, not a null-coalescing operator.

paul1956 commented 6 years ago

Nothing is not exactly the same as CType(Nothing, SomeType) which I find all over my code base. Nothing will not work in the example below. I left out the "If" to keep example short.

Node1 = Node1.ReplaceToken(LastToken, CType(Nothing, SyntaxToken))