Author Topic: Wiki wrong  (Read 6466 times)

0 Members and 1 Guest are viewing this topic.

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Wiki wrong
« on: July 24, 2019, 03:41:36 pm »
http://www.[abandoned, outdated and now likely malicious qb64 dot net website - don’t go there]/wiki/index_title_SUB.html

If you look at the description for SUB, it says, “SUB procedures can return multiple values through the parameters unlike functions.”

Functions can return multiple values back via parameters with no issues.  Something seems off with that little footnote.
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline Petr

  • Forum Resident
  • Posts: 1720
  • The best code is the DNA of the hops.
    • View Profile
Re: Wiki wrong
« Reply #1 on: July 24, 2019, 05:16:54 pm »
You are right, Steve. I write small test for it:

Code: QB64: [Select]
  1. T$ = GetTime$(H, M, S)
  2. PRINT T$, H, M, S
  3.  
  4. FUNCTION GetTime$ (hour, min, sec)
  5.     GetTime$ = TIME$
  6.     hour = VAL(LEFT$(TIME$, 2))
  7.     min = VAL(MID$(TIME$, 4, 2))
  8.     sec = VAL(RIGHT$(TIME$, 2))
  9.  


There's more of those inconsistencies. For other commands. For example, for SNDRAW, there is a warning that memory overload may occur. This is true, but only if there is a poorly designed program. When I look at memory consumption, it is even considerably lower than when playing WAV over SNDPLAYFILE, which first loads the entire WAV into RAM. So the message about memory full depends only on how the whole program is written.

Offline Qwerkey

  • Forum Resident
  • Posts: 755
    • View Profile
Re: Wiki wrong
« Reply #2 on: July 25, 2019, 07:42:36 am »
Well, up until this moment, I had thought that the whole idea of a FUNCTION was to return a single answer, but it does indeed pass back paranthetical parameters.  I'm disappointed, somehow!

Offline Petr

  • Forum Resident
  • Posts: 1720
  • The best code is the DNA of the hops.
    • View Profile
Re: Wiki wrong
« Reply #3 on: July 25, 2019, 10:02:08 am »
Quote
Well, up until this moment, I had thought that the whole idea of a FUNCTION was to return a single answer, but it does indeed pass back paranthetical parameters.  I'm disappointed, somehow!

You can return value also with SUB:

Code: QB64: [Select]
  1. ADD_UP answer, 50, 100
  2. PRINT answer
  3.  
  4. SUB ADD_UP (outpt, a, b)
  5.     outpt = a + b
  6.  

I think, the possibility of more results for better clarity is better to pass in the array.
« Last Edit: July 25, 2019, 10:09:04 am by Petr »

Offline freetrav

  • Newbie
  • Posts: 45
    • View Profile
Re: Wiki wrong
« Reply #4 on: July 25, 2019, 10:22:11 am »
I fundamentally do not have a problem with a procedure/subroutine or function modifying its arguments, but I think that where that is permitted, there needs to be some indication in the declaration that a parameter may be modified - for example, what would happen if in the SUB ADD_UP I had called it as "ADD_UP 1,2,answer"?

This is the difference between 'call by value' and 'call by reference'. In Pascal, call by reference is indicated by prefixing the parameter name with VAR, e.g., Procedure AddUp(var Answer:Integer; first, second: integer;); ...

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Wiki wrong
« Reply #5 on: July 25, 2019, 10:33:09 am »
QB64 always allows variables to pass by reference, as long as the TYPE matches.

Code: [Select]

X = 3
AddOne X
PRINT X

SUB AddOne (Var)
    Var = Var + 1
END SUB

In the above, the result will print 4, as the value passes back and forth through Var, since the types match.

Code: [Select]

X = 3
AddOne X
PRINT X

SUB AddOne (Var AS INTEGER)
    Var = Var + 1
END SUB

And here, we print 3.  X defaults to SINGLE, Var is an INTEGER, the values don’t interchange.

When types match, parameters pass values back and forth; otherwise they won’t.
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline freetrav

  • Newbie
  • Posts: 45
    • View Profile
