dotnet / vblang

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

[Proposal] TypeOf over multiple types. #23

Open AdamSpeight2008 opened 7 years ago

AdamSpeight2008 commented 7 years ago

(Ported from Roslyn Repo)

TypeOf ... IsNot ..

If TypeOf obj IsNot TypeA AndAlso
   TypeOf obj IsNot TypeB Then
If TypeOf obj IsNot {TypeA, TypeB} Then

TypeOf ... Is ...

If TypeOf obj Is TypeA OrElse
   TypeOf obj Is TypeB Then
If TypeOf obj Is {TypeA, TypeB} Then

Grammar

TypeOf_Obj   ::= "TypeOf" ws+ identifier
TypeOf_IsExpr    ::= TypeOf_Obj ws+ "Is" ws+ MatchAgaist
TypeOf_IsNotExpr ::= TypeOf_Obj ws+ "IsNot" ws+ MatchAgainst
MatchAgainst     ::= Single | MultipleTypes 
SingleType       ::= typeIdentifier
MultipleTypes    ::= '{' typeIdentifier (ws* ',' ws* typeIdentifier)+ ws* '}'

Ruled out TypeOf obj Is ( Type0, Type1. Type2 ) as that conflicts with tuple literal syntax.

rolfbjarne commented 7 years ago

I'm not sure I like this, but an improvement might be to allow Or or And instead of commas to separate the types.

This means your examples become:

If TypeOf obj IsNot {TypeA And TypeB} Then
If TypeOf obj Is {TypeA Or TypeB} Then

And it also allows you to test if a type implements multiple interfaces:

If TypeOf obj Is {InterfaceA And InterfaceB} Then

A downside is that it sounds grammatically weird in the negative case (IsNot .. Nor would sound better than IsNot ... And, but that would introduce another keyword for a dubious reason).

AdamSpeight2008 commented 7 years ago

The issue with TypeA And TypeB and TypeA Or TypeB is that they are operators, and would affect the order of evaluation. Eg

If TypeOf obj IsNot {TypeA And TypeB} Then
' becoming 
If TypeOf obj IsNot {Boolean} Then
rolfbjarne commented 7 years ago

The And and Or keywords would have to be treated like something other than operators in this context.

AdamSpeight2008 commented 7 years ago

Alternative Syntax is to use the syntax of generic type parameters.

TypeOf obj Is (Of T0, T1, T2)
TypeOf obj IsNot (Of T0, T1, T2)
Bill-McC commented 7 years ago

I wonder if we can relax the rules around Select Case such that Is doesn't require a comparison operator when using a Select Case TypeOf(obj) eg: `Select Case TypeOf(obj) Case Is T1, T2, T3

Case Else

