development

함수 포인터, 클로저 및 Lambda

big-blog 2020. 9. 18. 18:51
반응형

함수 포인터, 클로저 및 Lambda


지금 막 함수 포인터에 대해 배우고 있습니다. 주제에 대한 K & R 장을 읽으면서 가장 먼저 느꼈던 것은 "이봐, 이건 마치 종결과 같다"는 것이었다. 나는이 가정이 근본적으로 잘못되었다는 것을 알았고 온라인에서 검색 한 후이 비교에 대한 분석을 실제로 찾지 못했습니다.

그렇다면 C 스타일 함수 포인터가 클로저 또는 람다와 근본적으로 다른 이유는 무엇입니까? 내가 말할 수있는 한, 함수 포인터가 함수를 익명으로 정의하는 관행과는 반대로 정의 된 (명명 된) 함수를 가리키고 있다는 사실과 관련이 있습니다.

함수에 함수를 전달하는 것이 이름이 지정되지 않은 두 번째 경우에서 전달되는 정상적인 일상적인 함수 인 첫 번째 경우보다 더 강력한 것으로 보이는 이유는 무엇입니까?

두 가지를 그렇게 면밀히 비교하는 것이 어떻게 그리고 왜 잘못된 것인지 말해주세요.

감사.


람다 (또는 클로저 )는 함수 포인터와 변수를 모두 캡슐화합니다. 이것이 C #에서 다음을 수행 할 수있는 이유입니다.

int lessThan = 100;
Func<int, bool> lessThanTest = delegate(int i) {
   return i < lessThan;
};

익명 델리게이트를 클로저로 사용했습니다 (구문이 람다에 해당하는 것보다 조금 더 명확하고 C에 더 가깝습니다). 클로저에 lessThan (스택 변수)을 캡처했습니다. 클로저가 평가 될 때 lessThan (스택 프레임이 파괴되었을 수있는)은 계속 참조 될 것입니다. lessThan을 변경하면 비교를 변경합니다.

int lessThan = 100;
Func<int, bool> lessThanTest = delegate(int i) {
   return i < lessThan;
};

lessThanTest(99); // returns true
lessThan = 10;
lessThanTest(99); // returns false

C에서 이것은 불법입니다.

BOOL (*lessThanTest)(int);
int lessThan = 100;

lessThanTest = &LessThan;

BOOL LessThan(int i) {
   return i < lessThan; // compile error - lessThan is not in scope
}

2 개의 인수를받는 함수 포인터를 정의 할 수 있지만 :

int lessThan = 100;
BOOL (*lessThanTest)(int, int);

lessThanTest = &LessThan;
lessThanTest(99, lessThan); // returns true
lessThan = 10;
lessThanTest(100, lessThan); // returns false

BOOL LessThan(int i, int lessThan) {
   return i < lessThan;
}

그러나 이제 나는 그것을 평가할 때 2 개의 인수를 전달해야합니다. 이 함수 포인터를 lessThan이 범위에 포함되지 않은 다른 함수에 전달하려면 체인의 각 함수에 전달하거나 글로벌로 승격하여 수동으로 유지해야합니다.

클로저를 지원하는 대부분의 주류 언어는 익명 함수를 사용하지만 이에 대한 요구 사항은 없습니다. 익명 함수없이 클로저를 가질 수 있고 클로저없이 익명 함수를 가질 수 있습니다.

요약 : 클로저는 함수 포인터 + 캡처 된 변수의 조합입니다.


'실제'클로저를 사용하거나 사용하지 않는 언어에 대한 컴파일러를 작성한 사람으로서 위의 답변 중 일부에 정중하게 동의하지 않습니다. Lisp, Scheme, ML 또는 Haskell 클로저 는 새 함수를 동적으로 생성하지 않습니다 . 대신 기존 함수를 재사용 하지만 새로운 자유 변수를 사용 합니다. 자유 변수 모음은 적어도 프로그래밍 언어 이론가에 의해 종종 환경 이라고 불립니다 .

클로저는 함수와 환경을 포함하는 집계 일뿐입니다. New Jersey 컴파일러의 표준 ML에서 우리는 하나를 레코드로 표현했습니다. 한 필드에는 코드에 대한 포인터가 포함되어 있고 다른 필드에는 자유 변수 값이 포함되어 있습니다. 컴파일러 동일한 코드에 대한 포인터를 포함 하지만 자유 변수에 대해 다른 값을 포함하는 새 레코드를 할당하여 동적으로 새 클로저 (함수 아님)를 생성했습니다 .

이 모든 것을 C로 시뮬레이션 할 수 있지만 엉덩이에 통증이 있습니다. 두 가지 기술이 널리 사용됩니다.

  1. 함수 (코드)에 대한 포인터와 자유 변수에 대한 별도의 포인터를 전달하여 클로저가 두 C 변수로 분할되도록합니다.

  2. 구조체에 대한 포인터를 전달합니다. 여기서 구조체에는 자유 변수의 값과 코드에 대한 포인터가 포함됩니다.