Re: Wiki wrong
« Reply #6 on: July 25, 2019, 11:02:35 am »
Hmmm... I perceive possible issues with that. Consider:

SUB AddOne (Ecks AS INTEGER)
   Ecks = Ecks + 1
END SUB

SUB DIV2 (Ecks)
   Ecks = Ecks / 2.0
END SUB


If I call AddOne:

Q=3.5
AddOne Q

I would expect/want an error to be thrown, because Q can't be demoted to INTEGER. Ideally, this can (and should) be detected at compile time. If I call DIV2:

Q%=5
DIV2 Q

I would expect/want an error to be thrown, because the result of calling DIV2 can't be demoted to INTEGER (signalled by the % suffix to the varname). Ideally, this should issue a WARNING at compile time, because of the possible type mismatch; if the warning is ignored, the error should be thrown at run time. If I call either:

AddOne 3
DIV2 4

I would expect/want an error to be thrown, because the SUB is trying to modify a parameter that is not a variable. Ideally, the errors should be caught at compile time. This is why some sort of indicator (like Pascal's VAR parameters) should be used.

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Wiki wrong
« Reply #7 on: July 25, 2019, 11:55:58 am »
Hmmm... I perceive possible issues with that. Consider:

SUB AddOne (Ecks AS INTEGER)
   Ecks = Ecks + 1
END SUB

SUB DIV2 (Ecks)
   Ecks = Ecks / 2.0
END SUB


If I call AddOne:

Q=3.5
AddOne Q

I would expect/want an error to be thrown, because Q can't be demoted to INTEGER. Ideally, this can (and should) be detected at compile time.

Q would simply remain 3.5.  It's not an INTEGER, therefore the values don’t pass back and forth.  In the SUB, Ecks is an INTEGER, which would process as:

Ecks = Ecks + 1
Ecks = 3 + 1 (Since we passed 3.5 to an INTEGER, it truncates the value it uses for us)*
Ecks = 4

* (Or it might round up to a value of 4.  I’m not at the PC to test it ATM, and I don’t recall if we round or truncate when going from SINGLE to INTEGER types.)

Quote
If I call DIV2:

Q%=5
DIV2 Q

I would expect/want an error to be thrown, because the result of calling DIV2 can't be demoted to INTEGER (signalled by the % suffix to the varname). Ideally, this should issue a WARNING at compile time, because of the possible type mismatch; if the warning is ignored, the error should be thrown at run time.

Same thing here once again.  Since the types don’t match, the values are simply passed one way — TO the SUB — and not back from the sub.

Ecks = Ecks / 2.0
Ecks = 5 / 2
Ecks = 2.5 (and use this value for whatever you need with the rest of the sub)


If I call either:

[/quote]
AddOne 3
DIV2 4

I would expect/want an error to be thrown, because the SUB is trying to modify a parameter that is not a variable. Ideally, the errors should be caught at compile time. This is why some sort of indicator (like Pascal's VAR parameters) should be used.
[/quote]

Once again, these values are just passed TO the SUB and not back, since there’s no variable involved.

The *only* way to pass values by parameters is:

1) You must use a raw variable
2) The variable must match the corresponding variable type for the sub.

SUB Example (X AS INTEGER)
    X = X + 1
END SUB

If we call the sub with:

Example var% — the value of var% will pass both ways as the types match.

Example var## — the value of var## will only pass to the sub, not back.

Example var% + 3 — the value of var% + 3 only passes to the sub, not back.

Example (var%) — this one is glitchy and sometimes passes values and sometimes doesn’t.  I avoid it like the plague.



If one wants to be certain NOT to pass values back from a SUB/FUNCTION, be certain to make use of temp variables:

SUB ExampleNoPass (TempX AS INTEGER)
    X = TempX
    .... use X in SUB from here on and not TempX
END SUB

If you want to pass values back and forth, make certain to only pass single variables where types match.
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline Pete

  • Forum Resident
  • Posts: 2361
  • Cuz I sez so, varmint!
    • View Profile
