6.5.1 function Combination
The most important operation of a processing function is a combination. It is useful to look at an example first, which uses tuples to save names and populations of (cities). In Listing 6.16, we create a function that determines whether it is a city, town, or village, depending on the size of the population, and tests the status with several places saved in the list.
Listing 6.16 Processing city information (F # Interactive)
> Let places = [("Grantchester", 552); <-Creating a test data list
("Cambridge", 117900);
("Prague", 1188126); ];;
Val places: (String * int) List
> Let statusbypopulation (population) = <-based on population return status
Matchpopulation with
|n when n > 1000000, "City"
|n when n > ' town '
|_, "Village";
Val statusbypopulation:int–> string
> Places |> list.map (Fun (_,population) –> [1] <-Iteration Place, read population information
Statusbypopulation (population)); [2] <-calculation status
Val it:string list =["Village"; "Town"; "City"]
The first part of listing 6.16 (creating a list of test data, declaring the Statusbypopulation function) is very simple and important in the last few lines. We want to use List.map to get the state of each place, so we pass the lambda function as a parameter value. The lambda function first uses pattern matching [1], extracts the second element from a tuple, and then calls the Statusbypopulation function [2].
While the code can run, it can be written more elegantly. The key idea is that you must perform two operations in turn to first access the second element in the tuple, and then use the returned value to calculate it. The first operation can be implemented using the SND function, so you need to combine two functions. In F #, this can be written using the function combination operator (>>), like this:
SND >> statusbypopulation
The result of this operation is the function, the parameter is a tuple, the second element of the read tuple (must be an integer), calculates the state of the city according to this number. Looking at table 6.1, we can see how the function is combined, and the table shows the type signature of the function.
Table 6.1 Type signatures: snd,statusbypopulation, and functions obtained by combining two functions by using the >> operator
function value |
Type |
Snd SND (after specification) Statusbypopulation SND >> statusbypopulation |
(' A * ' B) ' B ' Int (' A * int) string int (' A * int) –> string |
The second row of the table indicates the specific type of the SND function, and the compiler infers that the second element of the tuple must be an integer, and if we replace the type parameter ' B ' in the first row with the int type, we get the type. Now, we have two functions that can be combined, because the return type of the second line function is the same as the input type of the third line function, by combining the two functions together, the resulting function, invoking the first function, and passing the result of the call to the second function, The final function has the same input type as the second row function, and the return type is the same as the third row function. Listing 6.17 shows the rewrite of the original code with a combination of functions.
Listing 6.17 using the function combination operator (F # Interactive)
> Places |> List.map (Fun x-(snd >> statusbypopulation) x); [1]
Val it:string list =["Village"; "Town"; "City"]
> Places |> list.map (snd >>statusbypopulation); [2]
Val it:string list =["Village"; "Town"; "City"]
The first line [1] explicitly calls the combined function, which takes a tuple of the city name and population as the parameter value. This demonstrates that the result of the composition is a function that can be invoked using normal syntax, however, the reason for using a function combination is the ability to use the combined function as a parameter value for other functions. Here, the parameter of the combination function is a tuple, which returns a string, so we can use it directly as the parameter value of the List.map to get a list of the sample location states [2].
The implementation of the function combination operator is very simple, and if the operator does not exist in the F # Library, we can define it ourselves:
> Let (>>) f g x = g (f (x))
Val (>>): (' b-> ' C), ' A-C ' (' A-B ')
In this declaration, the operator has three parameters. When we used it earlier, we only specified the first two parameters (the function is combined.) For a more in-depth understanding of how combinatorial functions work, you can see two possible explanations for type signatures in 6.2.
Figure 6-2 The type signature of the function combination operator. If you specify three parameter values (comments above), the result of the call is returned, and if you specify only two parameter values (note below), the combined function is returned.
This operator is able to combine functions because of a scatter application. If you specify only the first two parameter values, the result is a composite function, and when the operator receives the third parameter value, it invokes the first function with that parameter value, and then uses the result to invoke the second function. Obviously, if you specify all three parameter values, it is usually not very useful because we can call the function directly without using this operator!
Now that we've seen how function combinations work in F #, let's take a look at what's going on in C #.
6.5.1 function Combination