twinbasic / lang-design

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

Return should be legal in Subs #38

Open bclothier opened 2 years ago

bclothier commented 2 years ago

Describe the bug Trying to Return from a Sub raises a "internal error"

To Reproduce Create the code:

    Public Sub Duh()
        Return
    End Sub

It will highlight Return as an error.

Expected behavior No error. It should be legal to return. Had I put in something to return (e.g. Return 1 or Return "foo"), then it's appropriate to show an error since it's a Sub.

BTW, I prefer this over Exit Sub because this avoids the need to change the code should it change from Sub to Function (or vice versa).

Desktop (please complete the following information):

EduardoVB commented 2 years ago

"Return", I understand means to return some value (in a Function or Property Get procedure).

But it could also be understood as "return to the calling procedure", although that meaning seems a bit forced IMO.

bclothier commented 2 years ago

This is prior arts in several other languages. In fact, there's prior art in VBx with its GoSub...Return misfeature.

DaveInCaz commented 2 years ago

This is prior arts in several other languages. In fact, there's prior art in VBx with its GoSub...Return misfeature.

Total aside, but in VB6 I often use Gosub...Return as a substitute for a finally block.

Something like...

On Error Goto handler
...

'Work is done
Gosub cleanup
Exit Sub

cleanup:
    'release resources or whatever is needed
    Return

handler:
   Gosub cleanup
   DoStandardErorrHandling(...)

End Sub
EduardoVB commented 2 years ago

This is prior arts in several other languages. In fact, there's prior art in VBx with its GoSub...Return misfeature.

I always understood the Return of Gosub as a "return to the calling line+1" but the Return of procedures that need to return a value as something different, as "return this value and exit". But may be I misunderstood.

WaynePhillipsEA commented 2 years ago

The problem with this request is indeed the conflict with the legacy GoSub... Return syntax. My original plan was to allow Return inside of Subs, but only if no GoSub had been encountered in the procedure, which would allow for full backwards compatibility when we implement the GoSub...Return syntax.

However, just Return in a procedure (without GoSub) in VBx produces a runtime error, not a compile error, so from a technical perspective we can't guarantee that nobody is relying on that behaviour (although it would be extremely unlikely, and very bad code).

Kr00l commented 2 years ago

My two cents: Prior art is a good argument. However, like with the Class Finalize there are restrictions. So here the same with GoSub...Return. I would disallow Return on a Sub that has no GoSub. Also to avoid confusion or misinterpretation at the end.

Exit Sub is just fine then. It's a one liner.

With Exit Function we can't return a value without having two lines. But that's not the case for Exit Sub.

bclothier commented 2 years ago

It's probably a runtime error because there is no require to balance the Return to the GoSub. Also contrary to what the documentation says, one can actually leave in middle of a GoSub...Return. it is possible to make the Return conditional based on execution, which is frankly an insane way to code but there it is. It's also likely that VBx didn't have the capability to do code analysis to determine the execution path and thus statically prevent Return without GoSub. This code demonstrate that we can have unbalanced Return which may then behave differently depending on which execution path is taken:

Public Sub Maybe(N As Long)
    If N < 1 Or N > 3 Then GoSub Leave

    On N GoSub MakeAChoice, BailOut, Leave

MakeAChoice:
    If N > 0 Then
BailOut:
        Return
    Else
Leave:
        Exit Sub
    End If
End Sub

Given that a GoSub and Return must both exist together within a procedure for it, it seems dubious that anyone would put in a Return in a Sub routine with no GoSub and be content to get RTE3. Does it make sense to put in an additional check for whether there's any error handling for RTE3 anywhere in the project? If there aren't, then isn't that a strong indication that the use of Return was incorrect? I don't remember what happens if there's an unhandled error in a compiled VB6 application -- wouldn't it simply abort?

wqweto commented 2 years ago

GoSub/Return statements force VB6 codegen to generate a hidden local variable to keep track of "gosub list/call-stack" and there is ___vbaGosubFree call in routine prolog to deallocate this data structure no matter if it's empty or still contains outstanding GoSub calls that never Returned.

I don't remember what happens if there's an unhandled error in a compiled VB6 application -- wouldn't it simply abort?

