Open bclothier opened 3 years ago
I like this a lot. We should come up with wish lists for each data type group, whilst being mindful that VB.NET already has such a feature and so we should try to mirror their functions where appropriate.
Long term I can also see us having an option to turn off the global functions to tidy up the global namespace.
This feature is a prime candidate for being incrementally implemented.
An related question needs to be considered; does it help to treat primitive data types as if they were classes?
In .NET and other languages, doing so simplifies the syntax and allow those to be treated more consistently. However, we might not want the boxing/unboxing behavior. I would prefer something that can be inferred at the compile time so there is no additional runtime overhead as the result.
@bclothier looking at it from a backwards compatibility perspective, I just can't see that being an option.
Will these (duplicated functionality) disappear when the legacy mode switch is flipped? Just my opinion. This could bring tB far more away concerning portability of source code. Currently one can take a tB source code and still could re-implement it in VB6/VBA. (maybe with little adaptions, e.g Decimal/Variant)
I just read again the "legacy mode" issue and it describes perfectly my concern. Because I also suggested new features which will break portability. When this is all covered by that setting it's all fine. Sorry for the comment.
@Kr00l yep, the legacy mode will switch off everything that is not compatible with VB6/VBA, including this feature.
As a general note, forward-compatibility with VB6/VBA should not be a tB concern, only backward-compatibility.
I don't remember what it's called, but I think in c# or other languages there's a special syntactic sugar where if the first parameter of a function is a certain type, then you can either call the function passing a value explicitly as the first argument or implicitly as if it were a member call and the value was the Me
argument.
So
Function MyLeft(ByVal s As String, ByVal length As Long) As String '...
Dim a As String
MyLeft(a,5)
'is equivalent to
a.MyLeft(5) 'a is the implicit "Me" like first argument to MyLeft
If this syntax were allowed then we get the member calls free without converting primitive datatypes to classes or touching the implementation of the existing functions. Intellisense would know to populate a string's member calls with whatever functions take string as their first parameter. Maybe with some keywords to allow/disallow it*.
*I'm not sure if you'd ever want explicitly to allow or disallow the .syntax
, but if you do want to be explicit then a really neat way to allow it might be if Me
was the name of the first parameter, then it would allow the function to be invoked in the .way, otherwise it must be called the standard way. ByRef and ByVal and Subs that mutate rather than copy and return the value might need special consideration, I'm not sure...
I realise what I'm talking about are extension methods which do indeed use this
modifier on the first argument to mark the function as invokable as if it were an instance method of that type. In tB we could declare these in a module rather than a static class.
I suggest doing this: If the class has a function, you can call it like this:
Class a
Function b(c as string)
End Function
End Class
Dim d as a
b(d, "First Argument of Function")
This is handy in exported functions too.
@Greedquest unfortunately that approach would have significant impact on multiple areas of the compiler. The proposal from @bclothier would be much cleaner and easier, from the tB compiler implementation perspective, as it is pretty much already geared up for it.
Reading this the first thing that came to mind was "this could be very nice if it's implemented as extension methods" - with the possibility for tB user code to define custom ones, perhaps beyond intrinsic data types.
The syntax doesn't have to break existing signature syntax, either - I think something like this could work fine:
Public Function Split(ByVal Value As String, ByVal Delimiter As String) As String() Extends String
Where the data type that follows the Extends
keyword is simply required to match the data type of the first parameter in the signature.
Compatibility mode could then easily disallow the new Extends
keyword, and forbid calls to such extension methods.
Long-term, the legacy global-scope functions should end up in a dedicated namespace akin to Microsoft.VisualBasic
in VB.NET; with additional metadata linking the legacy functions to their extension method counterpart somehow, there's a future where a quick-fix could locate all the legacy functions and replace them with their extension equivalent.
I'm not opposed to compiler-supported "Dot-Methods behind certain Types", but would suggest that the community (not only Wayne) focuses on the "100% compatibility goal" first.
IMO - at this point in time - we need more "bug-reports" and not much else - (the community writing nice test-cases, to lessen the "Strain on Wayne" ;-))...
Nice to have "dream-features" will not help very much, when the foundation is brittle.
E.g. (regarding the example in the Opener-Post here) - having a stable "legacy-foundation", would be enough to write it this way (introducing a little helper-function):
Function SplitInitials(Name As String) As Collection
Set SplitInitials = New Collection '<- always return a valid Obj
Dim S: For Each S In Split(Name)
If Len(S) Then SplitInitials.Add Left(S, 1)
Next
End Function
Which then allows the UserCode to be written like that:
Dim First As String, Middle As String, Last As String
With SplitInitials(" John D. Smith ") '<- note the extra-spaces
If .Count Then First = .Item(1): Last = .Item(.Count)
If .Count > 2 Then Middle = .Item(2)
End With
Debug.Print First & Middle & Last
In sum, not more code (+ accounting for potential "extra-spaces" + "keeping up DRY").
The "legacy-base" is not a slouch, let's make it stable first.
Olaf
Not that we have enough flame war .NET vs BASIC, but wouldn't this feature request determine the path which tB will go?
Hmm, perhaps? Although if we allowed for extension methods, these could be added as library functions, perhaps in a separate library so that people didn't have to see them if they didn't want...
I agree - I think we need to be able to author our own extension methods so it can be then shared. If that can be done without affecting backward compatibility, then it would be a great win, I think.
One aspect about BASIC I dislike is that there are a number of functions that operates on a data types and thus are not easily discoverable. Take string for example. We need to know a number of string functions and operators to do something with string:
This example illustrates uses of several "unrelated" functions, including using an array which incidentally is also a
Variant
to do something useful with the string. In contrast, I quite like how other languages have functions defined on strings and other "primitive" data types:Because the functions are defined on the data types, we have easier time find possible functions to use with a data type; the intellisense can show all functions relevant to that data type and the return can be strong-typed such as with
Split
can safely return aString()
rather than aVariant
. We also have a easier time finding relevant string constants withString.Empty
than memorizingvbNullString
.It's noteworthy that while you could spell out the module name (e.g.
Strings.Left()
), this is not available with all functions. e.g.UBound
doesn't even appear in the object browser.Because tB is committed to backward compatibility, we can't do much about the existing functions. However, to allow for more terse and still readable code, I'd rather see functions defined on the "class" rather than added to the global namespace and promoting refactoring legacy codebase to this form which help expresses the intention more clearly in fewer lines.