Τετάρτη, 31 Ιανουαρίου 2018

Revision 43 (Version 9.0)

Some work on operators for Integers (are double inside) and long type (are 32 bit inside). We can use long as arrays (we can make Structures in memory if we want 1,2,4 byte unsign values)
This is from Help system in M2000, write Help Buffer
structure stringA {
      str as integer*20
}
structure beta {
      Alfa as byte
      align1 as byte*3
      Kappa as long*20
      name as stringA*10
      kappa2 as double
}
Buffer clear alfa as beta*100
Print Len(alfa), len.disp(alfa)
Print 5*len(beta)+ beta("kappa"), beta("kappa")
Return alfa, 50!kappa!10:=50
sLong=4
\Return alfa, 5*len(beta)+ beta("kappa")+10*sLong! :=50 as long
\Print eval(alfa, 5*len(beta)+ beta("kappa")+40  as long)
Print eval(alfa, 50!kappa!10)
Return alfa, 50!name!5:="George", 50!name!6:="Hello"
Print eval$(alfa, 50!name!5, 20)
Print eval$(alfa, 50!name!6, 20)
Print Len(alfa), type$(beta)

So this revision isn't about those integers in structures.
First check the For Next (or with block). I change he way M2000 use Int() on start value, end limit, and step (here step used as absolute value, we can change how for next work, as Basic or as in M2000). The basic difference is than in M2000 mode, always the block run one time).
long a
for a=1.8 to 10 step 2.6 { \\ change to 1 10 int(abs(2.6))=2
      print a ' 1 3 5 7 9
}
for a%=1.8 to 10 step 2.6 { \\ change to 2 10 3
      print a% ' 2 5 8
}
for x=1.8 to 10 step 2.6 {
      print x ' 1.8  4.4  7  9.6
}

And this is the final part for this revision, the operator /= for long values is a a division same as integer division in VB6, and can't change with switch "+div".  A always get a value as long but before a Fix() function remove the decimal part.
But for integers like A%, is a normal division, and when we move the value to variable we get a conversion. A 2.5 change to 3 (a -2.5 change to -3).


Long a=-2.1
Print a ' -3
a=2.9
Print a ' 2
a=-2.6
Print a ' -3
long a=2.6
Print type$(a)
Print a ' 2
Let a=-2.6
Print a ' -3
Print type$(a) ' long

Print Int(-2.6), Int(2.6) ' -3  2
Print Int(-2.5), Int(2.5) ' -3  2
Print Int(-2.4), Int(2.4) ' -3  2
Print Round(-2.5,0), Round(2.5, 0) ' -3  3
Print Round(-2.4,0), Round(2.4, 0) ' -2  2
Let A%=-2.5, B%=2.5
Print A%, B% ' -3   3
Let A%=-2.4, B%=2.4
Print A%, B% ' -2   2





Check this too for /= operator.
A%=10
For i=1 to 5 {
      A%/=2 ' 5 3 2 1 1
      Print A%
}
A%=10
For i=1 to 5 {
      A%=A% / 2 ' 5 3 2 1 1
      Print A%
}
A%=10
For i=1 to 5 {
      A%=A% div 2 ' 5 2 1 0 0
      Print A%
}
Print Type$(A%) ' Double
A=10
For i=1 to 5 {
      A/=2 ' 5 2.5 1.25 0.625 0.3125
      Print A
}
Print Type$(A) ' Double
Long A=10
For i=1 to 5 {
      A/=2 ' 5 2 1 0 0
      Print A
}
A=10
For i=1 to 5 {
      A=A / 2 ' 5 2 1 0 0
      Print A
}
Print Type$(A) ' Long
A=10
Dim A(10)=10, A%(10)=10
swap A(3), A ' we can swap values, so A get a double as 10
Print Type$(A) ' Double
Print Type$(A(3)) ' long  
Print A(3)*100 ' is a double as result
\\ long for array items not supported, except for swapping
For i=1 to 5 {
      A(3)/=2 ' 5 2.5 1.25 0.625 0.3125  ' assign/alter only to double in array
      Print A(3)
}
Print Type$(A(3)) ' Double
A=10
Print Type$(A) ' A is a Double
Long A ' Now A convert to long
Print Type$(A)
Print A ' value is 10
For A=1.9 to 10.9 {
      Print A,   ' from 1 to 10, because A is long
}
Print
Print A, Type$(A)


Τρίτη, 30 Ιανουαρίου 2018

Revision 41, Version 9.0

Fix Print object
Now this example work perfect
inventory kk=1,2,3,4
kk1=each(kk, 2, 3)
Print kk
Print kk1
mm=(1,2,3,4)
mm1=each(mm, 2,3)
Print mm
Print mm1
zz=stack:=1,2,3,4
zz1=each(zz, 2,3)
Print zz
Print zz1

Πέμπτη, 25 Ιανουαρίου 2018

Lambda Functions in M2000 in details

How functions invoke in M2000


In M2000 code is always in source form, and interpreter directly interpret it, without a scan-analyze it before use it. So a function definition make a simple copy of source in a list for functions with a key prepared to maintain the scope (local or global). This list for functions (and modules also have this place to stored) have some more fields than the one for source, but here we don't discuss it more. Functions names have parenthesis and this is the first difference from modules.

