SPL Programming - 5.7 [Sequence as a whole] Lambda syntax*

 

Looking back at the two functions A.pos(x) and A.select(x), we say that the former is not a loop function, and the symbols ~, # can’t be used in the parameter x, while the latter is a loop function, and the symbols ~, # can be used in the parameter. In fact, in the calculation process of these two functions, both of them will traverse A, that is, they will process the members of A one by one. What is the difference between the parameters of these two functions?

Before the introduction of loop function, all functions f(x) has one characteristic. If its parameter x is a calculation expression, it will be calculated before calculating this function f(). For example, max(3+5,2+1), when the computer calculates max()function, the parameters obtained are already 8 and 3. It’s the same case even if there may be variables in the parameters. The calculation expression of max(x+5,y+3) cannot be calculated when x and y are not assigned, and after x and y are assigned, x+5 and y+3 will be calculated first and then transferred to max function for calculation.

That’s OK. That’s what we learned about functions in math class. So is the parameter of A.pos(x).

However, A.select(x) is different. This x may not be calculated before calculating the select()function. It can be calculated only in the process of calculating the select() function. For example, A.select(~%2==0), we can’t calculate ~%2==0 before calculating the select() function. There is an unknown ~, which can only be determined in the process of looping A, and then we can calculate this expression.

For the function of sequence, its parameter is an expression to be calculated, which can only be calculated in the process of looping the sequence. The function with this characteristic is called loop function. Therefore, A.pos(x) and A.pseg(x) are not loop functions, while A.(x), A.run (x), A.select(x) and A.max(x) are loop functions.

For non-loop functions, even if the parameters are written as expressions, they will be calculated as specific values when they are passed in.

For a loop function, the parameter to be passed into the function is an expression, which contains some unknown variables such as ~, #. In fact, this expression can be regarded as a function with these unknown variables as parameters. In other words, the parameter of a loop function is essentially another function, not a simple value.

This is the essential difference between a loop function and non-loop function, it takes function as parameter.

The earliest programming languages did not support functions as parameters, and people were still inexperienced at that time. They simply used the experience of functions in mathematics (in fact, mathematicians have been engaged in functional analysis and studying functions of functions for a long time, but they are relatively abstruse after all). Later, in order to make code reuse more convenient, people introduced the concept of function pointer, and passed the function as a parameter to another function, so that the former can be called by the latter. However, it is still necessary to clearly define the called function with a piece of code. The code is complex and difficult to understand.

For example, A.select(~%2==0) in our example, the traditional syntax is divided into two steps: first, define a function f(x) as x%2==0, and then use A.select(f) to calculate. In the loop calculation of the select()function, the current member of A is passed into the function f() as a parameter. After calculation, the return value is obtained, and it is decided whether to add this member to the return value of select().

In fact, the calculation logic of many functions that need to be used as parameters is not complex. It can be described clearly by a simple expression, for example, this x%2==0 is very simple. It’s too troublesome to define an f function for such a matter.

Therefore, people further invented the programming syntax to define a function directly with an expression. Instead of defining the f function in advance, it can be directly writen into the parameter of the select() function:

A.select( f(x):{x%2==0} )

This method of temporarily defining functions in parameters is called lambda syntax in the industry.

With lambda syntax, code writing is much easier. Now many programming languages have begun to support this syntax.

There are still problems.

The function to be passed in must be a function with parameters, which are used to refer to some variables (the members of A here) generated in the calculation process of the host function (the select() in this example). Otherwise, if there are no parameters, it should have been calculated before calling the host function.

Therefore, in the above f(x):{x%2==0} syntax, there must be a way to specify the parameter. Use x in f(x) to tell the computer that x in x%2==0 is the parameter of the function as the parameter(a bit roundabout), and is waitting for the host function to pass the value in. If we only write x%2==0, the computer doesn’t know what x is, or is it a variable somewhere else?

It’s still a bit cumbersome and confusing to understand, but this is the status quo of most programming languages that support lambda syntax.

It’s not over yet.

The sequence number (that is #) of A’s members may also be used in the host function of select(). It is not enough for the function f() to have only one parameter, because the select()function needs to accept all kinds of functions as parameters, and the parameters of these functions must be unified, otherwise the select() function cannot call them.

Therefore, this function must have at least two parameters, and the syntax must be written as follows:

A.select( f(x,y):{x%2==0} )

In this example, we don’t use the y parameter prepared for the sequence number (that is #), but we have to write it in the definition.

However, we know that select()may also use ~[-1] and so on. How much more will be passed in? These parameters need to be defined as parameters of f() in advance. It’s too troublesome. Just pass in the whole A and then the current sequence number. Anyway, it can be calculated. Finally, it may be as follows:

A.select( f(x,y):{x(y)%2==0} )

The parameter x represents the sequence to be calculated by the host function, and the parameter y represents the sequence number of the member being calculated by the host function. The rest is written in this definition.

But that’s inconvenient again.

In fact, most of the programming languages that support lambda syntax do not come to this stage, and they only support to the level of f(x):{x%2==0}. In other words, there is no way to refer to sequence numbers and make adjacent references in expressions.

So, how can we make the code simple and descriptive?

That’s what SPL does now.

SPL simplifies this matter by using the symbols of ~ and # and [] to represent the parameters of the function as a parameter. Therefore, it is OK to write ~%2==0 instead of writing f(x) to specify the parameter name.

In this way, writing and understanding are simple, and because of these rich symbols, the description ability is also strengthened.


SPL Programming - Preface
SPL Programming - 5.6 [Sequence as a whole] Sorting related
SPL Programming - 6.1 [Reuse] User-Defined Functions