기술 # 1은 C에서 어떤 종류의 다형성 을 시뮬레이션하려고 하고 환경의 유형을 밝히고 싶지 않을 때 이상적 입니다. 환경을 나타 내기 위해 void * 포인터를 사용합니다. 예를 들어 Dave Hanson의 C 인터페이스 및 구현을 참조하십시오 . 기능 언어에 대한 네이티브 코드 컴파일러에서 발생하는 것과 더 유사한 기술 # 2는 또 다른 익숙한 기술인 가상 멤버 함수가있는 C ++ 객체와 유사합니다. 구현은 거의 동일합니다.

이 관찰은 Henry Baker의 현명한 균열로 이어졌습니다.

Algol / Fortran 세계의 사람들은 미래의 효율적인 프로그래밍에서 가능한 사용 함수 클로저가 무엇인지 이해하지 못했다고 수년간 불평했습니다. 그런 다음 '객체 지향 프로그래밍'혁명이 일어 났고 이제는 모든 프로그램이 함수 클로저를 사용하는 것을 제외하고는 여전히이를 호출하기를 거부합니다.


C에서는 함수를 인라인으로 정의 할 수 없으므로 실제로 클로저를 만들 수 없습니다. 당신이하는 일은 미리 정의 된 메서드에 대한 참조를 전달하는 것뿐입니다. 익명 메서드 / 클로저를 지원하는 언어에서는 메서드 정의가 훨씬 더 유연합니다.

In the simplest terms, function pointers have no scope associated with them (unless you count the global scope), whereas closures include the scope of the method that's defining them. With lambdas, you can write a method that writes a method. Closures allow you to bind "some arguments to a function and getting a lower-arity function as a result." (taken from Thomas's comment). You can't do that in C.

