C functional programming USES closures to encapsulate data implementation code

  • 2020-06-15 10:08:44
  • OfStack

Scope of C# functional programming

In C#, the scope of a variable is strictly defined. The essence is that all code lives in the methods of the class, and all variables live only in the module that declares them or in the code that follows. The value of a variable is variable, and the more open a variable is, the greater the problem. The one-like principle is that the value of a variable is best left unchanged or kept within the smallest scope. A pure function is best to use only variable values defined in its own module and not access any variables outside its scope.

Unfortunately, sometimes we cannot limit the value of a variable to a function. What if you define a few variables during the initialization of a program and need to use them again and again? One possible solution is to use closures.

Closure mechanism for C# functional programming

To understand the nature of closures, let's look at a few examples of using closures:


namespace Closures 
{ 
    class Closures 
    { 
        static void Closures() 
        { 
            Console.WriteLine(GetClosureFunc()(30)); 
        } 

        static Func<int,int> GetClosureFunc() 
        { 
            int val = 10; 
            Func<int, int> internalAdd = x => x + val; 
            Console.WriteLine(internalAdd(10)); 
            val = 30; 
            Console.WriteLine(internalAdd(10)); 
            return internalAdd; 
        } 
    } 
}

What is the resulting output of this code? The answer is 20, 40, and 60. The first two values should be easy for you to see, but why is the third value 60? Let's start with the execution of the program: the Closures function calls the GetClosureFunc function and enters it. The function call statement takes an argument of 30. This is because GetClosureFunc returns a function, which is called again at execution time, into the GetClosureFunc function. First, the value of val is 10. One value is passed in through the internalAdd method, so the first output value is 20. From this you can see roughly how local functions and local variables work in the same scope, and it is clear that changes to local variables affect the value of internalAdd, although variable changes occur after the initial creation of internalAdd. Finally, GetClosureFunc returns the internalAdd method, calling the function again with an argument of 30, and the result becomes 60.

At first glance, this doesn't really seem logical. val is supposed to be a local variable that lives on the stack, and when the GetClosureFunc function returns, it's gone, right? Indeed, this is the purpose of closures to prevent variable values from going out of scope when the compiler explicitly warns that this situation will cause the program to crash.

From a technical perspective, the location of the data is very important, the compiler creates an anonymous class, and create an instance of the class in the GetClosureFunc - if you don't need a closure work, is the anonymous functions will only with GetClosureFunc survival in a class, in the end, the local variable val actually is no longer a local variable, but a field in an anonymous class. As a result, internalAdd can now refer to functions stored in anonymous class instances. This instance also contains data for the variable val. The value of the variable val is preserved as long as the reference to internalAdd is maintained.

The following code illustrates the compiler's pattern in this case:


private sealed class DisplayClass 
{ 
    public int val; 

    public int AnonymousFunc(int x) 
    { 
        return x + this.val; 
    } 

    private static Func<int, int> GetClosureFunc() 
    { 
        DisplayClass displayClass = new DisplayClass(); 
        displayClass.val = 10; 
        Func<int, int> internalAdd = displayClass.AnonymousFunc; 
        Console.WriteLine(internalAdd(10)); 
        displayClass.val = 30; 
        Console.WriteLine(internalAdd(10)); 
        return internalAdd; 
    } 
}

Back to the idea of dynamic function creation: You can now create a new function out of thin air, and its functionality varies by parameter. For example, the following function adds a static value to an argument:


private static void DynamicAdd() 
{ 
    var add5 = GetAddX(5); 
    var add10 = GetAddX(10); 
    Console.WriteLine(add5(10)); 
    Console.WriteLine(add10(10)); 
} 

private static Func<int,int> GetAddX(int staticVal) 
{ 
    return x => staticVal + x; 
}

This principle is the basis of many function-building techniques, which are clearly the antithesis of object-oriented methods such as method overloading. But unlike method overloading, the creation of anonymous functions can happen dynamically at run time, triggered by just one line of code in another function. Special functions used to make an algorithm easier to read and write can be created in the methods that call it, rather than adding functions or methods at the class level -- the heart of functional modularity.

conclusion

Closures are an important tool for programming languages to support a functional design approach.


Related articles: