Τετάρτη 23 Νοεμβρίου 2022

About Types in M2000

A new revision uploaded see github.

There are two different aspects for types:  Those types on variables, and those types on parameters. You may thing that a parameter is like a variable. Indeed a parameter is a variable, but these, parameter and variable, have something not common, the type. A variable declared somewhere in the program and maybe passed as an argument for a function, module, or a subroutine, by default using the By Value pass. So the parameter of a function, module or subroutine, get the value of the argument, but what about the type?

A parameter get the value from a stack of values. An argument written to the stack of values, we can say the current stack. The argument get the value from an expression (this is the By Value pass), so passing only a variable is like passing the result of an expression with only a variable. For M2000 there are two major expressions: Numeric and Alphanumeric (String Expression). Boolean expressions consider Numeric (0 for false non zero for true).

For a function parameter, interpreter see the name of the parameter, and if ends to $, then expect to find string value in stack else an error raised. So according to major types, numeric and string we  have a first check, before checking the value.

For a function a(x,y) we can't pass strings as arguments. Although we can do this a="11234.5" and the interpreter do that a=Val("11234.5"), and pass the value 11234.5 to a, we can't pass the "11345.5" where we expect to get a numeric value.

How many numeric types used in M2000?

We can use integer, long, simple, double, currency, decimal and boolean (exist as type, and that type returns a comparison)

How we can force a type for a variable?

By default 1 is a double literal number, is same like 1.0.  A statement like A=10 when A not defined as local previous in the module, make A as double type and this can't change with assigning an new value. So A=10& give to A a double type 10 and not the long type 10&, if A has type double.

We can use Def statement to define types for variables before the first assignment.

Def integer K=100, L=4

make K and L integer type (16bit) with values 100% and 4% (% used for integer literal numbers). M2000 check overflow for variables and return error.

If the Def find K or L isn't new names (in local scope) then an error raised. Def defines variables always local and not exist before as local. We can't use Def to shadowing variables.

We can use this form also:

Def K as integer=100, L as integer=4

In a subroutine we can't use Def because we want something else for shadowing a local variable with a new one with same name.

Local K=100%, L=4%

or

Local K as integer=100, L as integer=4

Every time we use Local from above a new set of K and L shadowing any K and L (if any K and L exist before as local variables, or global variables too). So when we call a subroutine, we have same scope as the caller, but we can use Local variables, and at the exit from routine any variable we define erased, so any K or L which we shadow are available to the caller. Local also define local arrays too

We don't use Local in the main body of a Module or Function (except for simple functions which are like subroutines, with the code written at the end of module's code), because these define a new NameSpace, and any new variable/definition by default is Local. We can use Global to make at some point a global variable or array, and the life for that is the same as for local variables.

Parameter Types

Lets see the program bellow. Function A need two arguments, and the parameter list has two, the x and z, one of them is Long type (32 bit) and the other is Integer (16 bit). So what we get; It depends. Here we pass 2 and 3 (double type) which convert to long and integer, and then we have the return value as the multiplication of those two. We read the type from the variable M. The M variable is new variable so the type for it is the type of the return value from function, and here is Long (long*Integer return long or error). The second try, we pass a big value 300000 which can't fit to Integer and we get Overflow error.

Function Alfa(x as long, z as integer) {
=x*z
}
M=Alfa(2, 3)
Print type$(M)="Long"
try ok {
M=Alfa(2, 300000)
}
Print Error$=" Overflow in function ALFA()"


Now we change the example. We remove the types from the function signature and we make a SayType module. We want that module, because we use M only for the call, and at the end of module's run the M erased, so next time can be another type. So  We pass to Alfa() numeric literals. The first two have double type, the next two have Currency type, and the last two have Decimal type.

Use 2~, 3~ for single type (4byte float).