EDIT: Adding an example (I'm going to use Actionscript-ish syntax cause that's what's on my mind right now):

Say you have some method that takes another method as its argument, but doesn't provide a way to pass any parameters to that method when it's called? Like, say, some method that causes a delay before running the method you passed it (stupid example, but I want to keep it simple).

function runLater(f:Function):Void {
  sleep(100);
  f();
}

Now say you want to user runLater() to delay some processing of an object:

function objectProcessor(o:Object):Void {
  /* Do something cool with the object! */
}

function process(o:Object):Void {
  runLater(function() { objectProcessor(o); });
}

The function you're passing to process() isn't some staticly defined function anymore. It's dynamically generated, and is able to include references to variables that were in scope when the method was defined. So, it can access 'o' and 'objectProcessor', even though those aren't in the global scope.

I hope that made sense.


Closure = logic + environment.

For instance, consider this C# 3 method:

public Person FindPerson(IEnumerable<Person> people, string name)
{
    return people.Where(person => person.Name == name);
}

The lambda expression not only encapsulates the logic ("compare the name") but also the environment, including the parameter (i.e. local variable) "name".

For more on this, have a look at my article on closures which takes you through C# 1, 2 and 3, showing how closures make things easier.


In C, function pointers can be passed as arguments to functions and returned as values from functions, but functions exist only at top level: you cannot nest function definitions within each other. Think about what it would take for C to support nested functions that can access the variables of the outer function, while still being able to send function pointers up and down the call stack. (To follow this explanation, you should know the basics of how function calls are implemented in C and most similar languages: browse through the call stack entry on Wikipedia.)

What kind of object is a pointer to a nested function? It cannot just be the address of the code, because if you call it, how does it access the variables of the outer function? (Remember that because of recursion, there may be several different calls of the outer function active at one time.) This is called the funarg problem, and there are two subproblems: the downward funargs problem and the upwards funargs problem.

The downwards funargs problem, i.e., sending a function pointer "down the stack" as an argument to a function you call, is actually not incompatible with C, and GCC supports nested functions as downward funargs. In GCC, when you create a pointer to a nested function, you really get a pointer to a trampoline, a dynamically constructed piece of code that sets up the static link pointer and then calls the real function, which uses the static link pointer to access the variables of the outer function.

The upwards funargs problem is more difficult. GCC does not prevent you from letting a trampoline pointer exist after the outer function is no longer active (has no record on the call stack), and then the static link pointer could point to garbage. Activation records can no longer be allocated on a stack. The usual solution is to allocate them on the heap, and let a function object representing a nested function just point to the activation record of the outer function. Such an object is called a closure. Then the language will typically have to support garbage collection so that the records can be freed once there are no more pointers pointing to them.

Lambdas (anonymous functions) are really a separate issue, but usually a language that lets you define anonymous functions on the fly will also let you return them as function values, so they end up being closures.


A lambda is an anonymous, dynamically defined function. You just cannot do that in C... as for closures (or the convination of the two), the typical lisp example would look something along the lines of:

(defun get-counter (n-start +-number)
     "Returns a function that returns a number incremented
      by +-number every time it is called"
    (lambda () (setf n-start (+ +-number n-start))))

In C terms, you could say that the lexical environment (the stack) of get-counter is being captured by the anonymous function, and modified internally as the following example shows:

[1]> (defun get-counter (n-start +-number)
         "Returns a function that returns a number incremented
          by +-number every time it is called"
        (lambda () (setf n-start (+ +-number n-start))))
GET-COUNTER
[2]> (defvar x (get-counter 2 3))
X
[3]> (funcall x)
5
[4]> (funcall x)
8
[5]> (funcall x)
11
[6]> (funcall x)
14
[7]> (funcall x)
17
[8]> (funcall x)
20
[9]> 

Closures imply some variable from the point of function definition is bound together with the function logic, like being able to declare a mini-object on the fly.

One important problem with C and closures is variables allocated on the stack will be destroyed on leaving the current scope, regardless of if a closure was pointing to them. This would lead to the kind of bugs people get when they carelessly return pointers to local variables. Closures basically imply all relevant variables are either ref-counted or garbage-collected items on a heap.

I'm not comfortable equating lambda with closure because I'm not sure that lambdas in all languages are closures, at times I think lambdas have just been locally defined anonymous functions without the binding of variables (Python pre 2.1?).


In GCC it is possible to simulate lambda functions using the following macro:

#define lambda(l_ret_type, l_arguments, l_body)       \
({                                                    \
    l_ret_type l_anonymous_functions_name l_arguments \
    l_body                                            \
    &l_anonymous_functions_name;                      \
})

Example from source:

qsort (array, sizeof (array) / sizeof (array[0]), sizeof (array[0]),
     lambda (int, (const void *a, const void *b),
             {
               dump ();
               printf ("Comparison %d: %d and %d\n",
                       ++ comparison, *(const int *) a, *(const int *) b);
               return *(const int *) a - *(const int *) b;
             }));

Using this technique of course removes the possibility of your application working with other compilers and is apparently "undefined" behavior so YMMV.


The closure captures the free variables in an environment. The environment will still exist, even though the surrounding code may no longer be active.

An example in Common Lisp, where MAKE-ADDER returns a new closure.

CL-USER 53 > (defun make-adder (start delta) (lambda () (incf start delta)))
MAKE-ADDER

CL-USER 54 > (compile *)
MAKE-ADDER
NIL
NIL

Using the above function:

CL-USER 55 > (let ((adder1 (make-adder 0 10))
                   (adder2 (make-adder 17 20)))
               (print (funcall adder1))
               (print (funcall adder1))
               (print (funcall adder1))
               (print (funcall adder1))
               (print (funcall adder2))
               (print (funcall adder2))
               (print (funcall adder2))
               (print (funcall adder1))
               (print (funcall adder1))
               (describe adder1)
               (describe adder2)
               (values))

10 
20 
30 
40 
37 
57 
77 
50 
60 
#<Closure 1 subfunction of MAKE-ADDER 4060001ED4> is a CLOSURE
Function         #<Function 1 subfunction of MAKE-ADDER 4060001CAC>
Environment      #(60 10)
#<Closure 1 subfunction of MAKE-ADDER 4060001EFC> is a CLOSURE
Function         #<Function 1 subfunction of MAKE-ADDER 4060001CAC>
Environment      #(77 20)

Note that the DESCRIBE function shows that the function objects for both closures are the same, but the environment is different.

Common Lisp makes both closures and pure function objects (those without an environment) both to be functions and one can call both in the same way, here using FUNCALL.


The main difference arises from the lack of lexical scoping in C.

A function pointer is just that, a pointer to a block of code. Any non-stack variable that it references is global, static or similar.

A closure, OTOH, has its own state in the form of 'outer variables', or 'upvalues'. they can be as private or shared as you want, using lexical scoping. You can create lots of closures with the same function code, but different variables instances.

A few closures can share some variables, and so can be the interface of an object (in the OOP sense). to make that in C you have to associate a structure with a table of function pointers (that's what C++ does, with a class vtable).

in short, a closure is a function pointer PLUS some state. it's a higher-level construct


Most of the responses indicate that closures require function pointers, possibly to anonymous functions, but as Mark wrote closures can exist with named functions. Here's an example in Perl:

{
    my $count;
    sub increment { return $count++ }
}

The closure is the environment that defines the $count variable. It is only available to the increment subroutine and persists between calls.


In C a function pointer is a pointer that will invoke a function when you dereference it, a closure is a value that contains a function's logic and the environment (variables and the values they are bound to) and a lambda usually refers to a value that is actually an unnamed function. In C a function is not a first class value so it cannot be passed around so you have to pass a pointer to it instead, however in functional languages (like Scheme) you can pass functions in the same way you pass any other value

참고URL : https://stackoverflow.com/questions/208835/function-pointers-closures-and-lambda

반응형