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)
We can use less curly brackets, and types plus Enum type:
Function Calculate(amount as double, type as Types, years as long) {
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)
end if
}
Print Calculate(100, 1, 4)
Print Calculate(100, type2, 4)
Print Calculate(100, 3, 4)
Print Calculate(100, 4, 4)
Try Ok {
Print Calculate(100, 5, 4)
}
If Error or not Ok then print Error$
Using Global Enum we can pass numbers, so function test if number fit to expected Enum. If we use llocal enum we have to place enum type value (constant or variable).
Calculate=lambda -> {
Enum Types {
type1=1,
type2,
type3,
type4
}
=lambda
Types,
(amount as double, type as Types, years as long)
-> {
disc=If(years > 5 ->5/100 , years/100)
Select Case type
case type1
=amount
case type2
=amount-0.1*amount-disc*(amount-0.1*amount)
case type3
=0.7*amount- disc*0.7*amount
case type4
=amount-0.5*amount-disc*(amount-0.5*amount)
end select
}
}() ' see () execute top lambda immediately
Print Calculate(100, 1, 4)
Print Calculate(100, 2, 4)
Print Calculate(100, 3, 4)
Print Calculate(100, 4, 4)
Try Ok {
Print Calculate(100, 5, 4)
}
If Error or not Ok then print Error$
Or using an object, which can act as function, and as lambda function, using private and public members instead of closures. Here Enum Types is a member of Calc class type. Because Calc is a global function, there is no Calc.type1, we have to find it as using object, like here we use Calculate.type1. See type as .Types has a dot before Types.
Private:
counter=0
Public:
Enum Types {type1=1, type2, type3, type4}
Value (amount as double, type as .Types, years as long) {
.counter++
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)
end if
}
Property TimesCalc {
value {
link parent counter to counter
value=counter
}
}
}
Calculate=Calc()
Print Calculate(100, 1, 4)
Print Calculate(100, Calculate.type2, 4)
Print Calculate(100, 3, 4)
Print Calculate(100, 4, 4)
Try Ok {
Print Calculate(100, 5, 4)
}
If Error or not Ok then print Error$
Print "Calculate return value ";Calculate.TimesCalc;" times"
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
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"
end if
=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^): End While
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
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
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"
end if
=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)
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"
end if
=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
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.
Δεν υπάρχουν σχόλια:
Δημοσίευση σχολίου
You can feel free to write any suggestion, or idea on the subject.