Re: Wiki wrong
« Reply #8 on: July 25, 2019, 12:33:25 pm »
What I don't get about subs is TYPE is handled differently with SHARED than variables or strings. There is no reference to this in the Wiki about SUBs.

Code: QB64: [Select]
  1. main:
  2. WIDTH 160, 25
  3. PRINT "In the main we have: "; x$, y, pass.willnotpass, pass.wontpass
  4.  
  5. SUB a
  6.  
  7.     TYPE pass
  8.         wontpass AS INTEGER
  9.         willnotpass AS STRING
  10.     END TYPE
  11.  
  12.     SHARED x$
  13.     SHARED y
  14.     SHARED pass AS pass
  15.     x$ = "non-TYPE passes to subs and main"
  16.     y = 100
  17.     pass.willnotpass = "TYPE only passes to subs, not main"
  18.     pass.wontpass = 200
  19.     CALL b
  20.  
  21. SUB b
  22.     SHARED x$
  23.     SHARED y
  24.     SHARED pass AS pass
  25.     PRINT "In sub b we have: "; x$; y; " "; pass.willnotpass; pass.wontpass

If anyone has QBasic or QuickBASIC available please test this and see if it gives the same results as QB64. It may be legacy.

Pete
Want to learn how to write code on cave walls? https://www.tapatalk.com/groups/qbasic/qbasic-f1/

Offline freetrav

  • Newbie
  • Posts: 45
    • View Profile
Re: Wiki wrong
« Reply #9 on: July 25, 2019, 12:56:10 pm »
Steve, I understand how it actually works (your explanation); my concern is that this is "broken as designed" because the behavior is unpredictable unless you know the actual values being passed at any given call. I'm not asking for strong typing (a la Pascal) - but I should be able to determine what will happen on a call by inspection, without having to know what the actual value is at run time. If I have a sub

Code: QB64: [Select]
  1. SUB DoSomething(Ecks)
  2.    ...

and DoSomething expects Ecks to be a numeric type - integer, single, or double - and it tries to modify Ecks mathematically, there's nothing stopping me from writing the bogus code

Code: QB64: [Select]
  1. Em = "A String"
  2. DoSomething Em

Which should blow up in my face because I can't do math on "A String".

Arguably, it's my responsibility as codemonger to do input validation before passing data to subroutines/functions - but there's a difference between doing the basic bookkeeping of variable and parameter types versus validating e.g., that the input has only valid characters, matches a (regex) pattern, is within a specified numeric range, etc.

I contend that a compiler that doesn't do the bookkeeping has to be considered 'broken as designed'. Especially if I want to write libraries for general use, where I have NO control over what's passed in. Or use those created by others, which more often than not are poorly documented if documented at all.

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Wiki wrong
« Reply #10 on: July 25, 2019, 02:10:27 pm »
What I don't get about subs is TYPE is handled differently with SHARED than variables or strings. There is no reference to this in the Wiki about SUBs.

What you're seeing is what we've come to expect from any BASIC, Pete: Confusion over multiple variable types with the same name.

Take a look at the following, for example:

Code: [Select]
a = 3
Example
PRINT a

'And see the difference of the above, with what's below:

a$ = "Pete"
Example
PRINT a$


SUB Example
    SHARED a AS STRING
    a = "Hello World, " + a
END SUB

If you notice, SUB Example has a shared variable -- a.  The main program also has a variable named a -- but it's a SINGLE and not a STRING, such as we defined our sub to use...  We also have a$, which is the string variable a -- and it's the variable we're sharing information back and forth between the SUB and the main module with.

If we remove the second half of that code, the program STILL works as expected:
Code: [Select]
a = 3
Example
PRINT a

SUB Example
    SHARED a AS STRING
    a = "Hello World, " + a
END SUB

There's NO variable "a" in the code which is defined as a STRING, so even though we're changing it in the SUB, and sharing it in the main module, we're not doing a dang thing with it.   

Want proof?  Look at the following:
Code: [Select]
a = 3
Example
PRINT a