Module SayType (M) {

	Print type$(M)
Print M
}
Function Alfa(x, z) {
=x*z
}
SayType Alfa(2, 3)
SayType Alfa(2, 300000)
SayType Alfa(2#, 3#)
SayType Alfa(2#, 300000#)
SayType Alfa(2@, 3@)
SayType Alfa(2@, 300000@)

Hexadecimal values are two types: 0xFFFF (65535) and 0xFFFFFFFF (4294967295) are currency type (map to unsigned), 0xFFFF% is Integer signed (-1) and 0xFFFFFFFFF& is long signed  (-1). So there is no type unsigned long, but we can use a double, or  decimal or a currency type. The Binary.And() and other functions return Currency type but they work on 32bit unsinged long. The Binary.add() has no overflow, the overflow bit just not used,

So now we know that using types make functions to have problems with overflow. This isn't bad if we know what to do. Because the M2000 Environment (Interpreter plus internal objects) written for education and playing by programming for kids and pupils, using only the major types Numeric and String we can make the life easier.

There are three type of  identifiers for variables. like A for numeric (and pointers to objects), like A$ for strings and some kind of objects (including Document), and like A% for  numeric with integer value (we may have a double type under the hood, but the value is always integer). This third major type is like numeric but there is a rounding process like the school rounding method, at the half integer value. All of these prints True.

	A%=2.6
Print A%=3
A%=1.6
Print A%=2
A%=-1.6
Print A%=-2
A%=-2.6
Print A%=-3

If we use A=A/(A*2), we get 1 not 0.5

for i=1 to 10
A%=i: A%/=i*2: Print A%=1
next
And now we check these two programs which give different results:


We get  8 numbers: 100, 50, 25, 13, 7, 4, 2, 1
A%=100
Do
Print A%,
A%/=2
When A%>1
Print A%

We get 7 numbers: 100, 50, 25, 12, 6, 3, 1
A=100&
Do
Print A,
A/=2
When A>1
Print A
Why ? Because we have different rounding algorithm. 25 div 2 return 12, that works for Long type. A% from 25/2 get 12.5 and has >=0.5 decimals so convert to 13, so 13/2 give 6.5 so we get 7, then 7/2 give 3.5 so we get 4. Looking the values we see 13 vs 12, 7 vs 6, 4 vs 3, but the bigger problem is the amount of numbers which is 1 more for A%,  and this means one more iteration. The one more iteration can be a fault, or if we see alternative, the one less iteration can be a fault.

So lets see another problem (which is a part of  understanding the M2000 Interpreter), its not about type, but about scope. 

There is one problem with DIM statement. This statement can define arrays and can redim arrays (change the definition for an identifier for array which exist). If we have a global array say A(10) from 0 to 9, and we have a module with the statement DIM A(100) we change the global array.  To prevent this if we know about the global array, or we want the module to included in a library, we use the DIM NEW A(100) which make always a new local identifier (shadowing any other local or global with the same name. If we have to redim the local we use DIM A(50), the A() is the local one. If we want to redim the global one we use SET DIM A() where the SET change for that line the Namespace to global namespace so A() is the global array. Statement GLOBAL A(50) always make a new identifier for global namespace, and LOCAL A(50) make a new identifier for local namespace.  Global and Local can't redim arrays.

If the global A() exist and we have a parameter A() there will be no problem, because the READ statement (this used for reading from stack of values) always use the current namespace to create new identifiers, so the Read A() in a function or in a module make a new local array A().

global a(10)=1000
Print len(a())=10
Module beta {
// we change the global one
let a()=(1,2,3,4)
Print len(a())=4
}
beta
Print len(a())=4
dim a(100)=1
Module beta2(a()) {
// a() is local
Print len(a())=7
let a()=(1,2,3,4)
Print len(a())=4
}
beta2 (1,2,3,4,5,6,7)
Print len(a())=100
Module beta3 {
// a() is local
Push a() // this a() is global
read a() // this a() is local
dim a(4) // redim to 4 items
Print len(a())=4
}
beta3
Print len(a())=100

You can skip the next paragraph, which contain things about array names, pointer interfaces, passing by reference arrays, variables and array items.


Arrays have names like variables but these have to do with the way to assign values to array items, and read array items. We can make references with different type, using Link statement. So we may have A() and link A$() so we can put strings and numbers to same array. Tuple are arrays of one dimension, or zero, like this (,). So A()=(1,2+5, "abc") change A() to one dimension type, and length of three items, from index 0 to 2. See the string at index 2. To create an array with parentheses like A() we can use Dim, Local, Global, Link to make a reference (references can't change the reference item, here the array which hold), and the Read statement (from stack of values). The Read statement can make also a reference if we pass a reference and we ask a reference. The by reference pass works with & from both sides, for argument and for parameter. Actually the &A() is a string which hold the weak reference, so pushing the reference to stack, only the weak reference pass. The read statement see the & before the name, and the string in the stack and look if the weak reference is valid, and if is valid create the real reference, but if isn't valid raise error. The same works for variables passing by reference, but we can't link a numeric variable to a string and vis versa. Last for reference pass, we can pass by reference array items, but the arrays didn' lock. So lets say we pass &A(30) and &A() and we make the A() an empty array. Passing the array item, also as a weak reference, goes to a parameter say &A, not an array. Interpreter use a copy in copy out method. It use the weak reference to put the value to local A and at the exit check if the weak reference us still valid and if it is put back the value of A before erase it. If isn't valid no error raised. Arrays have two interfaces, one with names with parentheses and another one with pointers, names like numeric variables. So if we pass A which hold an array, this is a by value pass of pointer so is like passing by reference. If we pass A() without & before the name we pass actual a pointer from a different interface, so when a Read occur, at the moment of read, we get a copy (shallow) of array. Because we may have objects for array items, he copy process may copy some kind of objects but not those with the pointer interface. Pointer interface have the container types: arrays, lists, stacks and groups. Arrays and Groups are values if we didn't use pointer interface. So A()=B() make a copy of B() to A() without changing the internal pointer of actual array object. A=A() if A is a new variable we make A a pointer to A(). If A is a numeric variable change to pointer. If A is a Group, or lambda function or any other object we get error. Only three objects share a common pointer interface, arrays, stacks and lists. We can constrain the parameter type using AS LIST to get only list, etc. Array items always change to anything, except if they have a Group type which have a Set method so this method run on assignment. Here is an example. Class alfa() make a group which has a method  Value with a parameter and a Set method. We make 10 items in A()  of class alfa(). The a(3)=20 execute the set method of group in a(3). By default value and set are public methods.

class alfa {
private:
k=100
value (n){
=.k*n
}
set {
read .k
}
}
dim a(10)=alfa()
Print a(3)(3)=300
a(3)=20
Print a(3)(3)=60
We can extract a copy of a(3) object using Group() because Z=a(3) raise error, need an argument for the value (we get Variable N can't initialized, we didn't pass a value).
m=group(a(3))
Print m(3)
So m(3) isn't array, is a group which return a value. A group may have a name, so has a life same as a variable, or may have a pointer, and has a life until the last pointer erased. We can get a pointer from a(3):
z=pointer(a(3))
Print eval(z, 3)=60
a(3)=1000
Print eval(z, 3)=3000
As we see, we change value in a(3) running the set statement, and the z pointer now return the value through the eval(z, 3), where 3 is the argument for the value method. Getting a pointer from m can be done, but will be a weak pointer (we didn't see that from the pointer view, is the same like the real pointer about functionality). See now that z point to another group object, the m, which is a named object.  Pointer z internal holds only the reference to m. So we can't use after the death of m (at the end of execution of the module/function or sub/ simple function which may have a local group.
z=pointer(m)
Print eval(z, 3)=60

Last we can put lambda functions in arrays, and execute from there.
Dim L(10)
L(0)=lambda x=0 ->{
x++
=x
}
L(1)=L(0)
L(2)=L(1)
Print L(1)()=1, L(1)()=2
L(3)=L(1)
Print L(2)()=1, L(3)()=3, L(1)()=3
L(1)=L(0)
Print L(1)()=1, L(1)()=2
m=L(0)
Dim L(10)=m()
Print L() ' print 1 2 3 4 5 6 7 8 9 10
Dim L(10)<<m()
Print L() ' print 2 3 4 5 6 7 8 9 10 11
Print m()=12
Using Dim L()  we define or redim an array to empty size. Using L()=(,) we make an empty array too but for an array who defined before this statement.

That all for today...


Δεν υπάρχουν σχόλια:

Δημοσίευση σχολίου

You can feel free to write any suggestion, or idea on the subject.