Open mansellan opened 2 years ago
I like it.
FWIW, I'd go for this syntax:
Dim EmployeeOfTheMonth As New EmployeeRecord(1, "Wayne Phillips", HireDate:=#01/01/2000#)
Keep things more uniform & consistent with how we new up classes.
This sounds like a good proposal to me too.
I'm not keen on the As-New syntax though, since it blurs the line between objects and UDTs, in my opinion. Especially since As-New has very specific/special meaning already in the language.
Why not just allow Array() ?
Dim EmployeeOfTheMonth As EmployeeRecord = Array(1, "Wayne Phillips", #01/01/2000#)
I also dislike the As-New syntax approach.
I understand why As New might not be as desirable but I can't go with Array because UDTs aren't arrays so assigning an Array()
to an UDT feels very weird. With the As-New, there's at least prior arts with .NET.
But more importantly, in .NET, we can have truly immutable types (See: #50 ) but that requires language to support creating one, which currently is impossible to do with VBx short of creating a heavyweight class module. Just to emphasize, the whole point of allowing New()
constructor on the structures is to help the compiler enforce the immutability of the structures, so that there's no difference at the runtime and thus no performance penalty.
I have another idea. tB could allow a function which returns that UDT. Kinda like the default parameter accepting a function.
Example
Private Type MyUDT
Arg1 As Date
Arg2 As String
Arg3 As Long
End Type
Dim This As MyUDT = InitMyUDT(#01/01/2000#, "Hello", 777)
Private Function InitMyUDT(Arg1 As Date, Arg2 As String, Arg3 As Long) As MyUDT
With InitMyUDT
.Arg1 = Arg1
.Arg2 = Arg2
.Arg3 = Arg3
End With
End Function
This approach would allow optionals or even some logic inside the initializer. The one-time effort for the InitMyUDT is worth it and it ensures data type correctness and allows great flexibility and creativity.
But more importantly, in .NET, we can have truly immutable types (See: #50 ) but that requires language to support creating one, which currently is impossible to do with VBx short of creating a heavyweight class module. Just to emphasize, the whole point of allowing
New()
constructor on the structures is to help the compiler enforce the immutability of the structures, so that there's no difference at the runtime and thus no performance penalty.
But in VBx, UDTs are just collections of fields. I can't see a way of supporting immutability there without changing the underlying paradigm?
@Krool looks like that's already possible in twinBASIC, except that initialisation-by-function is only allowed inside a Sub
or Function
. I would think that initialising value types by calling a function would be problematic outside of local scope, because value types always need to have a value. It could get confusing (and potentially recursive) if you're accessing variables which are still being initialised...
I think its a slightly different aim - I was hoping to be able to initialise a UDT in as concise a way as possible (while still fitting in with BASIC syntax and type safety)
But in VBx, UDTs are just collections of fields. I can't see a way of supporting immutability there without changing the underlying paradigm?
As discussed in the linked issue, If it's going to cross COM boundaries, it can't be immutable. Immutability would only apply to the types used internally within the twinBASIC project. That way, it becomes a compile-time check which is enforced with no runtime penalty.
This approach would allow optionals or even some logic inside the initializer. The one-time effort for the InitMyUDT is worth it and it ensures data type correctness and allows great flexibility and creativity.
Here's the thing... if we really want to try hard and avoid using New
syntax, we are going to end up with a version that looks and feel like New
but isn't quite.
For instance, we can just provide a similar behavior with Init
similar to what Kr00l proposed:
Dim EmployeeOfTheMonth As EmployeeRecord = Init(1, "Wayne Phillips", HireDate:=#01/01/2000#)
But in order to define the Init
, we'd need to write it:
Public Type EmployeeRecord
ID As Integer
Name As String * 20
Address As String * 30
Phone As Long
HireDate As Date
Sub Init(ID As Integer, Name As String * 20, Optional HireDate As Date)
With Me
.ID = ID
.Name = Name
.HireDate = HireDate
End Sub
End Type
We have re-invented New
and used a different word....
Is requiring people to remember to use Init
an improvement over using familiar New()
? One argument against that is that in VBx we don't even have parameterized constructors so even the New
pattern will be new concept to those who use VBx exclusively. But if they are already familiar with other languages, they will get tripped up if they try to new up the UDT and find out that no, they can't use that keyword but Init
(or whatever instead). That might be argued that it's better because it emphasize that an UDT is different from a class. OTOH, that would make refactoring an UDT into a class more work than necessary because one'd have to track down all the uses of the UDT and update the keywords to construct the class.
OK that makes sense. But I'd like to suggest that UDTs should not attempt to be immutable, internally or otherwise. UDTs have always been mutable in VB6. In .Net, Tuple
s are mutable, ValueTuple
s are immutable. Frankly, it's a bit of a mess, and I think there's a better way.
Keep UDTs mutable, in all settings. If there's a need for immutable composite value types, then lets add them along with many other conveniences. I suggest this in #595, that way there would be:
This would seem to be a good analog to the tuple/struct/class system, just with less disturbance to existing syntax.
An alternative would be a ReadOnly Type
, but not sure...
EDIT: I'm a huge fan of immutability, I always strive to make everything possible immutable. But I'm wondering if retrofitting immutability to UDTs is a step too far.
Here's a different tack. We should just use Class
but provide an attribute indicating that we want the compiler to treat it as a value type.
[ ValueType ]
Public Class EmployeeRecord
ID As Integer
Name As String * 20
Address As String * 30
Phone As Long
HireDate As Date
Sub New(ID As Integer, Name As String * 20, Optional HireDate As Date)
With Me
.ID = ID
.Name = Name
.HireDate = HireDate
End Sub
End Class
The compiler can then optimize the definition so that it's now a value type and is passed around just like one and we have full control over how it is constructed, whether it's immutable using the same familiar syntax we use for classes. No new keywords or concepts (beyond the parameterized constructor) to learn.
That in turn solves the initialization question. Just use a class instead of UDT. Furthermore, UDT can be easily promoted into a ValueType
-attributed class.
Interesting idea...
But classes and structs (/UDTs) are different in so many ways that to condense it to a single attribute seems reductive.
I need to think on this some more.
@WaynePhillipsEA have you thought about setting up a Discord / Gitter / SE channel lol :-)
[ ValueType ] Public Class EmployeeRecord ID As Integer Name As String * 20 Address As String * 30 Phone As Long HireDate As Date Sub New(ID As Integer, Name As String * 20, Optional HireDate As Date) With Me .ID = ID .Name = Name .HireDate = HireDate End With End Sub End Class
In fact I think we're talking about the same thing @bclothier . if you swap your attribute for my keyword:
Public Structure EmployeeRecord
ID As Integer
Name As String * 20
Address As String * 30
Phone As Long
HireDate As Date
Sub New(ID As Integer, Name As String * 20, Optional HireDate As Date)
With Me
.ID = ID
.Name = Name
.HireDate = HireDate
End With
End Sub
End Structure
I think a new keyword is justified, because this is a language construct. It's not an implementation detail that different libraries could take different views on.
Yes, it boils down to whether an attribute decorating a class or a specific keyword is better. In both cases, we still can use the New()
sybtax for either which is a big win in my book.
The key thing for me is keeping the syntax simple and consistent as much possible and those two alternatives seem to achieve this well.
The attribute idea was mainly to avoid introducing a new syntax (in this case, Structure
) and making it much cheaper to transform from a reference type into value type or vice versa. We don’t want to make it so hard to modify a flawed design or accommodate a change in requirements that it disincentivizes the users from refactoring their code.
Bump to #50 though, It's been a while since I read it. I'd love for it to be part of the language at some point.
FWIW, I'd go for this syntax:
Dim EmployeeOfTheMonth As New EmployeeRecord(1, "Wayne Phillips", HireDate:=#01/01/2000#)
Keep things more uniform & consistent with how we new up classes.
Is that really desirable from a code-clarity POV? I mean that someone reading the code for maintenance purposes later on - how to they understand a UDT Initialization from a Class-instance initialization just by reading that line of code? Should there not be differences to make the underlying intent more clear?
The problem from my perspective is that New
currently only applies to reference types. UDTs are value types, so they exist (with default values) as soon as they are declared. New
to me implies construction, which doesn't apply to value types.
Is that really desirable from a code-clarity POV?
With IDE features like syntax highlighting and Peek Definition, it becomes less of a problem.
You can make the case that because UDT is different from class in kind rather than degree, it should have its own syntax for initialization. My position is that it'll just come off as a reinvention of the New()
syntax which means more learning than necessary. Construction is a very big and complex subject so I think we get more productivity out of using New()
for that purpose.
New to me implies construction, which doesn't apply to value types.
Yet, we are discussing exactly just that -- how to construct a value type...
Probably getting into semantics here, but I would say that UDTs get initialised rather than constructed
Sure. It's probably the result of .NET corrupting my mind. 😄
As I said, I can see arguments for using a separate Initialize
syntax, but when I think about how .NET allow you to have multiple constructors for a struct
, using the same syntax seems simpler when you consider the complexity that comes with construction.
Can you give me a better clue about that complexity you're talking about there? or are we touching on the things like generics (something I have the barest of understandings about presently) with that?
Here is a very simple suggestion for this:-
Public Type MyUdt
IntField As Integer
End Type
Dim var As MyUdt With {.IntField = 12}
In FreeBasic:
Dim udt_symbol AS DataType = ( expression [, ...] )
Use the name of the UDT as its own constructor - like tB classes but drop the New because it's not an object?
Dim employeeOfTheMonth As EmployeeRecord = EmployeeRecord(1, "Wayne Phillips", HireDate:=#01/01/2000#) 'my favourite
Dim employeeOfTheMonth As EmployeeRecord(1, "Wayne Phillips", HireDate:=#01/01/2000#) 'this feels auto-instantiated so not sure
or as a multiliner (only for option 1 above):
Dim employeeOfTheMonth As EmployeeRecord
employeeOfTheMonth = EmployeeRecord(1, "Wayne Phillips", HireDate:=#01/01/2000#)
'Maybe it's not safe to introduce essentially a function with the same name as the UDT,
' so perhaps one-liner is the only context where this should be allowed.
' But this feels most natural to me, just like how I can `Dim x As Class1 ... Set x = New Class1(*args)` in tB
' - simply drop the `Set` and `New` for UDTs
Is your feature request related to a problem? A nice feature of twinBASIC is the ability to declare and initialise variables in a single line. Can this be extended to include User Defined Types?
Describe the solution you'd like Given a UDT of the form:
the declaration:
will create a new copy with all fields initialised to their default values.
Suppose we want to set some (but not all) of the available fields:
It would be awesome if this could be condensed:
or, mixing named with positional:
Describe alternatives you've considered The status quo
Additional Context I wondered whether the brackets were strictly necessary. I think in the simple case, they help show that the values are closely related. More importantly, they're probably necessary for the case where types are nested.