When we call a function in an expression, the interpreter check if is an own internal function by using a hash table for fast search, and if not then check the list for user functions. This list has an idiom, can have same keys but always return the last one. A second idiom, for this list, is that always delete entities from the end, where we append new definitions. There is also a list for variables/arrays where the same behavior exist. This works for events also, because multiple events can be called and they return by opposite direction (events call functions for service them). For Threads it is unsafe to define new functions directly (we can define temporary calling modules which define functions), because a second definition for function replace an existing one in the same scope, and threads are running in same scope where defined (in a module). We skip threads now, to look in Functions, User Functions.


User Functions

To call a function we can use one a call in expression or calling like a module using CALL statement.

Calling in an expression we can pass any number and kind of arguments, and these are stored in a new stack of values. We have to READ from stack in function code to handle these arguments. Also note that from body of function we make local variables or global for temporary use. A local variable can shadow a global one. A new global variable also shadow an old one.  Variables outside of a function is not visible unless it is global at the moment of call, or they are members of same group, if function is member of a group. The members of a group have a dot as prefix so a statement K=.X+1 in a function means that K is local and .X is member of same group where function is a member.

We can return one ore more values from a function, using just a = as statement. So =10 means return 10 and =10,30 means return an array with 10 and 30, the same is for =(10,30) and =(,) means empty array (there is no null in M2000). Also we can pass by reference to stack of values. A reference is a kind of a weak type, and is a string in M2000. To understand this we can do this X=10 : Push &X : Read &Z and now Z reference to X. Z can't get second reference. The mechanism is simple &X is a string with the internal name of X in list of variables. The Read &Z take the string from stack and resolved it to item index in list of values, then make Z as new variable with a flag set to indicate that this is a reference and place the same index, without making new space for variable's value. If X was in another module or function and we call function using &X as argument then we have to use "&" before the name of new identifier to make the reference. At the end of the call normally interpreter erase not only the name from list but the value also, except if found the reference flag, and then skip the erasing.

We can pass by reference a function too. If A() is a function then the &A() is a string containing source of A(). We can use Print &A() to print source to screen; References for functions are the source of function in a block with curly brackets. So when we read from stack of values say Read &K() and the read statement find a string beginning with "{" expect source for function, and make K() with same source. If function is member of a group (means can see other members using dot prefix, or This plus dot prefix) then in reference added the absolute name of group which is member, and at read statement the new function has a field for group and fill it with this name.


 We can call a function using Call. We can use this form:
Call alfa(10,30)
If a non zero return from alfa then this interpreted as an error number, and throw an exception. We can avoid this by using Void before to ensure that any return value dropped and not used for errors.
We can use this if we want to tell to interpreter that Alfa is a function and we want to use it like a call to a module.
Call Void Function Alfa, 10,30