Pretty much the same as in .Net -- the exception is handled by parent try/catch block a.k.a "On Error GoTo Label" (if any) or if unhandled at all a run-time msgbox is displayed with the unhandled error description before aborting the process.

EduardoVB commented 2 years ago

Does it make sense to put in an additional check for whether there's any error handling for RTE3 anywhere in the project? If there aren't, then isn't that a strong indication that the use of Return was incorrect?

I think that rules should be as simple as possible. "If there is a Return, but there is no Gosub then the Return means something else..." That can cause confusion. I think enough is to have the same keyword ("Return") with already two completely different meanings (Return from Gosub and Return a value in a procedure) IMO.

EduardoVB commented 2 years ago

BTW, I think I recall there was a comment from Wayne about the new Return keyword couldn't be allowed if there was a Gosub in the same procedure (not sure if I'm right because I can't find that message now here, may be it was in Vbforums).

Since the new Return keyword needs to be followed by an expression, like Return thisValue and the old Return for Gosub can't be followed by an expresion (it causes a compilation error), I think both Return keywords can be allowed in the same procedure taking that into account.

bclothier commented 2 years ago

Pretty much the same as in .Net -- the exception is handled by parent try/catch block a.k.a "On Error GoTo Label" (if any) or if unhandled at all a run-time msgbox is displayed with the unhandled error description before aborting the process.

My point is that if the RTE3 goes unhandled (whether specifically or generally under catch-all handler), then we know that the program would have crashed and therefore was not written correctly in the first place. Or as C guys like to call it, "undefined behavior". 😄 Somewhere else we discussed that backward compatibility can stop at the point where code starts crashing. If unhandled RTE3 results in an abort where the execution is terminated, that's also where the backward compatibility can stop. Unfortunately to support that, this requires code path analysis to statically determine whether RTE3 is possible. That can get complicated when branching or jumping are involved.

Since the new Return keyword needs to be followed by an expression, like Return thisValue and the old Return for Gosub can't be followed by an expresion (it causes a compilation error), I think both Return keywords can be allowed in the same procedure taking that into account.

While this is technically possible, I think the 2 different uses within the same procedure would be quite jarring and confusing. Following the principle of least astonishment, I'd prefer that the Return had single behavior.

The irony here is that the GoSub...Return misfeature exists solely as a backward compatibility feature to port pre-VBx code into VBx but even so it failed abysmally in this regards because in the BASIC, you didn't have procedures at all. The GoSub...Return were basically a bastardized form of procedures. However, in VBx, you cannot write code outside of procedure blocks. Therefore to port the BASIC code, you had to least slap on a Private Sub BigOldUglyProcedure() and End Sub. Not quite 100% backward compatibility. Unfortunately because it exists, it has came to be used in other ways beyond what it originally was used for within BASIC.

EduardoVB commented 2 years ago

About the first point, you can write this legal code in VB6:

Private Sub Form_Load()
    On Error Resume Next
    Return
    Label1.Caption = Err.Description
End Sub

Why someone would do that I don't know, but it is possible.

About GoSub, you must be right about its origin (and how it came into VB6), but I do not agree with most people that think that it is intrinsically evil (or something like that). I serves sometimes the function of an anonymous function within a procedure. When you have a lot of local variables and realize that you need to repeat some action that need those variables, it is very cumbersome to convert the code to put that part in another procedure, and it is handy just to add a GoSub... and keep working. I try to avoid it, but I'm pragmatic and not idealistic, I did it sometimes and have no problem defending it.

bclothier commented 2 years ago

When you have a lot of local variables and realize that you need to repeat some action that need those variables, it is very cumbersome to convert the code to put that part in another procedure, and it is handy just to add a GoSub... and keep working.

See, that's why we want to make sure it's easy to refactor the code and the ability to refactor code is heavily dependent on whether the refactoring can be done without changing the behavior with the code. With GoTo and its associated cousins, it is nearly impossible to refactor without changing the behavior.

