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)