-
Notifications
You must be signed in to change notification settings - Fork 66
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Composable types #47
Comments
@AdamSpeight2008 Regarding this:
Isn't
Presumably, |
@zspitz we already support a limited form of intersection types in a generic method or the because the constraints are all intersected. I've thought a lot about how useful this would be other places. In fact, when we looked at adding interfaces to the Roslyn syntax tree, one thing that stopped us was the inability to specify a parameter as SytaxNode AND IXyzSyntax. This meant sacrificing all the members on SyntaxNode when using the interface type or casting constantly, so I love the idea. The thing that concerns me the most is, ironically, verbosity and repetition in method signatures. Your proposal for type aliasing reduces it somewhat, though. I'm less optimistic about union types though. It seems rarer that I'd need them, and a bit unwieldy to use them. I guess one benefit @gafter would bring up is that they do give you a way to enforce that a Select Case/pattern match handles all cases. I know it trees us from having to define type hierarchies that depend on base classes. But do people often have code that can work with an Apple or a CarEngine? |
@AnthonyDGreen @gafter I've rewritten and expanded this proposal; specifically the use case for union types. |
Could you clarify this? I don't see how the two are related.
Perhaps not. But there is often code that works on some end-type, and there are some intermediate types which need to be mapped to the end-type somehow. Currently, such a variable has to be defined as Dim toPrint As Object = ReturnsSingleOrMultipleStrings 'return type of Object
If TypeOf toPrint Is String Then toPrint = {toPrint}
For Each s In toPrint
Console.WriteLine(s)
Next which means no type-safety or Intellisense: toPrint = new Random ' no compilation error However, using union types: Function ReturnsSingleOrMultipleStrings() As IEnumerable(Of String) Or String
End Function
Dim toPrint = ReturnsSingleOrMultipleStrings
'the following line would be a compilation error; Random is not compatible with either String or IEnumerable(Of String)
'toPrint = new Random
'the following line would also fail to compile, because toPrint might be an IEnumerable(Of String)
'Console.WriteLine(toPrint.Length)
If TypeOf toPrint Is String Then
'"effective type" of toPrint is now String
Console.WriteLine(toPrint.Length) 'will now compile
toPrint = {toPrint} 'this assignment is allowed by the original definition of toPrint
'"effective type" of toPrint is now IEnumerable(Of String)
End If
'Since all branches of the If result in toPrint being an IEnumerable(Of String), the effective type of toPrint is IEnumerable(Of String)
Dim s1 = toPrint.FirstOrDefault ' s1 is typed as String
For Each s In toPrint
Console.WriteLine(s)
Next A current workaround would be to introduce a temporary variable: Dim temp = ReturnsSingleOrMultipleStrings 'return type of Object
Dim toPrint As IEnumerable(Of String)
If TypeOf temp Is String Then
toPrint = {temp}
ElseIf TypeOf temp Is IEnumerable(Of String) Then
toPrint = temp
Else
Throw New InvalidOperationException
End If But that means cluttering up the code with a new name for the sole purpose of type compatibility, and is also less concise than this: Function ReturnsSingleOrMultipleStrings() As IEnumerable(Of String) Or String
End Function
Dim toPrint = ReturnsSingleOrMultipleStrings
If TypeOf temp Is String Then toPrint = {toPrint}
I'm not quite sure what you mean. True, it's simpler to type as |
Pinging @KathleenDollard |
Interesting. I'd like other folks thoughts. I'm seeing moderate improvements in keystroke/line count with a new layer of conceptual thinking. And, I don't know how we'd implement it, particularly as a language feature, unless you envision simply expanding the shorthand approach to the longer form in the examples you showed. |
I think it adds complexity to the code: you need to have in mind different types and look at all the code branching to know what type you are dealing with. And you'd have code that would be fragile as in difficult to refactor. You'd have cases where calling Foo(bar) in the same method would result in compilation failures even though bar was in scope simply because it was a different type. |
The primary benefits are a more precise representation of the logic behind the code:
Improvements in keystroke/line count are a good thing; but I don't think that justifies such a change. However, I think making code a better reflection of what happens at runtime is a worthwhile goal.
Isn't that a good thing as long as it's within the design goals of the language?
I think it's not as earthshattering as all that. With
For intersection types, we could use a helper Public Structure Either(Of T1, T2)
ReadOnly Property Item1 As T1
ReadOnly Property Item2 As T2
Public ReadOnly IsFirst As Boolean?
Sub New(Item1 As T1)
Me.Item1 = Item1
IsFirst = True
End Sub
Sub New(Item2 As T2)
Me.Item2 = Item2
IsFirst = False
End Sub
End Structure and the compiler rewrite could look like this: ' assignment
'Dim x As String Or Integer
'If rnd.NextDouble > 0.5 Then
'x = "abcd"
'Else
'x = 5
'End If
Dim x As Either(Of String, Integer)
If rnd.NextDouble > 0.5 Then
x = New Either(Of String, Integer)("abcd")
Else
x = New Either(Of String, Integer)(5)
End If
' see PrintStrings sub above
Sub PrintStrings(toPrint As Either(Of IEnumerable(Of String), String))
If Not toPrint.IsFirst Then
toPrint = New Either(Of IEnumerable(Of String), String)({toPrint.Item2})
End If
For Each s In toPrint.Item1
Console.WriteLine(s)
Next
End Sub Union types could be implemented with a similar helper type: Public Structure Both(Of T1, T2)
ReadOnly Property AsT1 As T1
ReadOnly Property AsT2 As T2
ReadOnly Property Initialized As Boolean
Sub New(AsT1 As T1, AsT2 As T2)
Me.AsT1 = AsT1
Me.AsT2 = AsT2
Initialized = True
End Sub
End Structure and given these classes: Public Class BaseClass
Property I As Integer
End Class
Public Interface MyInterface
Property J As Integer
End Interface
Public Class DerivedClass
Inherits BaseClass
Implements MyInterface
Property J As Integer Implements MyInterface.J
End Class the compiler rewrite could look like this: 'Dim derived = New DerivedClass
'Dim y As BaseClass And MyInterface = derived
'Console.WriteLine(y.I)
'Console.WriteLine(y.J)
'Console.WriteLine(y.ToString())
Dim derived = New DerivedClass
Dim y = New Both(Of BaseClass, MyInterface)(derived, derived)
Console.WriteLine(y.AsT1.I) ' because I is a member of BaseClass
Console.WriteLine(y.AsT2.J) ' because J is a member of MyInterface
Console.WriteLine(y.AsT1.ToString()) 'because ToString is part of a shared base class (Object.ToString) this can be arbitrary Type checking against an intersection type would look like this: 'If TypeOf z Is String Or Integer Then
If TypeOf z Is String Or TypeOf z Is Integer Then Type checking against a union type would look like this: 'If TypeOf z Is BaseClass And MyInterface Then
If TypeOf z Is BaseClass And TypeOf z Is MyInterface Then N.B. There is still a further issue of how to handle multiple levels of composed types. |
Only as a reflection of the complexity of the underlying code logic. Currently, if my code deals with some object/value which I know is either a If my code deals with something that I know is both a
In the scenarios where intersection types add value, you have to do that anyway: e.g. in this branch, the
Could you elaborate on this?
Is that a bad thing? The alternative is to have a runtime failure of the call to Note that there is a proposal to enforce this in general for the All of these seem to be criticisms of the intersection types part of my proposal, and are not relevant to the union types, or for multitype checking. |
@Bill-McC @KathleenDollard @AnthonyDGreen Any further thoughts on this? |
Don't have anything to add as such. Not sure the case of string or int is an intersection... the only thing in common is ToString.
The case of baseclass with an interface dies require casting. In days of com, there would be an IBaseClass. Thoughts around this lead me towards duck typing, and psuedo types and interfaces. That I think is worth looking at, think it may still be open under a few proposals here.
When there us commonality, worth solving. When there is none, Object and casting is clearer .
Regards, Bill.
…________________________________
From: Zev Spitz <notifications@github.com>
Sent: Tuesday, April 24, 2018 11:20:18 AM
To: dotnet/vblang
Cc: Bill-McC; Mention
Subject: Re: [dotnet/vblang] Composable types (#47)
@Bill-McC<https://github.com/Bill-McC> @KathleenDollard<https://github.com/KathleenDollard> @AnthonyDGreen<https://github.com/AnthonyDGreen> Any further thoughts on this?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub<#47 (comment)>, or mute the thread<https://github.com/notifications/unsubscribe-auth/AID_hNNDN5jkYba4a8js2U5fYcs8n-oDks5trn3SgaJpZM4Maovr>.
|
I'm not yet sold on this feature. @CyrusNajmabadi has some good comments about why TypeScript needed this and maybe .NET doesn't in the C# of intersection / union types. I want to watch how thinking evolves on this, but I don't yet see the benefit ratio against the amount of work across languages and type system. |
This is the sort of thing that would want to have serious discussions about whether to do in the language (via erasure, your suggestion) or with BCL support. Either way, this is likely to be something we'd want a broad base of support and probably having C# take the lead on a rather technical issue. |
@KathleenDollard There is one possible reason why this would be more attractive in VB.NET over C# -- "classic" VB didn't have overloads and was limited to a single function name within a given scope; whereas C#'s syntax is in the main inspired by C-like languages, which support function overloads. I am not sure if this is still a design goal in VB.NET, but after all VB.NET had the Typescript can serve as a model for the transition from a language without composite types to a language with composite types; but I think the more appropriate comparison is to F#'s composite types, as F# is a statically typed language while still having composite types. I'm currently rewriting the Typescript definitions for the Excel object model, and this benefit is actually quite striking. Consider this definition, autogenerated from the registered type lib information:
What arguments can be passed into the Arguably, it's possible (and advisable) to embed the documentation in the file, and it would then be available in the editor. However, I still have no guarantee that the values I pass in are of the correct type for this context. But the following definition describes the possible values simply and concisely, and also enforces it at compile time:
Now, this could be expressed using overloads:
but now I have four method signatures to read instead of two (aside from the overloads not being quite accurate, because a single |
Just that for me, the main use case would be union types, for simplifying overloads (as you mentioned as well). Also, I can even abstract away the conversion code: Function Foo(one As (URI Or String), two As (URI Or String))
Dim getUri = Function(s As (URI Or String)) If(TypeOf s Is URI, s, New URI(s))
Dim uriOne = getUri(one), uriTwo = getUri(two)
'All done! Use uriOne and uriTwo here...
End Function |
If you write Public Structure Either(Of T1, T2)
Public ReadOnly Property Item1 As T1
Public ReadOnly Property Item2 As T2
Public ReadOnly IsFirst As Boolean?
Public Sub New(Item1 As T1)
Me.Item1 = Item1
IsFirst = True
End Sub
Public Sub New(Item2 As T2)
Me.Item2 = Item2
IsFirst = False
End Sub
Public Shared Widening Operator CType(value As T1) As Either(Of T1, T2)
Return New Either(Of T1, T2)(value)
End Operator
Public Shared Widening Operator CType(value As T2) As Either(Of T1, T2)
Return New Either(Of T1, T2)(value)
End Operator
End Structure You can write a function like this: Function Foo(one As Either(Of Uri, String), two As Either(Of Uri, String))
Dim getUri = Function(s As Either(Of Uri, String)) If(s.IsFirst, s.Item1, New Uri(s.Item2))
Dim uriOne = getUri(one), uriTwo = getUri(two)
'Use uriOne and uriTwo here...
End Function And call it like: Foo("https://www.google.com/", New Uri("https://github.com/")) |
@Berrysoft True. However, since we're talking about the compiler rewriting this:
to something like this:
I'm not sure if there would be any benefit if the compiler would rewrite to:
|
@Berrysoft Of course, for interop purposes, the casting operators would be very valuable; and in fact the F# compiler does this with discriminated unions with multiple types. |
Shoutout to the OneOf library, which allows creating union types in .NET. |
I propose that type names can be replaced with composed types, in either of the following forms:
<Type> And <Type>
-- intersection types<Type> Or <Type>
-- union typesUse cases:
Multitype checking
This syntax allows for checking either of two types (
Or
), or both of two types (And
). Requires no changes to theTypeOf <x> Is <Type>
statement, only an expansion of<Type>
.Instead of:
Use:
Instead of:
Use:
Parentheses are not required, because the compiler can differentiate between
<Type> Or <Type>
and<Boolean> Or <Boolean>
; but it's important that parentheses be allowed, to improve readability when needed:Use members from both type parts (intersection type only)
Define parameters / variables as an intersection of a class and an interface; allows using members from both without having to cast:
Consolidate function overloads (union types only)
Reduce the boilerplate function overloads needed for multiple types. Instead of this:
Use this:
Type aliases
VB.NET already has a syntax for type aliasing:
This should support using composable types:
Potential Issues
Some static flow analysis would make union types much more useful. The flow analysis would limit the type within a type check, or after an assignment (this is really just an extension of Option Infer Turbo (aka Smart Variable Typing) #172):
There already exists a syntax for intersection types, when used with generic constraints:
However, I don't see how it can be extended for union types as well.
OTOH, I don't see any reason not to allow composite types in generic constraints:
Is there CLR support for union and intersection types? (See F#.)
How could such members / classes be represented for compatibility with other languages? (Also see F#.)
How would reflection work with these types? (Again, see F#.)
Contexts
Composable types should be allowed in the following contexts:
TypeOf
statementThey should not be allowed in the following contexts:
Inherits
-- For union types, to say thatA
inherits fromB
or fromC
is meaningless. For intersection types, also not, because VB.NET doesn't support multiple inheritanceImplements
-- The class cannot implement either interfaceA
or interfaceB
; it must implement both, so union types are irrelevant. Implementing an intersection types adds nothing over implementing each type separately.Links
The text was updated successfully, but these errors were encountered: