twinbasic / lang-design

Language Design for twinBASIC
MIT License
11 stars 1 forks source link

Functions on data types #7

Open bclothier opened 3 years ago

bclothier commented 3 years ago

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:

Dim FullName As String
Dim Elements As Variant
Dim FirstInitial As String
Dim MiddleInitial As String
Dim LastInitial As String

Elements = Split(FullName, " ")
If UBound(Elements) >= 0 Then
  FirstInitial = Left$(Elements(0), 1)
End If
If UBound(Elements) >= 2 Then
  MiddileInitial = Left$(Elements(1), 1)
  LastInitial = Left$(Elements(2), 1)
Else
  MiddleInitial = vbNullString
  LastInitial = Left$(Elements(1), 1)
End If

Debug.Print FirstInitial & MiddleInitial & LastInitial

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:

Dim Fullname As String = "John D. Smith"
Dim Elements() As String = FullName.Split(" ")
Dim FirstInitial = Elements(0).Substring(0, 1)
Dim MiddleInitial = If(Elements.Length > 2, Elements(1).Substring(0, 1), String.Empty)
Dim LastInitial = If(Elements.Length > 2, Elements(2).Substring(0, 1), Elements(1).Substring(0, 1))

Debug.Print FirstInitial & MiddleInital & LastInitial

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 a String() rather than a Variant. We also have a easier time finding relevant string constants with String.Empty than memorizing vbNullString.

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.

WaynePhillipsEA commented 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.

bclothier commented 3 years ago

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.

WaynePhillipsEA commented 3 years ago

@bclothier looking at it from a backwards compatibility perspective, I just can't see that being an option.

Kr00l commented 3 years ago

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)

Kr00l commented 3 years ago

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.

WaynePhillipsEA commented 3 years ago

@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.

Greedquest commented 3 years ago

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...

Edit

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.

XusinboyBekchanov commented 3 years ago

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.

WaynePhillipsEA commented 3 years ago

@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.

retailcoder commented 3 years ago

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.

retailcoder commented 3 years ago

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.

vbRichClient commented 3 years ago

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

Kr00l commented 2 years ago

Not that we have enough flame war .NET vs BASIC, but wouldn't this feature request determine the path which tB will go?

mansellan commented 2 years ago

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...

bclothier commented 2 years ago

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.