In a modern language & IDE, this would be easily done by doing a "Extract Method" refactoring which also take care of moving variables or converting into a parameter. But with a GoSub in the picture, additional analysis is needed to ensure that code can be safely refactored. The consequence of this is that we have a codebase that is impossible to fix, much less maintain let alone enhance. That's A Very Bad Thingâ„¢ and it makes the technical debt much more expensive to pay off.

We do not want tB to fall in the same position that VB* codebase has where people look at it, and say "Forget it. Let's just rewrite the whole thing from scratch" which is actually another mistake. The desire to rewrite rather than refactor will stem from the fact that you can't reliably refactor code with elements similar to GoTo and its cousins (not to mention other misfeatures from BASIC days). A language that is easy to refactor makes it much easier to be pragmatic and not get mixed up in "oh, I should not have written it like that. I guess I'm stuck with it for next 20 years." tarpits that VBx codebase invariably falls into.

EduardoVB commented 2 years ago

I see your point on refactoring, and I agree.

Well, GoSub seems at first glance easier to refactor, you need to look for the last Return and that's the End Sub, and others Returns in the middle are Exit Sub.

GoTo seems impossible, a GoTo can jump anywhere.

I also defend the occasional use of GoTos in VB6, because sometimes you need, for example, to do this: You need to clear up some things before leaving the procedure, some API handles or whatever. And you have several Exit Function in the middle, for example because some error occurred. What I sometimes did, instead of repeating the clear-up code, is to replace all those Exit Sub with GoTo Exit_Sub, like this:

Private Sub Foo()
    ' Set some handles or whatever that need to be destroyed after use

    ' code

    If Condition1 Then
        GoTo Exit_Sub
    End If

    ' code

    If Condition2 Then
        GoTo Exit_Sub
    End If

    ' code

    If Condition3 Then
        GoTo Exit_Sub
    End If

    ' code
Exit_Sub

 ' Clear handles

End Sub
WaynePhillipsEA commented 2 years ago

For continuity reasons I believe we should strive to get this Return syntax allowed in Subs. The runtime error of the old syntax is a consideration, but this is largely mitigated by us simply disabling the new Return syntax when the legacy Gosub is encountered within a procedure.

That just leaves the nonsensical use of Return, with it throwing a runtime error in the old syntax without any Gosub appearing in the procedure... and I feel that supporting that 'feature' would be taking the 100% compatibility goal too literally here, without considering the bigger picture.

mburns08109 commented 2 years ago

LoL

About GoSub, you must be right about its origin (and how it came into VB6), but I do not agree with most people that think that it is intrinsically evil (or something like that).

Well, if you or I as an adult, were to pick up a baby rattle and start playing with it, giggling like a pre-toddler again, I wouldn't call that intrinsically evil either; but any reasonable observer of such behavior would quickly conclude that there is some sort of significant maturity issue at play with us for doing so.

Supreme Court Justice Robert H. Jackson first used the "suicide pact" phrase in comparison to the US Constitution, and I begin to wonder at this discussion if we're not edging towards being able to use that phrase in comparison to the concept of "100% Backwards Compatibility". Perhaps we should clarify a little on the question of "How much Backwards Compatibility is 'Too Much' backwards Compatibility?" Otherwise we risk condemning tB to re-commit the sins of the past when a significant part of our hope for this was some more progress towards the future? ...to say nothing of the relative effort involved in getting every jot and tiddle "just like before" -vs- the opportunity cost of not making other important progress.

DaveInCaz commented 2 years ago

@mburns08109 I thought the point was so that pure VB6 code would compile and run in tB as-is. To me (someone who maintains a lot of old VB6 code - and there are many of us) that would be extremely valuable.

From other posts I had the impression that tB would also offer some set of selectable options to gradually move away from the legacy limitations of VB6. A separate mode where gosub / return is illegal or whatever.

mburns08109 commented 2 years ago

I was kind-of hinting more towards what Ben was saying about refactorability -vs- trash-and-redo, and hint that if "backwards compatibility" questions don't get examined in the light of what he was saying, then perhaps we're edging more towards the "suicide pact" side of things than we should really be comfortable with (as opposed to offering any concrete advice about whether gosub-less Return statements should be either supported, treated with the same runtime error that VB6/VBA do, or treated as compiler errors).