'And see the difference of the above, with what's below:

a$ = a$ + "Pete"
Example
PRINT a$


SUB Example
    SHARED a AS STRING
    a = "Hello World, " + a
END SUB

The first call to Example sets a$ to become "Hello World, " -- even though the main module hasn't had a single reference to a$ so far.  The second call to the sub adds another "Hello World, " to the front of that string, and then adds a "Pete" to the end of it.

The value *IS* being shared -- even if we're not interacting with it, in the main program.



And how does this relate to what you're saying with TYPE?

It's the same thing; though with a minute twist.

Code: [Select]
Foo.x = 3
Foo.y = 4

PRINT Foo.x, Foo.y

Now, What TYPE is Foo.x and Foo.y in the above??

Being undefined as anything, they're both SINGLE type variables.

Code: [Select]
Foo.x = 3
Foo.y = 4

PRINT Foo.x, Foo.y
Example
PRINT Foo.x, Foo.y



SUB Example
    TYPE FooType
        x AS INTEGER
        y AS INTEGER
    END TYPE
    SHARED Foo AS FooType
    Foo.x = Foo.x + 10
    Foo.y = Foo.y + 10
END SUB

And with the above, what TYPE is Foo.x and Foo.y?

In the main module, they're undefined, so they're *still* both SINGLE variables, while in the SUB, Foo is declared as a FooType and restricts them to being elements which belong to Foo.



IF you want to be certain that the information in Foo (from the SUB) is still there and available in the main module, look at the following example:

Code: QB64: [Select]
  1.  
  2. Foo.x = 3
  3. Foo.y = 4
  4.  
  5. PRINT Foo.x, Foo.y
  6. Example
  7. PRINT Foo.x, Foo.y
  8.  
  9. M = _MEM(O, 4) 'A memblock starting at the offset for Foo from the SUB, and holding the next 4 bytes.
  10. PRINT _MEMGET(M, M.OFFSET, INTEGER), 'The first 2 bytes -- Foo.x
  11. PRINT _MEMGET(M, M.OFFSET + 2, INTEGER) 'And the next 2 bytes -- Foo.y
  12.  
  13.  
  14. SUB Example
  15.     TYPE FooType
  16.         x AS INTEGER
  17.         y AS INTEGER
  18.     END TYPE
  19.     SHARED Foo AS FooType
  20.     O = _OFFSET(Foo) 'The offset as to where our SUB is storing Foo at in memory.
  21.     Foo.x = 123
  22.     Foo.y = 456
  23.  
  24. SUB Ex2
  25.     SHARED Foo AS FooType

Notice that Foo.x and Foo.y in the main module never change -- we don't define them as being anything special, so they default to both being SINGLE variables.  In the SUB, Foo is defined as being a FooType, so its values aren't going to match those of the SINGLEs in the main routine.  The values *ARE* shared with the main routine -- as we can prove by using _MEM to looking in their memory address manually -- but we don't have any Foo AS FooType to interact with them with.

It's the exact same thing which we'd see in the following:
Code: [Select]
a = 3
PRINT a

Example

PRINT a

SUB Example
    SHARED a AS INTEGER
    a = a + 10
END SUB

We print 3, and then we print 3 again.  We have an "a" in the main module, and we have an "a" in the SUB which we're sharing, but they're not the same variable -- they're not the same variable types at all!

If we want to actually share "a" in the SUB above, we'd need to have an INTEGER a to share it with:

Code: [Select]
a = 3
PRINT a

Example

PRINT a
COLOR 4
PRINT a%

SUB Example
    SHARED a AS INTEGER
    a = a + 10
END SUB



See what's going on with your TYPE?  It's the same process -- it *IS* sharing the values with your main module.  You're just not interacting with them, as your main module is using a different set of variables with the same name instead.
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Wiki wrong
« Reply #11 on: July 25, 2019, 02:21:52 pm »
Steve, I understand how it actually works (your explanation); my concern is that this is "broken as designed" because the behavior is unpredictable unless you know the actual values being passed at any given call.

