Since Professor Qiu zongyan translated the second edition of structure and intepretation of computer programs, more and more Chinese developers are paying attention to this mit computer programming introductory textbook. At the same time, it is also noted that it introduces the functional programming (functional programming) and the scheme language used in the example.
Time was reversed to 30 years ago. In 1975, Bill Gates and Paul Allen wrote the legendary basic version, which they later sold to MITS for the basic version of the "First Barrel of gold. In the same year, Gerald Sussman, who is the author of the tool, created the scheme language. Actually, scheme is not a new language. To be precise, scheme is just a variant and dialect of lisp. As early as 1958, John McCarthy began to study a language called "processing List Data", which is also the origin of the lisp name (list processing ). "List processing" seems to be a very specific issue at first glance, but in fact this type of problem has a profound and important meaning, and we will see the story here later.
Compared with other dialects of lisp, scheme has the biggest feature: It can be compiled into machine code. That is to say, it runs more efficiently. In addition, scheme is well-behaved at the language level. The Philosophical philosophy that lisp has always admired is "micro-Core + high scalability", and scheme has brought this feature to its extreme. The built-in keywords (keyword) of scheme are poor, and operations such as smaller than, addition, subtraction, multiplication, division, and so on are all in the form of functions. It can be exaggerated to say that all programs can be written with the define keyword and parentheses. However, a side effect of this style is that there will be a lot of parentheses in the program, so some people also call lisp "lots of irritating, spurious parentheses ). For example, the following program is used to calculate the square of a value:
(Define (square X)
(* X ))
(Display (square 3 ))
When programmers who are getting started with C language and get used to procedural programming (compared with functional programming) are beginning to get started with lisp/scheme, the first touch is probably that scheme does not distinguish between data and operations. In the example of "square", "the square of X" can be expressed as "1 as the base," multiplied by X "to calculate twice ". If you use the C ++ language, this logic can be implemented as follows:
Int square (int x ){
Return 1 * x * X;
}
In scheme, we can also achieve this:
(Define (twice func base Arg)
(Func base Arg )))
(Define (square X)
(Twice * 1 X ))
What are the features of this implementation? The biggest feature is that an operation (multiplication operation) is passed as a parameter. According to the program design's "black talk", if a program unit can be passed as a parameter and return value, then this unit is called "first-class citizen ). In C/C ++/Java and other languages, although "operations" can also be passed through function pointers, functor and other forms, they are packaged. In scheme, another function can be passed into the function directly as a parameter, or it can be returned as a return value to another function. The function (that is, "operation") is completely treated as a first-class citizen.
What are the benefits of doing so? In the above example, we abstracted the logic of "two executions of an operation" and obtained the twice function. If we want to implement "execute the addition operation twice Based on 0" (that is, "multiply by 2"), just write as follows:
(Define (Double X)
(Twice + 0 x ))
Here, the twice function is "a function to operate on other functions". Its result depends on what function is passed in as a parameter. Functions like this are called high-order functions in functional programming terms ). Being able to naturally implement higher-order functions is the second important feature of scheme. As mentioned above, LISP stands for "list processing". In fact, the ability to process list data comes from the use of high-level functions. For example, we have the following list:
{1, 2, 3, 4, 5}
For this list, we need to do two things:
<! -- [If! Supportlists] --> 1. <! -- [Endif] --> double each element to get a new list: {2, 4, 6, 8, 10}
<! -- [If! Supportlists] --> 2. <! -- [Endif] --> calculate the square of each element to obtain a new list: {1, 4, 9, 16, 25}
In Java, we can achieve this:
List <int> doublelist (list <int> SRC ){
List <int> Dist = new arraylist <int> ();
For (INT item: SRC ){
Dist. Add (item * 2 );
}
Return Dist;
}
List <int> squarelist (list <int> SRC ){
List <int> Dist = new arraylist <int> ();
For (INT item: SRC ){
Dist. Add (item * item );
}
Return Dist;
}
The problem is clear: Except for the bold two lines of code, these two methods are almost completely repeated. The two methods are actually very similar: traverse a list and map each element of the original list to a new list according to "some rule. Because this "rule" is a ing operation on elements, in order to abstract this list operation, we must implement a High-Order Function to pass in the actual ing operation as a parameter. Therefore, in scheme, which can easily implement higher-order functions, the above logic is quite easy to implement:
(Define (double-list SRC)
(MAP double SRC ))
(Define (square-list SRC)
(MAP square SRC ))
As high-order functions are powerful and convenient in the abstract of operational logic, many people seek to treat operations as first-class citizens in a "mainstream" Procedural language, so as to implement high-level functions. For example, C # allows passing a method as a parameter or return value in the form of delegate, and adds high-order operations such as find to the list <t> type; functionalj (http://functionalj.sourceforge.net/) in the Java World /) in addition to the generic type provided by Java 5, common list operations such as filter and map are provided. If the previous example is implemented using functionalj, you can write it as follows:
// Double and square are function instances
List <int> doublelist (list <int> SRC ){
Return functions. Map (double, Src );
}
List <int> squarelist (list <int> SRC ){
Return functions. Map (square, Src );
}
The phrase "do not distinguish between data and operations" is simple. In fact, there is an important philosophical question behind it, that is, the question of "what is time. According to the concept of procedural programming, "time" is a variable inside the operation. The program records the instantaneous state of the system at each time point in the form of local variables; according to the concept of functional programming, "time" is an external variable that is passed into the function as a parameter. There is no local state in the function, and no value assignment operation is performed. Or, to put it simply, the same result will be obtained at any time when the same parameter is used to call the same function. This property is called "Reference transparency ". If an operation does not have reference transparency, it cannot be passed as a parameter or return value, because the calling environment and sequence may change the results of higher-order functions.
Apps with reference transparency have an additional benefit: they are inherently thread-safe. No matter how many threads there are and in what order of access, as long as the program has reference transparency, no additional thread synchronization mechanism is required to ensure that the results are correct. This is particularly important for server-side applications, especially web applications, targeting many users at the same time. Rod Johnson advocates "stateless Java server-side applications" in his book J2EE development without EJB. enterprise application developers also benefit from the idea of functional programming.
It is said that the original invention of lisp is quite unintentional: McCarthy throws it aside only by implementing an abstract syntax based on Lambda operations, however, his students found that writing programs in such a simple syntax had some fun. Some people say that computer scientists are a group of people who like to contract. The Invention of Lisp proves from a practical point of view that basically all program structures can be reduced to Lambda operations. According to the church calculus theory invented by Alonzo church, all functions that can be effectively computed, including fixed-value functions, can be defined using Lambda operations. For example, data "0" and operation "plus 1" can be defined using lambda:
(Define zero (lambda (f) (lambda (x) X )))
(Define (add-1 N)
(Lambda (f) (lambda (x) (f (n f) X )))))
On this basis, Lambda can be used to define the entire natural number system. This is an extreme example. In many other places, LISP/scheme can also analyze the concepts we are accustomed to in a similar way, allowing us to gain more in-depth insights. For example, in Chapter 2 "constructing data abstraction" of the SiCp object, we can see with our own eyes: "process-oriented programming" and "object-oriented programming ", to a large extent, it is nothing more than using different syntaxes of the same set of Lambda operations. When learning scheme, object-oriented programmers often get a new understanding by bridging the gap between "data" and "operation. In addition, the syntax of scheme is extremely simple, and the most commonly used keywords are no more than five. Therefore, as a teaching language, many schools use Java as the programming language for college students, when you look at these poor students two months later, they are still confused with complicated class libraries such as "anonymous internal classes" and "Io stream decorator, it's not hard for you to understand what I mean.
But this simplicity has also become the biggest obstacle for scheme to be popularized in enterprise applications: enterprise applications require not many elegant possibilities, but a feasible solution. Although the implementation versions of scheme such as PLT provide tool libraries such as XML and Servlet, the syntax is too flexible, the lack of best practices, and the lack of support from major vendors, it makes scheme unable to become the mainstream of enterprise applications. However, although there are almost no real applications, the concept of functional programming still inspires developers of enterprise applications. For example, the continuation feature introduced by webwork2.2 is derived from the concept of functional programming.
Last-but not least-it should be noted that although it is rarely seen in enterprise applications, however, LISP/scheme is widely used in scientific computing, artificial intelligence, mathematical modeling, and other fields. Therefore, it is unfair to call it "small language. On the whole, LISP/scheme is better than algorithm logic writing, rather than I/O operations. Of course we can say that this is why it is frustrated in the enterprise application field, but why cannot it be the result?