I guess what I'm trying to say is: when is it okay to leave some old things on the trash heap of history -vs- assuring that every memory leak is repeated for the sake of compatibility? Could this gosub-less return topic be one of those issues?

wqweto commented 2 years ago

Could this gosub-less return topic be one of those issues?

Only if it's leaking memory :-)) Are we repeating a memory leak here indeed or I'm missing the hyperbole?

Btw, something innocuous as adding new keywords to the language breaks compatibility too.

For instance TB added Continue statement as a convenience in loops but it broke VBx code like GoTo Continue which cannot be compiled by TB as is, unless the compiler is switched to some compatibility mode.

EduardoVB commented 2 years ago

Well, if you or I as an adult, were to pick up a baby rattle and start playing with it, giggling like a pre-toddler again, I wouldn't call that intrinsically evil either; but any reasonable observer of such behavior would quickly conclude that there is some sort of significant maturity issue at play with us for doing so.

There are two ways of following rules 1) when you understand the reason behind the rule, 2) when you don't understand it.

When it comes to God's rules, you need to follow them for your own good, better if you understand, and if you don't understand, better for you to follow the rules anyway because God is an Authority and He knows why even if you don't (in your tiny human mind).

When it comes to men rules, there are laws, you are obliged to obey the laws whether you understand the reasons behind them or not. And the government is also an authority that enforce the laws.

When it comes to other rules, that more than rules are common beliefs, or traditions, you can also obey 1) if you understand, and 2) if you don't understand why that rule is (and perhaps where it comes from).

Many people believe something because many other people does, or prominent people say so.

When it comes to "never, ever using GoTo", what do you think? Options: 1) You think it is a sin. 2) You think it is against the law. 3) You think someone said so (perhaps a prominent person) and many other believed. But you agree with that anyway because you understand the matter. 4) Same as 2) but you don't understand the matter.

When you understand the issue, you can agree or disagree (hence to obey or not obey, or just obey in part, according to what you understand that is more convenient), but if you don't understand it, you can only obey or disobey by blind faith (and perhaps criticize other that don't follow the rule that you follow -but don't understand-)