This print 100 to screen:
Call "{Print 100}"
Why? Because "{...}" is a function reference (it is the source in a block in a string.



For the call using Call we pass current stack to function. Is not the same as in first type of call. We can alter the stack of values, and the result stays there, after the call.
So if we do
Push 1,2 : Call Function Alfa 
Then Alfa() called with 2 in top of stack, then 1 after that and anything else. What we do with these values is a matter of what we want in Alfa function. We can inspect stack before use it. An invoke with Call uses slight less resources from a call in an expression. So we can make more recursive calls using Call.

Push 5
Call "{Read X : Print 100*X}"

This print 500
Print Function("{Read X : Print 100*X}", 10)
This print 1000
As we can see we can return a function just returning a string with code as a reference to function, and we can use Call or Function() to use it. Also if result is a string then we have to use Function$() to get it. (we use $ for strings, in M2000 is not optional)

But using this way to return a function has a major limitation. We don't have closures with it. Closures used as a kind of state. In M2000 we can change them inside function if we wish. To use closures we have to make special variables, which are objects call them lambdas

We can also make functions using DEF. This is a syntactic sugar. Interpreter make a function with two statements Read X and =X**2. We can't use a block of statements, with this definition. Used as BASIC Def Fn without Fn, but if we wish we can apply this, and then we have to use it as name, say FNa() for a DEF FNa().

Def PowerTwo(X)=X**2
Print PowerTwo(2)


What is a lambda function in M2000

A lambda function is an object with one list and a function definition. A list has closures and the function can be numeric/object or string type (using $). This object is type of value, which means that we can make a new one just assigning a lambda to another lambda. We get a copy of the list of closures and a copy of function definition (the source).
Lets see a lambda definition:
A=lambda (X,Y) -> X**Y
Now interpreter has A as value, and A() as function. A has an empty list for closures, and have a two line function (** is same as ^ and means Power):
Read X, Y
=X**Y
If A has an index in list of variables say 5 then source of function A() is this:
Call Extern 5
Now a A(2,3) do that: First pass 2 and 3, as 3 in top and 2 next value in a new stack of values, then call the real function in object in index 5 in list of variables.
Passing A() by reference means passing "{Call Extern 5}", but we can pass as &A so a Read &Z makes Z as reference to A and Z() as "{Call Extern 5}"
Using Link A to Z is the same as Push &A : Read &Z


A=Lambda (X,Y) -> X**Y
Link A to Z
Report &A()
Report &Z()
We get the same source "Call Extern 1" (or any other number, it is a matter of where A is saved as value in list of values. Report used as Print, but for multiple lines, although here we have only one, the Cal Extern <number>

Until now we didn't use closures. Lets make a lambda which generate a series of numbers from a given number. We make a function which return a lambda:

Function Construct {
      Read X
      =Lambda X -> {
            =X
            X++
      }
}
N=Construct(5)
Print N(), N(), N() \\ 5 6 7
K=N
Print N(), N(), N() \\ 8 9 10
N=K
Print N(), N(), N() \\ 8 9 10

X in lambda is a closure. It isn't a reference to X in Function Construct, but is a copy. When we call N() first interpreter make a new function with an internal name, and make a new X with value at that time, say 5 in the example. The = statement copy 5 to return value, and then X increase to 6. At the exit X copy back the 6 in closure list. So we print 5 then 6 then 7 and then we get a copy of N to K. Interpreter make K and K(). After we use three more time the N() we copy K to N so now X in N take the saved value the 7 so three more times we get a series of same values as before, 8 then 9 then 10.
As we see we can't change X directly from outside the N, but indirect with a copy of another lambda to that lambda.
X is a value type, so in each copy we get a new value equal to the one we copy. If we have a reference type (this is not a type of pass in a call, is a type that hold a pointer to value) then we copy this reference.

N=Lambda K=(1,2,3), X -> {
      =Array(K,X)
      X=If(X<Len(K)-1->X+1, 0)
}
Print N(), N(), N()
Print N(), N(), N()


This print 1 2 3 then 1 2 3
K has a pointer to an array which have 1, 2 , 3.  X is a new variable with 0 (by default if we didn't provide a number to initialize it.
If we want a copy of N say in M then we get a new K and a new X as closures to M but array is the same for these because we get only the pointer.

We can pass anything as a closure, and a group if we wish. Also there is no upper limit for the number of closures, although they must fit in the line (the paragraph, because M2000 see paragraphs, not lines, editor split paragraphs to lines or not using F1). We can pass lambda functions as closures too.

We can call a lambda function using Call


A=Lambda X=1 ->{
      Print X
      X++
}
Call A() ' 1
Call A() ' 2

Also we can use containers to save and use of lambda. Here we wish a function to act as a module, just to print a number and increase it.
Using from A(3) is easy if we call it in an expression, but not easy to call it using Call. We have to use a temporary copy, here M exist on For This {} block and not outside of it. Using A(3)() in an expression we call it, and return value is the default 0. Check last 3 lines. We use a M$ to hold a function as a Lazy evaluation of A(3)(). When we call this function, change scope to the scope where we use Lazy$() and then evaluate A(3)().

Dim A(10)
A(3)=Lambda X=1 ->{
      Print X
      X++
}
For This {
      M=A(3)
      Call M() ' 1
      Call M() ' 2
      A(3)=M
}
N=A(3)() ' 3
N=A(3)() '4
For This {
      M=A(3)
      Call M() ' 5
      Call M() ' 6
      A(3)=M
}
M$=Lazy$(A(3)())
N=Function(M$) '7
N=Function(M$) '8

We can use inventory as the container. Note that A("abc") = Object can be done if object is an array or a lambda function. We can use Return Alfa, key:=value [, keyN:=valueN] to return anything including array and lambda function.


Inventory A= "abc":=Lambda X=1 ->{
      Print X
      X++
}
For This {
      M=A("abc")
      Call M() ' 1
      Call M() ' 2
      ' this is the standard return to an inventory
      ' we can use a list of key:=value
      ' key maybe number or string, value maybe number, object, or string
      Return A, "abc":=M
}
N=A("abc")() ' 3
N=A("abc")() '4
For This {
      M=A("abc")
      Call M() ' 5
      Call M() ' 6
      A("abc")=M ' this is possible if M is lambda or array, else we get an error
}
M$=Lazy$(A("abc")())
N=Function(M$) '7
N=Function(M$) '8

More examples of using Lambdas we can see here:
Refactoring code in M2000
A game with functional programming in M2000

Here you find example of using recursion in lambda, using Lambda() to call itself. In recursion all closures are exist in any call, because interpreter make reference to first copies of closures, and at the final exit copies stored to original place in closure list.

Use of lambda functions in m2000

So last thing we see is how to make a lambda function using a Group (it isn't the same but works)

 
Group A {
Private:
      X=1
Public:
      Value () {
            =.X
            .X++
      }
}

Print A(), A(), A() ' 1, 2 , 3
' We use Group(name_of_group) to get the object as value, not the value
B=Group(A)
Print A(), A(), A() ' 4 5 6
' We use Let to bypass the error produced becuase we didn't place a Set {} in group
Let A=Group(B)
Print A(), A(), A()' 4 5 6
' Now we make a Set to do the copy
' This is standard for a group if group has no function Value {}
Group A {
      Set {
            Read This
      }
}
A=Group(B)
Print A(), A(), A()' 4 5 6
' We can change members, we can't remove (see below)
Group A {
      Value (N) {
            =.X*N
            .X++
      }
}
Print A(10), A(10), A(10) ' 70 80 90
' We can attach a temporary definition in a reference of A
For This {
      Link A to A1
      Group A1 {
            Function GetX {=.X}
      }
      Print A1.GetX() ' 10
}
' A1 now erased, and A1.GetX() erased too
Print Valid(A.GetX()) ' 0 means false, there is no GetX


We can use Call to a function in a group. Add these lines to previous code:

Print A(10)
Group A {
      Function CallMe {
            Link This to M
            Print M(![])
      }
}
Push 1,2,3
Print Stack.Size '3
Call A.CallMe(10) '110
Print Stack.Size '0 ... Call "eat" all items in stack (because [] place an empty stack)
Print A(10) ' 120

We can avoid "eating" of stack items, using something to get one number. Here we use Number to pop one number from stack (we get error if no number exist in top of stack).

Print A(10)
Group A {
      Function CallMe {
            Link This to M
            Print M(Number)
      }
}
Push 1,2,3
Print Stack.Size '3
Call A.CallMe(10) '110
Print Stack.Size '3
Flush ' erase all items from stack
Print Stack.Size '0
Print A(10) ' 120

As we can see Link This to M make M a reference of This inside This. So we can use the value of This (This always return Group as value and not value of group, if exist one, as here).
If we wish a return value of string then we make group with a name with a $ and we must define a Value$ {}. We can use name without $, in a group which returns string to access other properties.
Also note that Groups are value types and not reference types. To make it as references (pointers) we have to place in containers and use an index or key to manipulate. There is no second pointer for a Group, or a Lambda (internal these are objects, Visual Basic 6 objects) except for a group which defined as a SuperClass. A Superclass group has all members closed anytime, and open by statement For SuperClass {}  inside other groups, which have a reference to this superclass.
This example use a SuperClass to create three groups, two named and one unnamed in an item in an array. We use it to measure how many times we use value from any of three groups of the same super class. A super class can't have another super class. A group can handle only one super class, but an inner group can handle one super class too. So a group may have many super classes but each one in different group inside group (and one for the top group)
We can use X of superclass for times, or here we can use a unique member of superclass, times, who exist in superclass only.


SuperClass ASuper {
Unique:
      times
Private:
      X=1
Public:
      Function Times {
            For SuperClass {
                  =.times
            }                    
      }
      Set {
            Read This
      }
      Value () {
            For SuperClass {
                  .times++
            }        
            =.X
            .X++
      }
}
A=ASuper
Print A(), A(), A() ' 1, 2 , 3
B=Group(A)
Print A(), A(), A() ' 4 5 6
A=Group(B)
Print A(), A(), A()' 4 5 6
Print B(), B(), B() ' 4 5 6
Dim K(5)
K(2)=Group(A)
Print K(2)(), K(2)(), K(2)() ' 7 8 9
Print A.Times() ' 15 times used

To make lambda functions to have some common in any copy, we can use a container, so we have always a pointer to container and there we can have something common. The major difference here in group is that we have many properties to use outside of group, and in lambda we have only one, the value of it.
This is an example of how to use lambda functions to extract data.
Using lambda functions to get data

But this is an example of using data inside a lambda function, and we can use the idiom of M2000 to have a function to worry about the kind of arguments we pass to it. So through argument list we can establish a conversation between inner state and outer state:
This code is a copy from here Example from 8.7 version




\\ using Stack to process data in Lambda function
a=Lambda st -> {
      Read cmd
      \\ Select case need one statement in next line from a Case
      \\ (no second in row allowed), otherwise use { } for any code
      Select Case cmd
      Case 0
            st=Stack
      Case 1 ' is empty
            =Len(st)=0
      Case 2 ' len
            =Len(st)
      Case 3 ' merge To bottom
            {
                  N=[] \\ get current Stack, and leave it empty
                  Stack st {
                         Stack N
                  }
                  =True
            }
      Case 4
            =Stackitem(st, Number) \\ second parameter passing to lambda, read from function stack.
      Case 5
            Stack st {=[] } \\ return a Stack object by swaping  st and an empty one.
      Case 6
      {
            Stack st { Stack } \\ display Stack (current, is st)
            =True
      }
      Case 7 '' change item
      {
            Stack st
            Read Where
            Shift Where+1 : Drop
            Shiftback Where
            st=[]
      }
      Case 8
            Stack st {Flush}
      Case 9 ' copy2new
            st=Stack(st)
      Case Else
            =False
      End Select
}
m=a(0)
merge_stack=3
show_stack=6
show_item=4
change_item=7
show_len=2
empty_stack=8
copy2new=9
m=a(merge_stack,1,2,3,4,5)
m=a(merge_stack,11,12,13,14,15)
m=a(show_stack)
For i=1 To a(show_len) {
      Print a(show_item, i),
}
Print
For This {
      \\ block for temporary definitions
      Group Alpha {
            x=1, y=2
            Operator "<>" (N) {
                  Push N.x<>.x or N.y<>.y
            }
            Operator "++" {
                  .x++
                  .y++
            }
      }
      m=a(merge_stack,Alpha)
}
pos_alpha=a(show_len)

Print "position of alpha", pos_alpha
beta=a(show_item, pos_alpha)
Print Type$(beta)
Print beta.X, beta.y
beta.x+=100
m=a(change_item, pos_alpha, beta)
For i=9 To a(show_len) {
      For this {
            Local N=a(show_item, i)
            Print Type$(N), i
      }
}
For beta {.x=0 : .y=0}
delta=a(show_item, pos_alpha)
Print Type$(delta)
For delta {
      Print .x, .y
}
a2=a
\\ now [a2] and [a] reference same Stack
m=a2(9)
\\ but not now.
Print a2(show_len), a(show_len)
\\m=a(empty_stack)
\\Print a2(show_len), a(show_len)
m=a(change_item, pos_alpha, beta)

For This {
       \\ block for temporary variables/modules/functions
      delta10=a2(show_item, pos_alpha)
      delta20=a(show_item, pos_alpha)
      Print delta20<>delta10
      \\ Also now work for items in evaluator only
      Print a2(show_item, pos_alpha)<>a(show_item, pos_alpha)
      delta10++
      delta20++
      Print delta10.x, delta10.y
      Print delta20.x, delta20.y
      delta10=delta20
      Print delta20<>delta10
}

Last Example

This is a small and nice program using exception for error generated by code. A loop statement set a flag for loop at the end of a block.


\\ Supplier
Range = Lambda -> {
      read St, Ed
      =lambda St, Ed -> {
            if Ed>St then {
                  =St
                  St++
            }  Else Error "finish"
      }
}

\\Process for Each
Cube = Lambda -> Number**3

\\ Consumer
ToScreen = Lambda -> { Print Number }

\\ Main Loop
ForEach = Lambda -> {
      Read Range, Func, ToThere
      Try {
      Call ToThere(Func(Range()))
      loop
      }
}

Call ForEach(Range(1,11), Cube, ToScreen)



Τρίτη, 23 Ιανουαρίου 2018

Refactoring code in M2000 using an example from c Sharp


Example 1 code transcript to M2000 from C#, and  I add an exception if type is unknown. Source for C#: https://www.codeproject.com/Articles/1083348/Csharp-BAD-PRACTICES-Learn-how-to-make-a-good-code
At that article supposed to demonstrate good practice on the first example. My opinion is that this code is useful (adding the exception) if we want what we get, and never we have to change it. 
In original code there is a Result variable to hold result, and give it as last statement, but in M2000 we can give result (without leaving function) as many time we want, because always a function exit at last statement or if an exit statement exist out of block, or a break exist in a block  (but not in a Case block).
Type is colored blue because is known to editor and used in Group definitions, but here we use it as a variable. Check that variable list is as a tuple, but it is a syntactic sugar, interpreter make a Read statement as first line of code inside calculate as Read amount, type, years which means read from stack of values and place values to variables, and if not identifiers exist then make them. This is a way to define variables by assigning values from stack. We say stack and this is a stack of values, not the return stack, and all functions called with a new stack of values. Also notice that M2000 interpreter call user functions without knowledge about what a function need, so it up to us, as programmers,  to decide it, when we read values from stack, or do some task on stack before, and if we leave values then these values dropped, erased as stack erased at return from function.
The code below has something wrong...we don't describe what is the "magic numbers", and the most we didn't know what a number for type variable supposed to mean. Maybe we have a reference sheet, so we read that. We can make some remarks describing these values also. This is no problem. Writer in the article for bad practices never wrote about these optionals ways to "memorize" the "magic numbers".
In this article I want to show how we can expand this logic, not for a kind of "good practice", but a kind of useful refactoring to achieve more from the code, if we want that.
Lets see this code and we discuss the first expansion

Example 1


Function Calculate (amount, type, years) {
      result = 0
      disc = If(years > 5 ->5/100 , years/100)
      if (type == 1) Then {
            result = amount
      } else if (type == 2) Then {
            result = (amount - (0.1 * amount)) - disc * (amount - (0.1 * amount))
      } else.if (type == 3) Then {
      result = (0.7 * amount) - disc * (0.7 * amount)
      } else.if (type == 4) Then {
        result = (amount - (0.5 * amount)) - disc * (amount - (0.5 * amount))
      } else {
            Error "Unknown type"
      }
      =result
}
\\ we can use this without result
\\ so we can change the function definition here
Function Calculate (amount, type, years) {
      \\ If(condition -> true, false)  \\ only one expression evaluated
      \\ condition has to be an expression which return a number -1 or 0 (true or false)
      \\ there is another option if we use numbers>0, so 1 is for first expression and N for Nth expression
      \\ here we use years>5 which return always true or false (-1 or 0)
      disc = If(years > 5 ->5/100 , years/100)
      if (type == 1) Then {
            = amount
      } else if (type == 2) Then {
            = (amount - (0.1 * amount)) - disc * (amount - (0.1 * amount))
      } else.if (type == 3) Then {
            = (0.7 * amount) - disc * (0.7 * amount)
      } else.if (type == 4) Then {
            = (amount - (0.5 * amount)) - disc * (amount - (0.5 * amount))
      } else {
            Error "Unknown type"
      }
}
Print Calculate(100, 2, 4)
Print Calculate(100, 4, 4)



Try to use Calculate(100,6, 4) you get an error. So now we see that this code works. But we don't provide any reference about value for type variable.
So let we use some lambda and inventories. We use "original"  calculate too to get results and compare them.
Lambda functions are first citizens in M2000. We can put some closures with it. a closure is a copy if exist or a new variable which exist only for lambda function. If a closure is a reference type, such as an inventory list, then we get a copy of reference. A lambda function is a value type.
Here in lambda GenerateFunctions we make a local lambda as disc and place it in each lambda for each key in inventory TypeOfCostumer. GenerateFunctions return a lambda also, which have a reference to TypeOfCustomer. This is the last reference because after the exit of GenerateFunctions all local variables destroyed (but for reference type, only the identifier destroyed, which points to inventory). Inventory will be destroyed when no pointer point to it.
We can call a lambda function directly from a inventory, passing what we want (as mentioned above, no check for parameters happen in the call, but in the function where we call). So the return from GenerateFunctions is a lambda and when we call it we pass typename$, amount and years. So now we don't use number for type of costumer. If we want to add some other types we have to do this in source, at GenerateFunctions definition.

For testing purposes we use a TestIt() subroutine, passing by reference a lambda, just name of it, and an inventory with keys as the names of types for customers. We use the M2000 console, and the Menu, a drop down list which use the character position (text cursor) to calculate the real position of showing. We can use Esc to make no choice, so maybe Menu (as read only variable), return 0. So Menu with a number from 1 means we have a choice. Because Inventories keys are numbered from 0, we have to use Menu-1.
Also in this example we use a Menu with direct strings to show, and a Menu with strings which are keys from an inventory which pass (we pass it by value, but because is reference type, we pass a copy of pointer to it).
For this code we can omit the second parameter (erased from definition, and at the calling of sub) because Sub can read CostumerType, is at module level, so each variable at that level is visible in subs (that not hold for functions and modules - modules are not subs, but may have functions/modules/subs/threads also). When we call a sub a READ NEW statement executed. Read new make new variables always, so CostumerType exist two times, but the last one is visible until we return from sub, where all new definitions erased.
Also notice in TestIt() subroutine we use a For This block, which is useful for temporary definitions. So Iterator exist only in this block. A subrutine is like a block with temporary definitions, but is not a block (a block use different resources in M2000 from a subroutine)

Example 2


GenerateFunctions =Lambda -> {
      Inventory TypeOfCustomer
      disc =lambda (years) -> If(years > 5 ->5/100 , years/100)
      Append TypeOfCustomer, "NotRegistered":=Lambda disc (amount, years)-> {
            = amount
      }
      Append TypeOfCustomer, "SimpleCustomer":=Lambda disc (amount, years)-> {
            = (amount - (0.1 * amount)) - disc(years) * (amount - (0.1 * amount))
      }
      Append TypeOfCustomer, "ValuableCustomer":=Lambda disc (amount, years)-> {
            = (0.7 * amount) - disc(years) * (0.7 * amount)
      }
      Append TypeOfCustomer, "MostValuableCustomer":=Lambda disc (amount, years)-> {
            = (amount - (0.5 * amount)) - disc(years) * (amount - (0.5 * amount))
      }
      \\ we place TypeofCustomer as a closure for returned lambda
      =Lambda TypeofCustomer (typename$, amount, years) -> {
            If Not Exist(TypeofCustomer,typename$) then Error "Not implemented yet"
            =TypeofCustomer(typename$)(amount, years)
      }
}
\\ We place lambda from GenerateFunctions() to a closure in FunctionByType
DiscountManager =lambda FunctionByType=GenerateFunctions() (typename$, amount, years)-> {
      =FunctionByType(typename$, amount, years)
}
Function Calculate (amount, type, years) {
      result = 0
      disc = If(years > 5 ->5/100 , years/100)
      if (type == 1) Then {
            result = amount
      } else if (type == 2) Then {
            result = (amount - (0.1 * amount)) - disc * (amount - (0.1 * amount))
      } else.if (type == 3) Then {
      result = (0.7 * amount) - disc * (0.7 * amount)
      } else.if (type == 4) Then {
        result = (amount - (0.5 * amount)) - disc * (amount - (0.5 * amount))
      } else {
            Error "Unkown type"
      }
      =result
}
Print Calculate(100, 2, 4), DiscountManager("SimpleCustomer",100,4)
Print Calculate(100, 4, 4), DiscountManager("MostValuableCustomer",100,4)
Inventory CustomerType = "NotRegistered":=1, "SimpleCustomer":=2, "ValuableCustomer":=3, "MostValuableCustomer":=4
TestIt(&DiscountManager, CustomerType)
End

Sub TestIt(&DiscountManager, CustomerType)
      \\ simple menu to test it
      Menu "NotRegistered", "SimpleCustomer", "ValuableCustomer", "MostValuableCustomer"
      If Menu>0 Then Print Menu$(Menu), DiscountManager(Menu$(Menu),100,4)

      \\ Fill menu with keys, We use For This to open a block for temporary definitions
      CustomerTypeKey$=Lambda$ CustomerType (base0)-> Eval$(CustomerType, base0)
      For This {
            \\ clear Menu list
            Menu
            \\ Make an iterator
            Iterator=Each(CustomerType)
            \\ Use it  (iterator^ is the cursor and based 0)
            \\ Menu + means append a string to menu
            While Iterator {Menu + CustomerTypeKey$(Iterator^)}
            \\ Menu ! means show menu
            Menu !
            \\ We can open menu to specific string using Menu Show "SimpleCustomer"
      }
      \\ we can use Menu (base 1) to get index in base 0, subtract 1, and get the name using CustomerTypeKey$()
      \\ This is usefull if we have different names in menu, so Menu$(Menu)) can't return the proper key for inventory
      If Menu>0 Then Print CustomerTypeKey$(Menu-1), DiscountManager(Menu$(Menu),100,4)
End Sub




So from now on we don't repeat the Sub TestIt(). Just copy to run examples. The third program change the game, using a Class to make a Group with some members. We want now to add types later in code. To Check that we make MostValuableCustomer as an addition after the first use.
We can make a group and handle it without constructor, but Class definitions gave us two significant things, the constructor, and the ability to not include some members in the returned group, after the Class: label. A class definition is a group factory, not a class with the meaning of prototype. Each group in M2000 is a prototype by itself. Inheritance in M2000 exist as two kinds, but here we don't use either, and it is out of scope for this text.

To use the class we have to call it like a function and the result we have to put somewhere, in a name, so we define a named group, or in an item in a container. Here we make a named group the DiscoutManager. We don't have private members here.
Notice that we use a Module as member of group to append customer types, accessing .TypeOfCustomer (a member of group, see dot before, this is like This.TypeOfCustomer). Also notice that we make disc as copy of member .disc (another lambda) to pass it as a closure. Also notice the mysterious syntax of ![] which are a symbol ! and special function [] which get stack of values, replacing with a new empty one. So after the call to that module we get an empty stack of values (this is something which a M2000 programmer must know, if using the stack for storing previous calculations). So Symbol ! in a function before a stack object move all items from that object to function's own stack object. We use this to pass fast from a module to a function values without defining variables, and then pass the variables to function call.

Example 3


Class DiscountManagerClass {
      Inventory TypeOfCustomer
      disc =lambda (years) -> If(years > 5 ->5/100 , years/100)
      Calculate=lambda->0
      Module AppendTypeOfCustomer (CustomerType$, lambdafun) {
            disc=.disc
            Append .TypeOfCustomer, CustomerType$:= Lambda disc, lambdafun -> {
                  = lambdafun(disc, ![]) ' pass stack to lambdafun
            }
      }
      Module RefreshCalculate {
            FunctionByType=Lambda PrivateTypeofCustomer=.TypeofCustomer (typename$, amount, years) -> {
                  If Not Exist(PrivateTypeofCustomer,typename$) then Error "Not implemented yet"
                  =PrivateTypeofCustomer(typename$)(amount, years)
            }
            .Calculate<=lambda FunctionByType (typename$, amount, years)-> {
                  =FunctionByType(typename$, amount, years)
            }
      }
Class:
      Module DiscountManagerClass {
            \\ get a local disc from This.disk
            .AppendTypeOfCustomer "NotRegistered", lambda (disc, amount, years)->amount
            .AppendTypeOfCustomer "SimpleCustomer", Lambda (disc, amount, years)-> (amount - (0.1 * amount)) - disc(years) * (amount - (0.1 * amount))
            .AppendTypeOfCustomer "ValuableCustomer", Lambda (disc, amount, years)->(0.7 * amount) - disc(years) * (0.7 * amount)
            .RefreshCalculate
      }
}
Function Calculate (amount, type, years) {
      result = 0
      disc = If(years > 5 ->5/100 , years/100)
      if (type == 1) Then {
            result = amount
      } else if (type == 2) Then {
            result = (amount - (0.1 * amount)) - disc * (amount - (0.1 * amount))
      } else.if (type == 3) Then {
      result = (0.7 * amount) - disc * (0.7 * amount)
      } else.if (type == 4) Then {
        result = (amount - (0.5 * amount)) - disc * (amount - (0.5 * amount))
      } else {
            Error "Unkown type"
      }
      =result
}
DiscountManager=DiscountManagerClass()
Print Calculate(100, 2, 4), DiscountManager.Calculate("SimpleCustomer",100,4)
\\ Now we add MostValuableCustomer, in execution time
For DiscountManager {
      .AppendTypeOfCustomer  "MostValuableCustomer", Lambda (disc, amount, years)->(amount - (0.5 * amount)) - disc(years) * (amount - (0.5 * amount))
      .RefreshCalculate
}
Print Calculate(100, 4, 4), DiscountManager.Calculate("MostValuableCustomer",100,4)
\\ now we get a reference from DiscountManager.TypeOfCustomer
CustomerType=DiscountManager.TypeOfCustomer
CustomerTypeKey$=Lambda$ CustomerType (base0)-> Eval$(CustomerType, base0)
TestIt(&DiscountManager.Calculate, CustomerType)
End

(copy Sub here)
The problem with previous example is that we can't change Disc closure in lambda functions when these functions written as values to inventory. So it is good example if we never change Disc lambda in DiscountManager. So the solution is to have Disc as a reference type, but lambdas are not reference types. So we have to use an inventory and write there our lambda. So we pass as closure the inventory, and if we change the item at key 0 (we can use numbers as keys also) we change the "service" lambda.
Also we want to change the function associate with a customer type after the first use. So we make an new member to our group (in class definition) as ChangeTypeOfCostumer. 
Block For DiscountManager {} is a same kind as for For This {}, so any new definition erased after, but not for those that we make as closures and those inserting in containers.

Example 4


Class DiscountManagerClass {
      Inventory TypeOfCustomer, disc= 0:=lambda (years) -> If(years > 5 ->5/100 , years/100)
      Calculate=lambda->0
      Module AppendTypeOfCustomer (CustomerType$, lambdafun) {
            disc1=.disc
            Append .TypeOfCustomer, CustomerType$:= Lambda disc1, lambdafun -> {
                  = lambdafun(disc1(0), ![]) ' pass stack to lambdafun
            }
      }
      Module ChangeTypeOfCustomer (CustomerType$, lambdafun) {
            disc1=.disc
            If Not Exist(.TypeOfCustomer, CustomerType$) then Error "Not implemented yet"
            Return .TypeOfCustomer, CustomerType$:= Lambda disc1, lambdafun -> {
                  = lambdafun(disc1(0), ![]) ' pass stack to lambdafun
            }
      }
      Module RefreshCalculate {
            FunctionByType=Lambda PrivateTypeofCustomer=.TypeofCustomer (typename$) -> {
                  If Not Exist(PrivateTypeofCustomer,typename$) then Error "Not implemented yet"
                  \\ pass amount and year too, of not we get error
                  =PrivateTypeofCustomer(typename$)(![])
            }
            .Calculate<=lambda FunctionByType -> {
                  ' need typename$, amount, years
                  =FunctionByType(![])
            }
      }
Class:
      Module DiscountManagerClass {
            \\ get a local disc from This.disk
            .AppendTypeOfCustomer "NotRegistered", lambda (disc, amount, years)->amount
            .AppendTypeOfCustomer "SimpleCustomer", Lambda (disc, amount, years)-> (amount - (0.1 * amount)) - disc(years) * (amount - (0.1 * amount))
            .AppendTypeOfCustomer "ValuableCustomer", Lambda (disc, amount, years)->(0.7 * amount) - disc(years) * (0.7 * amount)
            .RefreshCalculate
      }
}
Function Calculate (amount, type, years) {
      result = 0
      disc = If(years > 5 ->5/100 , years/100)
      if (type == 1) Then {
            result = amount
      } else if (type == 2) Then {
            result = (amount - (0.1 * amount)) - disc * (amount - (0.1 * amount))
      } else.if (type == 3) Then {
      result = (0.7 * amount) - disc * (0.7 * amount)
      } else.if (type == 4) Then {
        result = (amount - (0.5 * amount)) - disc * (amount - (0.5 * amount))
      } else {
            Error "Unkown type"
      }
      =result
}
DiscountManager=DiscountManagerClass()
Print Calculate(100, 2, 4), DiscountManager.Calculate("SimpleCustomer",100,4)
\\ Now we add MostValuableCustomer, in execution time
For DiscountManager {
      .ChangeTypeOfCustomer "SimpleCustomer", Lambda (disc, amount, years)-> (amount - (0.2 * amount)) - disc(years) * (amount - (0.2 * amount))
      .AppendTypeOfCustomer "MostValuableCustomer", Lambda (disc, amount, years)->(amount - (0.5 * amount)) - disc(years) * (amount - (0.5 * amount))
      .RefreshCalculate
}
Print Calculate(100, 4, 4), DiscountManager.Calculate("MostValuableCustomer",100,4)

Return DiscountManager.disc, 0:=lambda -> .5
Print DiscountManager.Calculate("SimpleCustomer",100,4)
Print DiscountManager.Calculate("MostValuableCustomer",100,4)
\\ restore old function
Return DiscountManager.disc, 0:=lambda (years) -> If(years > 5 ->5/100 , years/100)
Print DiscountManager.Calculate("SimpleCustomer",100,4)
Print DiscountManager.Calculate("MostValuableCustomer",100,4)

\\ now we get a reference from DiscountManager.TypeOfCustomer

CustomerType=DiscountManager.TypeOfCustomer
CustomerTypeKey$=Lambda$ CustomerType (base0)-> Eval$(CustomerType, base0)
TestIt(&DiscountManager.Calculate, CustomerType)
End

(copy Sub here)
An idea from example 4 is to put it in a module (without Sub) and return to stack only two values: DiscountManager.TypeOfCustomer, DiscountManager.Calculate

These all we need to use the final Calculate and to get names from TypeOfCustomer.


Conclusion

For my opinion, some "bad practice" is not always bad if we have no problem, and works nice. Forget the spaceship if a bicycle is all you need.