End Select`

aarondglover commented 7 years ago

If Mads and the other c# fan boys didnt believe that Pattern Matching is too much for the simple brain of a VB developer then there wouldn't be much need to do the select case approach and VB would have a powerful feature.

AdamSpeight2008 commented 7 years ago

The use case I've seen are in the expression part of an if-statement. Select Case ... End Select isn't an expression.

AdamSpeight2008 commented 7 years ago

@AnthonyDGreen I am looking at implementing this feature, in the form. ~TypeOf obj Is (Of T0, T1)~ TypeOf obj Is { T0,T1 } (in a prototype form)

Initial research suggest that I'd only need to change the binder to bind to ~generic type parameter list~ and new type TypeArray. Then in lowering apply the compiler generated transform the to the expanded version.

AdamSpeight2008 commented 6 years ago

@KathleenDollard @AnthonyDGreen There is a working proof of concept of this feature in PR #93

AdamSpeight2008 commented 6 years ago

@rolfbjarne I going experiment with TypeOf expr Is T1 Or T2 Or T3 as I think it maybe possible to support, by manipulating the lowering.

(TypeOf expr Is T1 Or T2 Or T3) ==>
( (TypeOf expr Is T1) Or (TypeOf expr Is T2) Or (TypeOf expr Is T4)
AdamSpeight2008 commented 6 years ago

@KathleenDollard @AnthonyDGreen I've updated the original post to include the two proposals.

KathleenDollard commented 6 years ago

I've been traveling and balancing a family illness, so not as responsive as I'd like to be, and the VB LDM has missed a number of meetings. Explaining the silence on this.

@aarondglover Your comment on Mads and the managed language team is not correct. Folks on the team do not believe that VB programmers have "simple brains."

The C# community is taking two steps in to pattern matching. The structural one already taken and the functional one on the table for C# 8. We don't know if pattern matching will be in an upcoming version of VB, but if it is, VB will hopefully take a single step into expressions (perhaps also with structural).

I'm afraid I'm struggling to see the value in this proposal. My personal comprehension of the current is better than the proposed. Is there a win other than in comprehension and can folks weigh in on whether they see a big win in the proposed on comprehension?

AdamSpeight2008 commented 6 years ago

@KathleenDollard @AnthonyDGreen I've mocked up a example of each based on a vb file in Roslyn repo. Original Example

Proposal 1: Example Using TypeOf Expressions with TypeListSyntax

Proposal 2: Example using Extended TypeOf Expressions

AdamSpeight2008 commented 6 years ago
paul1956 commented 6 years ago

There are two patterns that appear all over my code, the first would be a lot simpler to express if I could just list out all the possibilities, and for the second I need a Select statement that works with TypeOf and Is. How you implement it I don't care. I don't see the value in C#'s DeclarationPattern for VB unless everything is declared Object or Infer is On, two things I personally don't like, or VB has local Options where I could allow Infer On just around pattern matching.

{If or Return} TypeOf statement Is CSS.BreakStatementSyntax OrElse
                      TypeOf statement Is CSS.ContinueStatementSyntax OrElse
                      TypeOf statement Is CSS.ExpressionStatementSyntax OrElse
                      TypeOf statement Is CSS.ReturnStatementSyntax OrElse
                      TypeOf statement Is CSS.ThrowStatementSyntax OrElse
                      TypeOf statement Is CSS.YieldStatementSyntax

and

If TypeOf exprNode Is Syntax.CasePatternSwitchLabelSyntax Then
Dim ConstantPattern As ConstantPatternSyntax = CType(PatternLabel.Pattern, ConstantPatternSyntax) 
...
ElseIf TypeOf exprNode Is Syntax.ObjectCreationExpressionSyntax then
...
ElseIf TypeOf exprNode Is Syntax.SwitchLabelSyntax Then
...
ElseIf TypeOf exprNode Is Syntax.DeclarationPatternSyntax Then
...
Else
...
End If

I used Roslyn Types here but this applies to VB Forms as well where you frequently want to change some common control property and need to cast if first before the property is available with Options Strict and Explicit On.

AdamSpeight2008 commented 6 years ago

@paul1956 Your first example is what this proposal is specifically targeting. Using TypeList Syntax

{If or Return} TypeOf statement Is { CSS.BreakStatementSyntax,
                                     CSS.ContinueStatementSyntax,
                                     CSS.ExpressionStatementSyntax,
                                     CSS.ReturnStatementSyntax,
                                     CSS.ThrowStatementSyntax,
                                     CSS.YieldStatementSyntax}

Using TypeOf Operators Syntax

{If or Return} TypeOf statement Is CSS.BreakStatementSyntax OrElse
                                   CSS.ContinueStatementSyntax OrElse
                                   CSS.ExpressionStatementSyntaxm OrElse
                                   CSS.ReturnStatementSyntax OrElse
                                   CSS.ThrowStatementSyntax OrElse
                                   CSS.YieldStatementSyntax

If we take this code sample, which is similar in form to your second example and use Select TypeOf

        Public Overrides Function GetAttributeNodes(node As SyntaxNode) As IEnumerable(Of SyntaxNode)
            Select TypeOf node
              Case CompilationUnitSyntax    Out result : Return GetAttributeNodes(result.Attributes)
              Case TypeBlockSyntax          Out result : Return GetAttributeNodes(result.BlockStatement.AttributeLists)
              Case EnumBlockSyntax          Out result : Return GetAttributeNodes(result.EnumStatement.AttributeLists)
              Case DelegateStatementSyntax  Out result : Return GetAttributeNodes(result.AttributeLists)
              Case DeclareStatementSyntax   Out result : Return GetAttributeNodes(result.AttributeLists)
              Case MethodStatementSyntax    Out result : Return GetAttributeNodes(result.AttributeLists)
              Case MethodBlockBaseSyntax    Out result : Return GetAttributeNodes(result.BlockStatement.AttributeLists)
              Case PropertyBlockSyntax      Out result : Return GetAttributeNodes(result.PropertyStatement.AttributeLists)
              Case PropertyStatementSyntax  Out result : Return GetAttributeNodes(result.AttributeLists)
              Case EventBlockSyntax         Out result : Return GetAttributeNodes(result.EventStatement.AttributeLists)
              Case EventStatementSyntax     Out result : Return GetAttributeNodes(result.AttributeLists)
              Case FieldDeclarationSyntax   Out result : Return GetAttributeNodes(result.AttributeLists)
              Case ParameterSyntax          Out result : Return GetAttributeNodes(result.AttributeLists)
              Case ModifiedIdentifierSyntax Out result : Return GetAttributeNodes(result.Parent)
              Case VariableDeclaratorSyntax Out result : Return GetAttributeNodes(result.Parent)
            End Select
            Return SpecializedCollections.EmptyEnumerable(Of SyntaxNode)()
        End Function

The Out result is scoped to only the Case Block it is on. eg

Case CompilationUnitSyntax    Out result : Return GetAttributeNodes(result.Attributes)
' ...
 Case CompilationUnitSyntax
    Dim result As CompilationUnitSyntax = DirectCast(node, CompilationUnitSyntax)
    Return GetAttributeNodes(result.Attributes)

Which why we can reuse result in subsequent Case Statements.

paul1956 commented 6 years ago

@AdamSpeight2008 I agree with your first comment, I was addressing the need question from @KathleenDollard. We need a simpler solution,

I have tried to use Select TypeOf and cant find any syntax where it don't get errors. I my actual usage I am usually doing a lot more work and don't need a single line version. I typically want to do the cast to a new variable and operate on the result, so scope is also not an issue and, that is why in my example I showed the CType (I did notice that I could use DirectCast., thanks for the tip).

AdamSpeight2008 commented 6 years ago

@paul1956 Select TypeOf is a separate proposal.

paul1956 commented 6 years ago

@AdamSpeight2008 I though you were showing me how to do Select TypeOf, I fully support the proposal it is really needed.

zspitz commented 6 years ago

A generalized pattern matching syntax would combine the OR pattern and the type-check pattern, to get the same result as this proposal:

Dim o As Object
'...
If o Matches TypeA, TypeB Then

or, if the type-check pattern syntax is not optional:

Dim o As Object
'...
If o Matches Of TypeA, Of TypeB Then
zspitz commented 6 years ago

I would also suggest that just as we don't have a ValueOf keyword:

Dim x = 5
If ValueOf x < 10 Then

but instead we compare directly the value of x:

Dim x = 5
If x < 10 Then

so too on a conceptual level we aren't interested in the type per se, but rather in does this object support this type? --

Dim o As Object
'...
If o Is IEnumerable Then

and it's unfortunate that Is is already taken, as testing for reference equality. But we shouldn't introduce new uses of TypeOf where it is unnecessary.

277

jrmoreno1 commented 5 years ago

Is there an example where this would be used in the Roslyn codebase?

paul1956 commented 5 years ago

@jrmoreno1 It is heavily used in the VB to C# and C# to VB converters but I am not sure where they are today.