If you think that someone using occasionally GoTo in VB6 (I'm not talking about other languages) is like playing with a baby rattle, think what you want. It is out of everyone else's control what you think.

mburns08109 commented 2 years ago

It's not really what I think that's the issue that should concern you (which is partly why I didn't voice a direct opinion on the options). It's the question of how you our your successors will view the code when looking at it from a future perspective - especially if, as Ben hinted at, the question of the day at that point will be "refit/repair/refactor this?" - or "nuke it from orbit and start over?" (the extra Hyperbole is included free of charge.)

EduardoVB commented 2 years ago

It's not really what I think that's the issue that should concern you (which is partly why I didn't voice a direct opinion on the options). It's the question of how you our your successors will view the code when looking at it from a future perspective - especially if, as Ben hinted at, the question of the day at that point will be "refit/repair/refactor this?" - or "nuke it from orbit and start over?" (the extra Hyperbole is included free of charge.)

There are already many millions of VB6 code lines already, you cannot change that fact.

If tB adds anonymous functions, GoSub, and possibly GoTo too, won't be justified anymore and nobody should use that in new code.

When the programmers writes a GoSub or GoTo, tB will issue a warning, that should be enough. And if someone wants to use GoTo anyway, what's your (or anybody else) problem? It is his choice, it does nothing to you.

It looks to me like the vaccinated complaining because some others don't want to take the vaccine.

mwolfe02 commented 2 years ago

Perhaps we should clarify a little on the question of "How much Backwards Compatibility is 'Too Much' backwards Compatibility?"

When you're trying to provide 100% backward compatibility, then there is no such thing as "Too Much" backwards compatibility.

I understand the point you are trying to make, but the difference between 100% compatible and 99% compatible is WAY MORE than 1% when it comes to developer migration effort. Microsoft understood this when they created Excel. Anything that prevents existing VBx code from running in twinBASIC will cause many developers that are tB-curious to simply throw up their hands and think, "just another failed VBx clone."

When you're trying to gain market share as an upstart, you need a product that Just Worksâ„¢.

Now, I think it's reasonable to stop at 99.999% compatibility if the final 0.001% amounts to actual implementation bugs in VBx AND there is a project-wide "Quirks Mode" flag that will recreate those old bugs for the developers that relied on them for whatever reason. But GoSub does not fall into that category.

mwolfe02 commented 2 years ago

The way to encourage better programming practices moving forward is to provide an opt-in mode that disables language features like GoSub. The community can then encourage usage of that as a best practice the way that Option Explicit is encouraged as a best practice in VBx now.

mansellan commented 1 year ago

For continuity reasons I believe we should strive to get this Return syntax allowed in Subs. The runtime error of the old syntax is a consideration, but this is largely mitigated by us simply disabling the new Return syntax when the legacy Gosub is encountered within a procedure.

That just leaves the nonsensical use of Return, with it throwing a runtime error in the old syntax without any Gosub appearing in the procedure... and I feel that supporting that 'feature' would be taking the 100% compatibility goal too literally here, without considering the bigger picture.

Bump.

A Return outside of a Gosub block, then catching the runtime error that is certain to result is... bizarre?

I feel like this could easily be accommodated by an "absolute" project switch for legacy compatibility. Almost nobody would be caught by this, and a project switch could keep the 100% promise.

bclothier commented 1 year ago

Just wanted to put out a different approach:

Private Sub Foo()
  Return Default
End Sub

Private Function Bar() As Long
  Return Default ' Returns 0
End Function

Private Function Fizz() As String
  Return Default 'Returns vbNullString
End Function

Private Function Buzz() As Variant
  Return Default 'Returns vbEmpty
End Function

The Return Default is a illegal statement in VBx so there is no backward compatibility issues, and has the added bonus of simplifying the refactoring (e.g. changing a Sub into a Function and still getting a default value).

fafalone commented 1 year ago

Need to be really conservative with taking over keywords... Default isn't exactly an uncommon name so you might be creating issues with Function Default() for a lot of people.

bclothier commented 1 year ago

Hmm, Return Default is a syntax error so the problem would only exist in a tB project, rather than importing from legacy VBx projects. A warning could be issued if there's an ambiguity, though one might question whether it should be necessary in the first place. If we don't want the potential of an ambiguity, Return As Default would work, too if a bit more wordy.

fafalone commented 1 year ago

Default is a legal function name in VBx; that's where I see the problem arising.

But I think so long as there's no GoSub, it shouldn't be needed to have anything besides Return at all.

Another possibility I was thinking about... do we even need a toggle based on the presence of GoSub? Couldn't the two be integrated by block?

Sub foo()
someCode
GoSub bar
Return
bar:
subCode
Return
End Sub

The Return at the bottom would be considered within a GoSub block, so it triggers an exit to the next higher level. The Return up top also returns to the next higher level-- exiting the sub entirely.

bclothier commented 1 year ago

The issue is that in VBx, that's a runtime error 3 Return without GoSub. If there's VBx code that rely on this behavior, it would stop working and thus not be 100% backward compatible. However, I do agree with mansellan's earlier remark that it's bizarre. Just want to avoid the need to have a switch or anything like that.

That said, if we did go with just Return, I wouldn't shed a tear over the change in behavior WRT the error 3.

fafalone commented 1 year ago

AFAIK Wayne has said it's been decided to not worry about compatibility with illegal syntax... that would be handcuffing the language for what's a theoretical benefit (I've never seen VB code rely on syntax errors). tB already allows AddressOf where VBx doesn't... that shouldn't be rolled back just on the off chance someone somewhere may have written a routine that relies on triggering that error.

If tB was taking backwards compatibility that far, there's things way higher on the compatibility list that aren't merely theoretical... when it's time to start advertising tB to the general public, there's going to be a need for an asterisk and explainer of what "100% compatibility" means, because there's a lot of code out there which will never run in tB because it's using elaborate hacks based on internal VB structures and undocumented runtime APIs. So there's already a limit here... and bizarre situations of people using illegal syntax to deliberately trigger errors is far less of an issue.

bclothier commented 1 year ago

Nitpick: it's not illegal syntax. It's legal syntax that results in a runtime error.

Greedquest commented 1 year ago

What if Return on a Sub did something useful like set the HResult. Return 0 for success, same as Exit Sub Return N for failure

Enforcing an integer return code in a Return statement (or an expression that evaluates to one) would prevent syntax clash I think.

I could see this being handy for console applications, perhaps if the last HResult corresponded to the console app exit code.

FullValueRider commented 1 year ago

With some minor tweaks you can do this already. Define a UDT as type Void. You can now change all your subs to functions which return type Void, and use Return Void in the method. Its a little bit of extra work but is quite explicit. Subs remain available for when you have such time critical code that the small overhead of a function can't be tolerated.

mansellan commented 1 year ago

I quite like Return Default tbh. It solves the problem quite neatly, and (in future) being able to assign Default explicitly could come in quite useful for generics (at least, it does in .net).

wqweto commented 1 year ago

Btw, for functions the proposed Return Default behavior can almost be emulated by Return Empty (not sure about reference types on retval). Another option is to allow Return Empty on subs.

mburns08109 commented 1 year ago

Just to throw some conversational gasoline on this fire, am I mis-remembering, or was there not a BASIC language implementation (or two...) that permitted flow-control logic like:

...
GoSub Routine1
Label1:
...
GoSub Routine2
Label2:
...
End

Routine1:
...
If x then Return Else Return Label2

Routine2:
...
If x then return label2 else return Label1

...and if that's correct, will we need to accommodate that logic as well (since we're talking language specifications here)?

mburns08109 commented 1 year ago

As for "Return Default" and "Return Empty" Well, We'll need to add "Return Nothing" to the list. Or, how about "Return (literal value here}" too? I know! Let's add "Return WhateverTheHeckYouWant" while we're at it too! ("Hyperbole Alert!" - for those who need the tool-tip)

What's wrong with allowing the programmers to decide explicitly what needs to be returned in ANY programming circumstance and MAKE THEM EXPLICITLY DO IT!?

Why are we trying to make these things fit into a specification for a language that was first meant for use as a TEACHING TOOL - and NOT to be the "Be-All-End-All Best-est Language Uber Alles!!" (as we seem to be drifting towards)?

Let's not make this into a specification for a language that is now so confusing that BEGINNERS nave NO HOPE of understanding it in a teaching scenario. We're STILL supposed to be an ENTRY POINT language for new students (or also, for professionals from other fields who now have a need for a minor skill in programming too).

I'll also toss into this part of the discussion: KISS principles still apply here, do they not? Also: Any idiot can make things more complicated - but it can take real genius to make things simpler. Can we take a breath and figure out which of those we're applying more in our efforts, here?

Can I get you all to add a new filter into your logical matrices for deciding to suggest new things, please? Here's the new question to evaluate: "Will this idea help clarify or confuse things for the new student to the language?" If the answer isn't an easy "clarify", then perhaps you need to rethink the suggestion further.

fafalone commented 1 year ago

Having Return be legal in functions but not in subs does make things more confusing for beginners. So we do need syntax for allowing it in subs. I would agree that if at all practical that should just be a plain old 'Return', but the others were just suggesting possible alternatives if it's too impractical to resolve the issue with GoSub/Return (I don't think it is, and believe my comment above outlines a way to handle syntax that would even allow mixed use).

I don't think that if Wayne determines we do need an extra keyword in Sub we should use ones that already have very specific meanings in this context... i.e. they're currently used in Function Return syntax:

Function foo() As IUnknown
    Return Nothing
End Function

Function bar() As Variant
    Return Empty
End Function

I agree that would raise an issue with reducing clarity.

(Your other comment, Return <label> isn't allowed in VB6 and the last thing we need to be doing is encouraging more use of this mistake by expanding it's features... maybe we should talk about a compiler warning similar to the one for DefType).

mburns08109 commented 1 year ago

(Your other comment, Return

Just to be clear, I was not advocating FOR that capability. Nor did I say it was a VB6 feature. (I was trying to call it out as a possible tripping hazard, in terms of the tB language spec., at best). I just dimly recall being able to so things LIKE that in BASIC-variants of the past.

mburns08109 commented 1 year ago

Having Return be legal in functions but not in subs does make things more confusing for beginners.

I disagree - that is entirely consistent with their basic definitions: a Function returns a value. A Sub does not. That is simple and clear.

What is less simple and clear is the semantics of allowing Return (as a flow control element) into Subs and having the same Return keyword mean something different in a Function (return a value as well as flow-control) -vs- Return for a GoSub again being only a flow-control keyword.

THAT is more confusing than the well-established (function name here) = (return value expression) syntax you all suddenly seem to abhor even though it is quintessential VBx syntax.

To me it would make more sense to treat all procedures as if they were virtual objects(classes) with properties, and THEN (SubOrFunctionNameHere).RetVal = (return value expression) (OR Set (SubOrFunctionNameHere).RetVal = (return value expression) if you will) (perhaps this is where the This-as-a-keyword for the current "virtual function object instance"...would at least make sense, permitting This.Retval = (whatever). (I would maintain that for SUBs, RetVal (as used here) does nothing (they don't return values to keep semantic backwards consistency) - even if the syntax would be otherwise permissible.

Then the "return this value from the current procedure" semantic would be separated entirely from the flow-control logic. Now THAT would keep things clearer IMO.

fafalone commented 1 year ago

Why do you think having two definitions of Return improves clarity?

GoSub/Return... Go SUB, RETURN (from SUB) without returning a value. The VB6 context of "Return" already defines it as 'return from' rather than 'return this', a statement of pure flow-control.

Adding on something after 'Return' to signify return with that value already marks a difference without any harm to clarity. Plus it's water under the bridge, I seriously doubt Return is going away in functions. You're proposing two different meanings for a free-standing Return. "When you use Return with GoSub, it's pure flow control logic. But if you use Return outside of GoSub, it's both flow control logic in functions and assignment logic in functions, but in Subs without GoSub, it's purely assignment logic, so because there's nothing to assign you're not allowed to use it. " This doesn't appear to be clear at all, it's extremely confusing how you're drawing this line that Return returns from a GoSub, and Returns from a function with the functions value after it, but doesn't mean you can return from a sub.

mburns08109 commented 1 year ago

One other point, We all despise the Def(type) = (whatever) elements of the VBx language, right?

Well, your Return Default would tend to need a variation of those, like: DefVarType = (one of: vbString, vbLong, vbLongLong, etc.)

But instead of saying what variable types match which first-letter-in-the-variable-name that the Def(Type)s do tells the compiler what the Default SubType is used for all otherwise un-assigned Variants (once they are assigned a value, they adjust to the type of the value, of course as they do at present i.e. MyVar = "somestring" becomes variant(vbString) ).

This leaves open an interesting question about how those types may or may not change though. What should happen here:

DefVarType = vblonglong
Dim MyVar as variant
MyVar = 2

...Is MyVar a byte? Integer? Long? LongLong? or a single, Double, or Decimal?

All are logical possibilities, but I'd posit that Existing "Evil Type Coercion" rules should be adjusted in favor of the rule: If the assigned value to an uninitialized variant subject to a DefVarType statement keeps its DefVarType Subtype - IF the assigned value fits the type.

Meaning that MyVar in the above hypothetical example is a Variant(vbLongLong) with a value of 2.

...or should DefVarType be applied as a method attribute? That might make more sense than a module-wide or program-wide statement would.

fafalone commented 1 year ago

Oh yes we can definitely agree on Def[Type] being bad.

But that's not valid Def__ syntax at all... there's DefVar but it's followed by a letter, not a type. MyVar is a Variant, because you've explicitly defined it, and follows the normal rules for whether it's VT_UI1, VT_I2, or VT_I4.

mburns08109 commented 1 year ago

fafalone,

Don't confuse/conflate DefVar with my proposal for a DefVarTYPE. (Perhaps "DefVarSubType" would be clearer?)

fafalone commented 1 year ago

Oh ok I see. Yes it could be useful to assign a default type... but I'd suggest we'd also then need syntax to change the type, and then there'd have to be a whole set of rules for when you assign something that doesn't fit. Seems to be getting into the weeds of the underlying VARIANT type in a way that goes against what you were saying about clarity and accessibility to beginners. I'm always up for more direct access to the lower levels though, easier than calling CopyMemory to overwrite the first two bytes storing the type.

mburns08109 commented 1 year ago

I think it would promote clarity in that the DefVarType vbLongLong would allow removal of the mystery of how much memory is reserved for them (especially in terms of API calls to functions which will populate the variant on exit) - so going into the API call the variant may be unitialized, but the memory for it was "predeclared" and not a surprise in any way - may help with some "As Any" parameters too?

mburns08109 commented 1 year ago

Well, with the c[Type]() functions (cInt(), clng(), etc.) We kind of have that control already, don't we? ...even if it's mostly implicit in terms of changing the variant subtypes. Or am I not grokking the effect of Evil Type Coercion as fully as I need to here?

fafalone commented 1 year ago

The memory reserved for a Variant is always the same, 16 bytes for 32bit, 24 bytes for 64bit.

FullValueRider commented 1 year ago

It may be that VBA provides a way out of the return in sub conundrum.

In some senses VBA is quite advanced in that it allows the name of a function to be used as a variable in the body of the function. i.e. It allows an implicit result variable

Consider the legal VBA below

Public Function Foo(ByRef Bar1 As Long, Bar2 As Long) As String

    Dim myDefault As String
    myDefault = "abcdefghijklmnopqrst"

    If Bar1 < 1 Then

        Foo = myDefault
        exit for

    Else

        Foo = VBA.Mid(myDefault, Bar1, Bar2)

    End If

    If Mid$(Foo, 3, 1) = "d" Then

        Foo = VBA.UCase(Foo)

    End If

End Function

This code shows Foo being used as a function and as a variable.

Foo is the implicit return variable which has the same name as the Function name and which is the value returned at the end of the function or when an exit for is encountered.

The cognitive dissonance of this feature is quite high.

If however, @wayne could engineer an alias for Foo as a variable, e.g. result, then the above code could be written in a much clearer form.

Public Function Foo(ByRef Bar1 As Long, Bar2 As Long) As String

    Dim myDefault As String
    myDefault = "abcdefghijklmnopqrst"

    If Bar1 < 1 Then

        result = myDefault
        Exit Function
    Else

        result = VBA.Mid(myDefault, Bar1, Bar2)

    End If

    If Mid$(result, 3, 1) = "d" Then

        result = VBA.UCase(Foo)

    End If
    ' result is the value returned by foo
End Function

The introduction of the result alias would not replace 'Foo =', but provide an alternative expression of the intent so that both versions of the code above would be allowed.

To assist the use of 'result' the twinBasic ide should be enhanced to follow the lead of the VBA ide, such that changing a function to a property or sub should automatically rename the end statement and also any exit statements (but this is a good idea anyway which should be implemented independent of the above discussion).

This change would neatly eliminate the issue about the conflicting use of return, does not impact current code, and allows clearer expression of intent for future code.

I think the discission then evolves to how 'result' is implemented. Is it an addition to the twinbasic syntax, is it a toggleable using a global attribute/local attribute. My preference would be for a global attribute that can be overridden by a local attribute

Global attribute [AllowResult]

Local Attribute [Result = false]

or more interestingly [Result as Foo] for a method named foo

or even more interestingly [Result as Foo = "HelloWorld"] directs the compile to use the method name as the return value, using the default value of "Hello World"

[Result as FooBar = "HelloWorld" direct the compiler to use the variable Foobar as the return variable with the default value of "HelloWorld". Foobar would be the return type specified by the function, and no declartion of FooBar would be required in the function itself.

e.g.


<a global or module level option/attribute that allows/disallows function name aliasing>

' at the function level
[Result as FooBar = "HelloWorld"]
Public Function Foo(ByRef Bar1 As Long, Bar2 As Long) As String

    Dim myDefault As String
    myDefault = "abcdefghijklmnopqrst"

    If Bar1 < 1 Then

        Foobar = myDefault
        Exit Function
    Else

        FooBar = VBA.Mid(myDefault, Bar1, Bar2)

    End If

    If Mid$(result, 3, 1) = "d" Then

        FooBar = VBA.UCase(Foo)

    End If
    ' FooBar is the value returned by foo
End Function