The main reason it works as it does, is simply because that's how QB45 worked in the past, and QB64 was created in an attempt to maintain as much compatibility for that old code.  It's not exactly the way I would prefer to have it operate either, but it's what we have to work with, if we want to maintain compatibility.
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline Jack002

  • Forum Regular
  • Posts: 123
  • Boss, l wanna talk about arrays
    • View Profile
Re: Wiki wrong
« Reply #12 on: July 25, 2019, 03:40:45 pm »
Steve,
What you refer to is SCOPE. I used to know what it was decades ago. In at least one language you can/could define variables within a subroutine and once out of it, poof, all those values are gone now, you're out of the scope. In defining functions we had ways to send values into a function or subroutine and either not get them back out or get them back out depending on how you coded it. (I think I'm thinking of Pascal. Really tight type defintions and few ways to pass things from one type to another)

That reply up there, #10, its scary to think you can have two variables of two different types both named the same both pointing to the same place in memory, that can spell disaster. I don't ever want to have to untangle that mess. It seems you address it above, but I have to think that allowing this in QB64 is a bug. Its a door to a pandora's box you don't wanna open.  Is it all for the sake of backward compatibility? If so, the only possible cure is to name your variables well and dont' let yourself fall into this trap. *shrug*
QB64 is the best!

Offline Pete

  • Forum Resident
  • Posts: 2361
  • Cuz I sez so, varmint!
    • View Profile
Re: Wiki wrong
« Reply #13 on: July 25, 2019, 03:48:14 pm »
I had to play around with some code for a bit, but I see what QB is doing now. Like you said, LONG is the default, so the variables that are undefined get passed back to the main. A default sting also gets passed back to the main, but once a TYPE is declared, that string is no longer an undefined string. The same is true if you do something like: SHARED mystring as STRING

By defining all the variable, you can successfully not pass sub variables back to the main.

Here is the code I used to work this out...

Code: QB64: [Select]
  1. 'DEFINT A-Z
  2. main:
  3. WIDTH 160, 25
  4. PRINT "Dim String = "; pete
  5. PRINT "main results:"
  6. PRINT "non type variable ="; nontype_y
  7. PRINT "type variable ="; pass_long.y
  8. PRINT "non type string = "; nontypestring$
  9. PRINT "type string = "; pass_string.mystring
  10.  
  11. SUB a
  12.     TYPE pass_string
  13.         mystring AS STRING
  14.     END TYPE
  15.  
  16.     TYPE pass_long
  17.         y AS LONG
  18.     END TYPE
  19.  
  20.     SHARED pete AS STRING
  21.     SHARED nontype_y ''AS LONG
  22.     SHARED pass_long AS pass_long
  23.     SHARED nontypestring$
  24.     SHARED pass_string AS pass_string
  25.  
  26.     pete = "Pete"
  27.     nontype_y = 100
  28.     pass_long.y = 200
  29.     pass_string.mystring = "type string"
  30.     nontypestring$ = "non type string"
  31.  
  32.     CALL b
  33.  
  34. SUB b
  35.     SHARED pete AS STRING
  36.     SHARED nontype_y ''AS LONG
  37.     SHARED pass_long AS pass_long
  38.     SHARED nontypestring$
  39.     SHARED pass_string AS pass_string
  40.  
  41.     PRINT "Sub b ressults:"
  42.     PRINT "Dim String = "; pete
  43.     PRINT "non type variable ="; nontype_y
  44.     PRINT "type variable ="; pass_long.y
  45.     PRINT "non type string = "; nontypestring$
  46.     PRINT "type string = "; pass_string.mystring
  47.     PRINT
  48.  

Pete

 
Want to learn how to write code on cave walls? https://www.tapatalk.com/groups/qbasic/qbasic-f1/

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Wiki wrong
« Reply #14 on: July 25, 2019, 06:07:05 pm »
Quote
Like you said, LONG is the default

SINGLE is the default.

X = 3.24
PRINT X

The above prints 3.24, not 3.
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!