其他分享
首页 > 其他分享> > what is the lexical scope

what is the lexical scope

作者:互联网

Why I write

First of all, write down the whole process of how I encountered these problems

when I learned how to overload the function call operator, and I learned about function object; which led to the concept of function closures, lamda expression, lexical scope and finally First-Class Function.

Overload the function call operator

here is the c++ code example

#include <iostream>

using std::cout;
using std::endl;

struct Foo {
public:
    int operator()(int a, int b) const{
        return a + b;
    }
};

void test()
{
    Foo f1;
    int a = 1, b = 2;
    int c = f1(a, b);
    cout << "c = " << c << endl;
}

int main()
{
	test();
    
    return 0;
}

Programming running results

c = 3

At first I thought f1 was both a Function Object and a Closure, but it is not; f1 is a function object , but it is not exactly a closure.

Closure

here is the definition of the Closure: "In programming languages, a closure, also lexical closure or function closure, is a technique for implementing lexically scoped name binding in a language with first-class functions".

when I finished reading this passage, what I thought at that moment was: what are lexical scope , name binding, and first-class functions

The following is a description of these two terms:

lexical scope

reference:https://stackoverflow.com/questions/1047454/what-is-lexical-scope

First, lexical scope (also called static scope), in C-like syntax:

void func()
{
    int x = 5;
    return;
    void func2()
    {
        printf("%d\n", x);
    }
}

Every inner level can access its outer levels.

So here we can try to give some possible explanations:

  1. Lexical Scope refers to the grammatical rules of a programming language, first it is a scope, then it defines how variable names are resolved in nested functions in a programming language that support lexical scope: inner functions contain the scope of parent functions even if the parent function has returned.
  2. lexical scope also means that a variable defined outside your scope or upper scope is automatically available inside your scope which means you don't need to pass it there. like x is automatically available to func2()

name binding

Here is the explanation of name binding:

In program language, name binding is the association of entities(data and/or code) with identifiers. An identifier bound to an object is said to reference that object. Binding is intimately connected with scoping, as scope determines which names bind to which objects - at which locations in the program code (lexically). and in which one of the possible execution paths(temporally).

and the lexically scoped name binding means static binding in c++, here are some details about Static Binding and Dynamic Binding

A Language with First-Class Functions

then what is a language with first-class functions

the definition of First-class Functions is: "In computer science, a programming language is said to have first-class functions if it treats functions as first-class citizens. This means the language supports passing functions as arguments to other functions, returning them as the values from other functions, as assigning them to variables or storing them in data structures.

But I prefer this explanation: First-class Functions are functions that can be treated like any other value. You can pass them to functions as arguments, return them from functions, and save them in variables.

//code examples in c++

//"[](int a, int b){ return a*b; }" actually called a lamda expression
// it will change to a copy of the function closure which insatantiation by the lamda
// we can see it as an anonymous function here
// we will talk about it in more detail later

//an argument of a function, 
std::accumulate(vec.begin(), vec.end(), 1, [](int a, int b){ return a*b; });

//returned from a function
std::function<int (int, int)> madkAdd() {
    return [](int a, int b){ return a + b; };
}

std::function<int (int, int)> add = makeAdd();
add(2021, 1);//2022

//assign it to variable
auto f = [](int x){ std::cout << "x = " << endl;};
f(10);//x = 10

here are some more details:

https://www.modernescpp.com/index.php/first-class-functions

https://lispcast.com/what-are-first-class-functions/

Closure and Lamda expression in c++

Go back to the previous question: why function object is not exactly a closure in c++?

As C++ does not allow defining functions and objects inside a function, function object does not (always) allow lexical scoping, where with lexical scope, a name always refers to its (more or less) local lexical environment.

So what is closure in C++?

The reference link is here

ok now we know what is the definition of closure , and we can see in the First-class functions part, we see these two statements:

auto f = [](int x){std::cout << "x = " << x << endl;};
f(10);//x = 10

In here, the expression [](int x){std::cout << "x = " << x << endl;} is a lamda expression, and it is just that: an expression. As such, it exists only in a program's source code. it does not exists at runtime, and the runtime object created by that expression is the closure. f is just a copy of the closure. The actual closure object is a temporary that's typically destroyed at the end of the statement.

Scott Meyers got a good explanation to this using analogues. The distinction between a lamda and the corresponding closure is precisely equivalent to the distinction between a class and an instance of the class. A class exists only exists in source code; it doesn't exists at runtime. What exists at runtime are objects of the class type. Closures are to lamdas as objects are to classes, because each lamda expression causes a unique class to be generated(during compilation) and also causes an object of that class type--a closure--to be created (at runtime).

The exception to this rule is when you bind the closure to a reference. The simplest way to do that is to employ a universal reference

//right value reference
auto&& rrefToClosure = [&](int x, int y) { return 2 * (x + y); };
std::cout << "rrefToClosure(1, 2) = " << rrefToClosure(1, 2) << std::endl; //rrefToClosre(1, 2) = 6

but binding it to an lvalue-reference-to-const will also work:

//left value reference to const
const auto& lrefToConstToClosure = [&](int x, int y) { return 3 * (x + y); };
std::cout << "lrefToClosure(1, 2) = " << lrefToClosure(1, 2) << std::endl; //lrefToClosre(1, 2) = 9

the difference between right reference and left reference is as follows:

reference link are here

void printReference (const string& str)
{
    cout << str;
}

void printReference (string&& str)
{
    cout << str;
}

The first option can take lvalues because it's an lvalue reference. It can take rvalues because it is marked const and rvalues are allowed to bind to const lvalue references.

The second version is only allowed non-const rvalues because you can't implicitly strip const from the referencee and rvalue references don't allow lvalues to bind to them.

The semantic difference is that the former(lref) function is saying "I am just going to read what you pass in here and I'd rather not copy it", whereas the latter(rref) is saying "I reserve the right to rip the guts out of this object and paint my living room with them".

标签:function,closure,what,int,lexical,functions,scope,class
来源: https://www.cnblogs.com/christopherJames/p/chrisjames.html