F # and FP
Written by Allen Lee
Be yourself
When it comes to introverted personality, people will think of "Silence, do not love to talk", "unsociable, poor communication", "mysterious, not open enough" and other personality characteristics. Even some well-known Psychology dictionaries use negative descriptions to define the introvert. For example, the Dictionary of Psychology describes introvert as "Focus on your own thoughts, avoiding social interactions tends to escape from The real world, while The International Dictionary of Psychology defines introvert as "a major personality trait, it is characterized by self-focus, lack of social skills, and relatively passive ". People regard introverted personality as a problematic personality, and our society constantly emphasizes that extroverted personality is the natural result of healthy development, it is no wonder that many introverted people are ashamed to admit their authenticity and are under great social pressure.
Similar situations occur in functional programming. I once saw a post in hubFS.net. The author said that one day he told his colleagues that he had done some functional programming in his spare time, and the response was: "Functional programming-isn' t that the long haired, dope smoking end of computing? "This description reminds me of a very introverted person who keeps himself in the room all day for computation, without hair cutting, taking a bath, eating, drinking, and drinking, occasionally, buying something is like a savage traveling out of the mountains, attracting countless strange eyes. If this is what people think of functional programmers, then you don't have to be surprised to say that functional programming languages are the languages of mathematicians or are only suitable for university studies.
Is introverted personality really useless? None. I recently read "introverted advantage". It not only helps me understand the differences between introverted and extroverted personalities, but only because of different brain mechanisms, in addition, it makes me realize the precious character of an introverted character-"the ability to concentrate heavily, and the ability to observe and understand the impact of changes on each person, the habit of getting rid of limitations and thinking about problems, the willpower to make unusual decisions, and the potential to slow the outside world ". I once read this sentence in Zheng xinyao's smart fast food:
People are tired, mostly because they assume another person.
Perhaps, introverted people should discover their inherent values again, rather than trying to transform themselves into extroverted people.
Similarly, functional programming should have its own sky. Michael L. scott introduced the concept of functional Programming in Programming Language Pragmatics and once mentioned "program programmers-probably most-who have written significant amounts of software in both imperative and functional styles find the latter more aesthetically appealing ". During this time, due to the study of F #, I was eager to read a variety of articles about functional programming. Sometimes I couldn't help thinking: do we have too many preferences on functional programming, so that we cannot understand it more deeply and completely? If so, it would be a pity, because while denying functional programming, we will also miss the value that should be taken seriously. Perhaps we should try to understand what aspects of functional programming can do better, rather than simply rejecting it.
Today's main character-Functions
In my impression, the map function is usually used to map a group of elements into another group of elements according to certain rules. For example, the following code maps an integer list to a list composed of the square of the corresponding element:
Figure 1
Once, in Programming Language Pragmatics, I introduced the Higher-Order Functions (10.5 Higher-Order Functions) Section to see a "new method" of map Functions ":
Figure 2
On the left side of the arrow is the usage of the map function in Scheme, while on the right side of the arrow is the output result. This time, it accepts two input lists. The ing rule is a binary function that will be applied to the two elements corresponding to the positions in these two lists, the calculation result will be placed in the corresponding position of the output list. Think about it. If you want to implement this map function without any restrictions on programming languages, what would you do? (Hey, I suggest you first think about how to implement this map function in your preferred language and then continue reading it .)
That night, I couldn't sleep in bed, And suddenly got up and wrote this Code:
Code 1
What is this? Function? What are parameters? What is the type? What is the return value? What is the function body ?...... Calm down! Let me hear about it.
First, map is indeed a function and a high-level function. You may have heard of this term elsewhere, A high-order function is a function that meets at least one of the following conditions: (1) accepting one or more functions as input; (2) outputting a function. After reading this, you may be confused: "When will the map function accept the function as input? "Didn't you see it, right? Check Code 1 carefully. Is it full of "unknown type" symbols? When you try to read this code from start to end, do you feel dizzy, chest tightness, rapid breathing, and weak wrist? If yes, you are likely to suffer from "Explicit type declaration dependency syndrome ". Haha, just kidding.
Then, match the map function signature with the code in Figure 2. It is not difficult to see that f, l, and r are the parameters of the map function. Among them, f is a binary function used to represent ing rules, and l and r are two input lists involved in calculation. However, what types of f, l, and r are specific? Perhaps, you have heard that one of the highlights of F # is its type inference system. Now is the best time to test this system, let's see what type it will deduce the f, l, and r parameters and the return values of the function. Enter the map function code in F # Interactive and you will see the following output:
Figure 3
This is the type of the map function. You have not heard the error. This is indeed the type of the map function. It contains the type information of parameters and returned values:
- F :( 'a-> 'B)
- L: 'A list
- R: 'A list
- Returned value: 'B list
Here, 'a and 'B' are type parameters, and 'a list can also be written as list <'a>. It can be seen that the F # type inference system has successfully deduced the list of parameters l and r and the type of the returned value is F # (generic). As for f, the type inference system analyzes from the map code that it is a binary function. Since the code of the map function does not indicate that the return value of f is of the same type as the parameter, the type inference system uses two different type parameters to represent them, in other words, the return value of f can be the same or different from the parameter type.
Then, let's take a look at the implementation of the map function. If you are not familiar with the syntax of F # And do not understand the code of the map function, it doesn't matter. Let's put down the syntax temporarily and try to understand it with our own logic thinking. "Match... With... ", Literally means" put... And... In this example, we can understand it as "matching (l, r) with the following situations". Then, the code lists three conditions for matching:
- First case: both l and r are empty lists (in F #, "[]" indicates an empty list. In fact, the type inference system just sees "[]" to know that the type of l and r is the list of F #). At this time, the map function returns an empty list.
- The second case: This is the most interesting part of the whole map function. We don't stop on the surface features of the list, but go deep into its internal structure. We use "lh :: it can be further divided into the "Header" (lh) and "tail" (lt) lists, and the "Header" (lh and rh) to f function (f lh rh), recursively pass the decomposed "tail" (lt and rt) to map function (map f lt rt), and pass ":: "operator connects the operation results of these two functions. Here, we can see that the ":" operator can be used to break down the list and combine the list. "->" the left side is the form before transformation, the right side is the form after transformation, but a short sentence contains three steps: "Understanding", "decomposition", and "re-synthesis". If you have watched the "Steel alchemy" animation, you will find that this is actually three major steps of alchemy described in the animation.
- The third case: in fact, it describes two cases. One is l-to-r and the other is r-to-l. That is, one can continue to be decomposed, And the other cannot, in either case, the operation cannot be continued, so an exception is thrown.
In fact, this matching implementation method has a formal name called "pattern matching". You can try to understand every situation as a transformation from one form to another, it may be easier to accept. Since we want to use the map function recursively, we need to use the rec keyword when declaring it.
Finally, let's try the map function in F # Interactive following the example in Figure 2:
Figure 4
The parameters of the multiplication operator and the return value are of the same type. Next, let's take a look at a function with different types of parameters and return values:
Code 2
This function returns a Tuple containing the values of a and B and their product. Replace the multiplication operator in Figure 4 with the g Function and try again:
Figure 5
Now, if I want to display the output list in "2*3 = 6" (taking the first element as an example), what should I do? F # provides a List. iter function, which is used and List <T>. the ForEach method is similar. We can. the iter function passes an anonymous function and calls the printfn function for each element. The question is, how can I separate the three values I want from each element? Observe the output list carefully and you will find that each element conforms to the (x, y, z) mode. Therefore, we can "match" Each element to this mode, extract the data we want (assuming the result is the output list ):
Figure 6
If I only need to output the third value of each element, I can ignore the other two values through the "_" symbol:
Figure 7
It can be seen from this that the deeper significance of pattern matching is actually to break down and extract data based on understanding the data structure, rather than the so-called "switch" version.
Call a function
Generally, when calling a function (or method), we provide all the parameters it requires. Perhaps, this practice has been solidified into our behavior as a common sense, that's why I explicitly mention it here is a bit incredible.
Imagine if the function parameters are obtained from different places at different times? For example, the three parameters of the map function are provided by three different users at three different times. In other words, only one parameter can be provided at a time.
At this time, it is recommended that you create two helper functions to "fix" the first two parameters of the map function:
Code 3
This solution is good and simple enough, but there is a small constraint. Take the map_with_g_and_l function as an example. The creation of the map_with_g_and_l function can only be completed if the parameter l already exists. In other words, first, I need to define these parameters as variables somewhere and then use them to create these auxiliary functions. When I need to change the parameters of the function, I only need to change the values of these variables. Speaking of this, people familiar with object orientation may say, "You should encapsulate these three parameters into an object! "This is a good idea. We can use the Record Type of F # to try it out. (If you are not familiar with it, you can read the relevant chapters from C #3.0 to F # to add some basic knowledge ):
Code 4
Next, we instantiate a Map <'a, 'B'> object:
Code 5
It is worth noting that in F #, the null literal is not a normal value for functions and lists, so they do not allow you to use the null literal directly, including comparison and assignment, however, the null literal can be used as their values in the case of exceptions. Code 4 and code 5 demonstrate two alternatives to use the null literal (in fact, F # null literal is not recommended. If you really need to express the value "can be empty", you can use Option Type of F, if you are interested, you can use Option Type to refactor Code 4 ). Then, we set the values of l and r respectively and call the Invoke method:
Code 6
For those familiar with imperative programming and object-oriented programming, the above thinking process is natural, but those familiar with functional programming may feel that we have complicated the simple problem. In functional programming languages, functions support Currying by default, which makes it possible to provide parameters in the scoring stage:
Code 7
Speaking of this, you may ask: "What is the difference between code 7 and code 3? "The simplest answer is: The functions in code 7 are computed, and the functions in code 3 are defined. Cannot understand? Don't worry. To understand the difference, you need to first figure out why the function can accept some parameters, and what is colialization.
Take the g function (Code 2) as an example. to enable it to provide parameters separately, we should write it as follows:
Code 8
From the code above, we can see that there is actually only one g function parameter, and it will return an anonymous function. This is also a parameter of this anonymous function. In other words, if my function has N parameters, I will nest an anonymous function in it, which is obviously not intuitive enough (compare Code 2 and code 8 ). So what is colihua? What role does it play here? On HaskellWiki, the definition of colized is as follows:
Currying is the process of transforming a function that takes multiple arguments into a function that takes just a single argument and returns another function if any arguments are still needed.
After reading this, I think you have understood that Ke Lihua enables the g Function written in code 2 to get the write effect in code 8. In other words, in F #, code 2 is equivalent to code 8.
So what is the difference between the computed function and the defined function? To help you see their differences, let's modify the g Function of code 8:
Code 9
Then, we use the g Function in the form of code 7 and code 3 in F # Interactive:
Figure 8
H1 is the calculated function, while h2 is the defined function. Pay attention to the output position of "slot". When we calculate h1, the "printfn" slot "in the g function has been executed, but this sentence will not be executed in future calls to h1. When we define h2, the "printfn" slot "in the g function is not executed, but this sentence will be executed every time h2 is called. Are there any differences? When we calculate h1, some g functions have been executed! Can you imagine what a function can be executed by division? In fact, the process of calculating h1 has a formal and very relevant name, called "Partial Application ". So far, I think you should feel that code 7 and code 3 are essentially different.
Composite Functions
Next, let's consider a new requirement. I saved the data in a file:
Figure 9
I want to output the following results on the console:
Figure 10
Compared with figures 9 and 10, it is not difficult to find that figure 10 outputs an even number of data products in the two columns of Figure 9. From Figure 9 to figure 10, we have gone through the following process:
Figure 11
If each step of the preceding process corresponds to a function, the following six functions are required to complete the process:
Code 10
The question is, how do you call these functions? A common practice is:
Figure 12
However, F # provides a special operator -- "|>", which allows you to call these functions in the following way:
Figure 13
Hey! Have you found anything? Take a look at Figure 11, and then look at Figure 13. I believe you have seen what I have seen. What if I want to use a function to represent the process in Figure 11? A common practice is:
Code 11
At this moment, I believe you should be very curious to know whether the process_and_print function can retain the "shape" of Figure 11 as shown in Figure 13. Of course you can! F # provides another special operator -- ">", which allows you to combine these functions in the following way:
Code 12
If the Supply Chain shows the entire process from purchasing raw materials to sending final products to consumers, the "Data Link" of code 12 shows the entire process from reading the original data to outputting the final result to the console. After we set up the entire chain, the rest is "providing raw materials:
Figure 14
Advanced topic
Back to our map function, some people say that it may cause stack overflow. Well, it does. What should I do? We can change it to "Tail Recursion" (Tail Recursion ):
Code 13
In addition, you can also use the local function to "hide" its implementation:
Code 14
If you want to better understand Tail Recursion, I recommend you go to Chris Smith's Understanding Tail Recursion.
You can also change it to CPS (Continuation-Passing Style ):
Code 15
If you don't know about CPS, I recommend you go to Matthew Podwysocki's Recursing on Recursion-Continuation Passing and wesdyer's Continuation-Passing Style.
What are you doing next? Assignment! Suppose I have a list:
Let have = [['a'; 'B'; '1']; ['C'; 'D'; '2']; ['E '; 'F'; '3']
I want to change it to this:
Let want = [['a'; 'C'; 'E']; ['B'; 'D'; 'F']; ['1 '; '2'; '3']
What should I do? This question was found on Jomo Fisher's blog, where many people provide implementations in different languages. You can try it in the language you are best.
Tolerate "new" things
Functional programming is no longer a new thing, but why cannot it become popular? Is it not close to reality? Is it because its theoretical threshold is too high? Or because we have a better choice? Before giving me my opinion, I 'd like to share with you a little story I saw in the consulting mysteries-the treasure chest of consultants at geralder winberger:
Officials of a railway company rejected the call for a public stop at a certain place, because after some research, they found that no passengers waited for the station during the call-of course, at that time, the train was not prepared to stop at the station, and passengers had no reason to wait.
Can you see the contradiction? Because no stop station is set up on a platform, passengers are not waiting here. Because no passengers are waiting here, there is no need to set up a stop station on the platform. What is the logic? Wimbledon summarized this "logic" as "the railway paradox ":
Due to poor services, people are rejected for better services.
Imagine that if employers refuse to recruit new graduates due to lack of experience, they will lose the opportunity to increase their work experience, resulting in more employers refuse to recruit them. Experience is a portrayal of the past. It cannot represent the present, nor predict the future. Many companies look at talents with narrow eyes while shouting that there is no talent. At this time, I can't help but think of a problem Zheng xinyao mentioned in smart fast food:
Is there a lack of talent or a mechanism to accommodate talent?
Functional programming is often considered a "thing of the school". It is only applicable to academic research, so people think it is not necessary to let it enter the "real world ", it also lost the opportunity to step out of the school gate. Q: Have you ever been to functional programming? Or is it a re-occurrence of "crossing the river by pony" following the opinions of everyone "?
Since Microsoft announced the commercialization of F #, many people have begun to ask F # And C # which is better? Why are they so concerned about this issue? Why do they think that F # And C # are mutually exclusive rather than deny? In today's society, the intensity of competition is far greater than ever before. The concept of survival of the fittest is also deeply rooted in people's thoughts. The phenomenon of old and new replicas is even more common, so that the first response people make when facing new things is almost "What Will It Replace ". In today's society, the speed of development is staggering, and there seems to be too many things people have to bear, so that they can't afford to wait for some time to think about it, let alone understand another world view that may be significantly different. Being unable to tolerate multiple different world views will force you to choose one from it, and in your subconscious, this will create the idea that I must choose the best one. This concept of alienation will lead you to choose a programming language, just like a slave selects the master, you must be careful to decide, so that the wrong master will not be tired of your future. Q: Is Programming Language a programmer service or is it a programmer's programming language?
Newton said that an object has the inertia to maintain its original state. Why does it mean human thinking and behavior? Li Zixun said in "Dancing in the soul:
Each theory creates an associated reality for human vision and perception. The more we appreciate a theory, the more we are constrained by this theory, our vision and perception are also narrower. If we have the psychological tendency to unconsciously deny other theories, we can basically be called the "prisoner" of a certain theory ". The deeper we trust and rely on a theory, the more people's cognitive ability will be gradually reduced to a very poor situation by the theory until they completely lose their freedom of mind and perception.
Remember how many times I used to take a certain measure to conform to a certain theory instead of selecting a theory to solve a specific problem? Different programming ideas are like looking at things from different perspectives. No matter which angle you use today to look at things, it is always advantageous to have one more choice, as long as you do not lose your way in these choices.