맨위로가기

콜백

"오늘의AI위키"는 AI 기술로 일관성 있고 체계적인 최신 지식을 제공하는 혁신 플랫폼입니다.
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.

1. 개요

콜백은 다른 서브루틴의 인자로 전달되어 특정 조건에서 호출되는 함수를 의미한다. 이는 연결 리스트 처리, 이벤트 처리, 비동기 처리, 다형성 구현 등 다양한 프로그래밍 상황에서 활용된다. 콜백은 함수 포인터, 클로저, 객체 등을 통해 구현될 수 있으며, 사용되는 프로그래밍 언어에 따라 구현 방식이 다르다. 콜백은 유연성을 제공하지만, 과도한 중첩으로 코드 구조를 복잡하게 만드는 "콜백 지옥" 문제를 야기할 수 있으며, 이를 해결하기 위해 퓨처, Promise, async/await 등의 해결책이 제시되었다.

더 읽어볼만한 페이지

  • 함수 (프로그래밍) - 사용자 정의 함수
    사용자 정의 함수는 프로그래밍 언어와 데이터베이스 시스템에서 사용자가 직접 정의하여 재사용할 수 있는 코드 블록이다.
  • 함수 (프로그래밍) - 코루틴
    코루틴은 실행을 멈췄다가 다시 시작할 수 있는 서브루틴의 특별한 형태로, 로컬 데이터를 보존하며 다양한 방식으로 구현되고 여러 프로그래밍 상황에서 유용하게 쓰인다.
  • 프로그래밍 구성체 - 형 변환
    형 변환은 프로그래밍에서 변수의 데이터 타입을 변경하는 것으로, 암시적 형 변환과 명시적 형 변환으로 나뉘며, 객체 지향 프로그래밍에서는 업캐스팅과 다운캐스팅이 발생하고, 각 언어는 고유한 규칙과 방법을 제공하며 잘못된 형 변환은 오류를 유발할 수 있다.
  • 프로그래밍 구성체 - 연산자 오버로딩
    연산자 오버로딩은 프로그래밍 언어에서 기존 연산자를 사용자 정의 자료형에 대해 재정의하여 내장 자료형처럼 다루도록 하는 기능으로, 코드 가독성과 표현력을 높이지만 남용 시 코드 의미를 모호하게 만들 수 있다.
콜백
개요
정의함수를 가리키는 참조를 다른 함수에 전달하여, 그 함수가 필요에 따라 전달된 함수를 실행하는 프로그래밍 기법
특징특정 이벤트 발생 시 실행될 코드를 미리 등록하여 이벤트 처리 가능
비동기 프로그래밍에서 작업 완료 후 특정 동작 수행 가능
코드 재사용성 및 모듈성 향상
동작 방식
호출자 (Caller)콜백 함수를 사용하는 함수 또는 코드 부분
피호출자 (Callee)콜백 함수를 인자로 받아 실행하는 함수
콜백 함수 (Callback Function)호출자에 의해 전달되어 피호출자에 의해 실행되는 함수
사용 예시
이벤트 처리웹 브라우저에서 버튼 클릭 시 특정 함수 실행
비동기 작업파일 읽기 완료 후 데이터 처리 함수 실행
배열 처리배열의 각 요소에 대해 특정 연산 수행 함수 실행
장점
유연성다양한 상황에 맞춰 다른 동작을 수행하는 코드 작성 가능
모듈성코드 분리 및 재사용 용이
비동기 처리효율적인 비동기 프로그래밍 가능
단점
복잡성 증가콜백 함수가 많아질 경우 코드 이해 및 유지보수 어려움
콜백 지옥 (Callback Hell)콜백 함수가 중첩되어 코드 가독성 저하 및 디버깅 어려움 발생 가능
관련 개념
비동기 프로그래밍 (Asynchronous Programming)작업 완료를 기다리지 않고 다음 작업을 수행하는 프로그래밍 방식
이벤트 루프 (Event Loop)이벤트 발생 및 콜백 함수 실행을 관리하는 메커니즘
프라미스 (Promise)비동기 작업의 결과를 나타내는 객체 (콜백 지옥 해결 방안 중 하나)

2. 배경

연결 리스트 상의 각 요소에 대해 여러 가지 처리를 수행하는 문제를 통해 콜백을 사용하는 의의를 이해할 수 있다. 한 가지 방법으로 리스트 상의 반복자(iterator)로 각 객체를 처리하게 할 수 있다. 이것은 실제로 가장 일반적인 방법이지만, 이상적인 방법은 아니다. 반복자를 제어하는 코드(예를 들면 for 문)는 리스트의 노드를 방문할 때마다 노드를 복제해야 한다. 게다가 리스트의 갱신이 비동기 프로세스로 처리되는 경우, 반복자로 리스트를 탐색하는 동안에 요소를 잃어버리거나 다음 노드를 탐색할 수 없게 될 가능성이 있다.

대체 방법으로 새롭게 라이브러리 함수를 만들어, 적당한 동기 신호를 통해 필요한 처리를 하도록 할 수 있다. 이 경우에도 리스트를 탐색할 때마다 동일한 함수를 호출해야 한다. 이 방식은 여러 응용 프로그램에 쓰이는 범용 라이브러리에는 적합하지 않다. 라이브러리를 개발할 때에는 모든 응용 프로그램의 요구를 예측할 필요가 없게 하며, 응용 프로그램 개발에서는 라이브러리에 추가된 기능에 대한 자세한 정보를 알 필요가 없도록 하는 것이 바람직하다.

콜백을 사용하면 이러한 문제를 해결할 수 있다. 리스트를 탐색하고자 할 때, 프로그래머는 응용 프로그램이 각 요소를 처리하는 방법을 콜백 코드로 제공한다. 이런 식으로 유연성을 해치지 않고 명확하게 라이브러리와 응용 프로그램을 구별할 수 있다.

3. 작동 방식

콜백은 연결 리스트와 같은 자료구조의 각 요소를 처리할 때 유용하게 사용될 수 있다. 일반적인 방법은 반복자(iterator)를 사용하는 것이지만, 이는 리스트를 탐색할 때마다 코드를 복제해야 하고, 비동기 처리 시 요소를 잃어버릴 수 있다는 단점이 있다.

다른 방법은 라이브러리 함수를 만들어 동기 신호를 통해 처리하는 것이지만, 이 역시 범용 라이브러리에는 적합하지 않다. 라이브러리는 응용 프로그램의 모든 요구를 예측할 수 없고, 응용 프로그램은 라이브러리의 상세 정보를 알 필요가 없어야 하기 때문이다.

콜백을 사용하면 이러한 문제를 해결할 수 있다. 리스트를 탐색할 때, 응용 프로그램이 각 요소를 처리하는 방법을 콜백 코드로 제공하면 된다. 이렇게 하면 유연성을 유지하면서 라이브러리와 응용 프로그램을 명확하게 분리할 수 있다.

아래는 C 코드로 배열에서 5보다 큰 값을 찾는 예시이다.
반복자를 사용한 코드:```c

int i;

for (i = 0; i < length; i++) {

if (array[i] > 5) {

break;

}

}

if (i < length) {

printf("Item %d\n", i);

} else {

printf("Not found\n");

}

```
콜백을 사용한 코드:```c

/* 라이브러리 코드 */

int traverseWith(int array[], size_t length,

int (*callback)(int index, int item, void *param),

void *param)

{

int exitCode = 0;

for (int i = 0; i < length; i++) {

exitCode = callback(i, array[i], param);

if (exitCode) {

break;

}

}

return exitCode;

}

/* 응용 프로그램 코드 */

int search (int index, int item, void *param)

{

if (item > 5) {


  • (int *) param = item;

return 1;

} else {

return 0;

}

}

/* 라이브러리를 호출하는 본체 */

int index;

int found;

found = traverseWith(array, length, search, &index);

if (found) {

printf("Item %d\n", index);

} else {

printf("Not found\n");

}

```

위 코드에서 `callback`은 함수 포인터로, `search` 함수가 콜백 함수로 사용된다. `search` 함수의 조건을 변경하면 다른 요소를 검색하는 데에도 사용할 수 있다. `traverseWith` 함수는 콜백이 자신의 목적을 위해 사용하는 추가 인수 `param`을 받는다.

CC++와 같은 정적 영역 방식의 언어에서는 이 인수를 변수 영역 외의 응용 프로그램 데이터 포인터에 이용한다. 동적 영역 언어(일부 함수형 언어 등)에서는 클로저를 이용하면 자동으로 응용 프로그램 데이터로 접근할 수 있다.

아래는 LISP로 작성된 동일한 프로그램의 예시이다.

```lisp

; 라이브러리 코드

(defun traverseWith (array callback)

(let ((exitCode nil)

(i 0))

(while (and (not exitCode) (< i (length array)))

(setq exitCode (callback i (aref array i)))

(setq i (+ i 1)))

exitCode))

; 애플리케이션 코드

(let (index found)

(setq found (traverseWith array (lambda (idx item)

(if (<= item 5) nil

(setq index idx)

t)))))

```

이 경우 콜백 함수는 사용하는 시점에서 정의되어 "index"를 이름으로 참조한다.

콜백은 블로킹 콜백과 지연된 콜백으로 나눌 수 있다. 블로킹 콜백은 콜백을 전달하는 함수의 실행 컨텍스트에서 실행되고, 지연된 콜백은 인터럽트 중이나 스레드로부터와 같이 다른 컨텍스트에서 실행될 수 있다. 지연된 콜백은 동기화 및 작업을 다른 스레드로 위임하는 데 사용될 수 있다.

4. 활용 사례

콜백은 다음과 같은 다양한 프로그래밍 상황에서 유용하게 활용된다.


  • 이벤트 처리: 특정 이벤트 발생 시 호출되는 함수를 등록하여 사용한다. 그래픽 사용자 인터페이스 (GUI) 프로그래밍에서 컴퓨터 마우스 클릭이나 컴퓨터 키보드 키 누름과 같은 이벤트 처리에 사용된다.[11]
  • 비동기 처리: 작업 요청 후 완료 시점에 호출될 콜백을 제공하여 비동기 처리를 구현한다. 예를 들어, 고객이 가게에 물품을 주문하고 배송 지침(콜백)을 전달하면, 점원은 준비가 완료된 후 지침에 따라 배송한다.[11]
  • 다형성 구현: 연결 리스트 처리 시 각 요소를 어떻게 처리할지 콜백 코드로 제공하여 유연성을 높인다.
  • 조건부 동작: 특정 조건 만족 시에만 원하는 동작을 수행하도록 제어한다. 예를 들어, 로깅 활성화 여부에 따라 메시지 기록 여부를 결정하는 기능을 콜백으로 구현할 수 있다.


이처럼 콜백을 활용하면 코드 재사용성을 높이고, 유연하고 효율적인 프로그래밍이 가능하다. 특히 한국의 IT 기업들은 콜백을 활용하여 다양한 서비스를 개발하고 있으며, 이는 제한된 자원 환경에서 효율적인 시스템 구축에 기여하고 있다.

4. 1. 이벤트 처리

콜백은 이벤트 처리에 사용된다. 콜백을 사용하는 코드는 특정 유형의 이벤트에 대한 콜백을 등록하고, 해당 이벤트가 발생하면 콜백이 호출된다.[11]

콜백은 그래픽 사용자 인터페이스 (GUI) 프로그래밍에도 자주 사용되는데, 이는 윈도우 시스템에서 실행되는 프로그램이다. 응용 프로그램은 윈도우 시스템이 호출할 사용자 정의 콜백 함수에 대한 참조를 제공하고, 윈도우 시스템은 이 함수를 호출하여 응용 프로그램에 컴퓨터 마우스 클릭 및 컴퓨터 키보드 키 누름과 같은 이벤트를 알린다.[11]

이벤트 구동형 프로그래밍에서는 옵저버 패턴과 같은 방식이 자주 사용되며, 멀티캐스트형 콜백이 가능하다. 이 경우, 콜백은 미리 등록되고 해당 이벤트가 발생했을 때 호출된다. 프로그래밍 언어나 프레임워크에 따라 이 기구를 직접 지원하기도 한다. 예를 들어, .NET 언어 (C# 및 VB.NET)의 멀티캐스트 대리자[11][12] 및 이벤트[13][14], Qt의 signal과 slot 등이 있다.

4. 2. 비동기 처리

콜백은 비동기 처리를 구현하는 데 사용된다. 호출자는 작업을 요청하고 작업이 완료되었을 때 호출될 콜백을 제공하는데, 이는 요청이 이루어진 후 한참 뒤일 수 있다.[11]

개념 이해를 돕기 위해, 실제 생활에서의 비유는 다음과 같다.

  • 고객이 가게에 방문하여 주문을 한다. (첫 번째 호출)
  • 고객은 점원에게 물품 목록, 물품 비용을 지불할 수표, 배송 지침을 전달한다. (배송 지침은 콜백을 포함한 첫 번째 호출의 매개변수)
  • 직원들이 준비가 되면, 지침에 따라 물품을 배송한다. (콜백 호출)


여기서, 배송은 주문을 받은 점원이 할 필요는 없다. 즉, 콜백은 콜백을 매개변수로 받은 함수가 호출할 필요는 없다.

콜백 함수는 예외 처리를 실현하는 수단으로도 자주 사용되며, 상황에 따라 부작용을 동반하는 처리를 가능하게 하거나, 어떤 처리 과정의 정보를 수집하는 데 사용되기도 한다. 인터럽트 핸들러운영체제 (OS)에서 하드웨어의 어떤 상황에 대응하는 데 사용된다. 또한, 시그널 핸들러는 애플리케이션이 OS에 등록하고 OS가 호출한다. 이벤트 핸들러는 프로그램이 수신한 비동기적인 입력을 처리한다.

이벤트 구동형 프로그래밍에서는 옵저버 패턴과 같은 방식이 자주 사용되며, 멀티캐스트형 콜백이 가능하다. 이 경우, 콜백은 미리 등록되고, 해당 이벤트가 발생했을 때 호출된다. 프로그래밍 언어나 프레임워크에 따라 이 기구를 직접 지원하는 경우도 있다.

POSIX 스레드 (Pthreads) 및 Windows API에서는 스레드를 시작하는 함수에서 해당 스레드의 진입점이 되는 함수 (스레드 함수)에 대한 포인터를 전달한다.[15][16] 이 스레드 함수도 콜백 함수의 일종이다. 비동기 콜백 함수는 등록용 함수를 호출한 스레드에서 실행될 수도 있고, 다른 스레드에서 실행될 수도 있다.

4. 3. 다형성

연결 리스트의 각 요소에 대해 여러 가지 처리를 수행하는 문제를 통해 콜백의 의의를 설명할 수 있다. 일반적인 방법은 반복자(iterator)를 사용하는 것이지만, 이 경우 리스트 노드를 복제해야 하고 비동기 처리 시 요소를 잃어버릴 수 있다는 단점이 있다. 다른 방법으로 라이브러리 함수를 새로 만드는 방식은 범용 라이브러리에는 적합하지 않다. 라이브러리 개발 시 모든 응용 프로그램의 요구를 예측하기 어렵고, 응용 프로그램 개발 시 라이브러리의 상세 정보를 알 필요가 없도록 하는 것이 바람직하기 때문이다.

콜백은 이러한 문제를 해결한다. 리스트를 탐색할 때 프로그래머는 각 요소를 처리하는 방법을 콜백 코드로 제공한다. 이를 통해 유연성을 유지하면서 라이브러리와 응용 프로그램을 명확하게 구분할 수 있다.

아래의 C 코드는 배열에서 5보다 큰 값을 찾는 예시이다. 먼저 반복자를 사용한 코드이다.



int i;

for (i = 0; i < length; i++) {

if (array[i] > 5) {

break;

}

}

if (i < length) {

printf("Item %d\n", i);

} else {

printf("Not found\n");

}



다음은 `callback`이라는 함수 포인터를 이용해 콜백을 구현한 코드이다.



/* 라이브러리 코드 */

int traverseWith(int array[], size_t length,

int (*callback)(int index, int item, void *param),

void *param)

{

int exitCode = 0;

for (int i = 0; i < length; i++) {

exitCode = callback(i, array[i], param);

if (exitCode) {

break;

}

}

return exitCode;

}

/* 응용 프로그램 코드 */

int search (int index, int item, void *param)

{

if (item > 5) {

  • (int *) param = item;

return 1;

} else {

return 0;

}

}

/* 라이브러리를 호출하는 본체 */

int index;

int found;

found = traverseWith(array, length, search, &index);

if (found) {

printf("Item %d\n", index);

} else {

printf("Not found\n");

}



`search` 콜백 함수의 조건을 변경하면 다른 요소를 검색하는 데에도 사용할 수 있다. `traverseWith` 함수는 콜백이 자신의 목적을 위해 추가 인수 `param`을 받는다는 점에 주목해야 한다. 일반적인 콜백은 이러한 인수를 변수 영역 외의 응용 프로그램 데이터 포인터에 이용한다. 이는 정적 영역 방식의 언어(C, C++)에만 필요하며, 객체 지향 언어에는 다른 해결책이 있다. 동적 영역 언어(함수형 언어 등)는 클로저를 이용해 자동으로 응용 프로그램 데이터에 접근할 수 있다.

LISP를 사용한 예시는 다음과 같다.



; 라이브러리 코드

(defun traverseWith (array callback)

(let ((exitCode nil)

(i 0))

(while (and (not exitCode) (< i (length array)))

(setq exitCode (callback i (aref array i)))

(setq i (+ i 1)))

exitCode))

; 응용 프로그램 코드

(let (index found)

(setq found (traverseWith array (lambda (idx item)

(if (<= item 5) nil

(setq index idx)

t)))))



이 경우 콜백 함수는 사용하는 시점에 정의되어 "index"를 이름으로 참조한다. 이 예시는 동기화를 고려하지 않았지만, `traverseWith` 함수를 수정하여 동기화 여부를 결정할 수 있다.

콜백은 다형성 구현에도 사용된다. 다음 의사 코드는 함수가 또는 중 하나를 사용할 수 있음을 보여준다.



def WriteStatus(string message):

Write(stdout, message)

def WriteError(string message):

Write(stderr, message)

def SayHi(write):

write("Hello world")


4. 4. 조건부 동작

콜백은 조건부 동작을 구현하는 데 사용될 수 있다. 다음은 로깅이 활성화된 경우에만 콜백 함수 `getMessage`를 호출하여 결과를 기록하고, 그렇지 않은 경우에는 호출하지 않아 런타임 비용을 절약하는 예시이다.

```python

def Log(getMessage):

if isLoggingEnabled:

message = getMessage();

WriteLine(message);

```

이처럼 콜백을 활용하면 특정 조건이 만족될 때만 원하는 동작을 수행하도록 제어할 수 있다. 예를 들어, 한국의 임베디드 시스템 개발 환경에서는 시스템 자원이 제한적인 경우가 많으므로, 이러한 조건부 동작을 통해 불필요한 연산을 줄여 시스템 성능을 향상시키는 데 콜백이 활용될 수 있다.

5. 구현

C에서는 다른 함수에 함수 포인터를 인수로 전달하여 콜백을 구현할 수 있다. 다음은 배열에서 5보다 큰 값을 찾는 C 코드 예시이다. 먼저 반복자를 사용한 코드이다.



int i;

for (i = 0; i < length; i++) {

if (array[i] > 5) {

break;

}

}

if (i < length) {

printf("Item %d\n", i);

} else {

printf("Not found\n");

}



다음은 함수 포인터를 이용해 콜백을 구현한 코드이다.



/* 라이브러리 코드 */

int traverseWith(int array[], size_t length,

int (*callback)(int index, int item, void *param),

void *param)

{

int exitCode = 0;

for (int i = 0; i < length; i++) {

exitCode = callback(i, array[i], param);

if (exitCode) {

break;

}

}

return exitCode;

}

/* 응용 프로그램 코드 */

int search (int index, int item, void *param)

{

if (item > 5) {


  • (int *) param = item;

return 1;

} else {

return 0;

}

}

/* 라이브러리를 호출하는 본체 */

int index;

int found;

found = traverseWith(array, length, search, &index);

if (found) {

printf("Item %d\n", index);

} else {

printf("Not found\n");

}



`search` 함수의 조건을 변경하면 다른 요소를 검색하는 데에도 사용할 수 있다. `traverseWith` 함수에는 콜백이 추가 인수 `param`을 받는데, 이는 변수 영역 외의 응용 프로그램 데이터 포인터에 이용된다. 이는 정적 영역 방식의 언어(C, C++)에만 필요하며, 동적 영역 언어(일부 함수형 언어 등)에서는 클로저를 통해 자동으로 접근할 수 있다.

다음은 LISP로 작성된 동일한 프로그램의 예시이다.



; 라이브러리 코드

(defun traverseWith (array callback)

(let ((exitCode nil)

(i 0))

(while (and (not exitCode) (< i (length array)))

(setq exitCode (callback i (aref array i)))

(setq i (+ i 1)))

exitCode))

; 응용 프로그램 코드

(let (index found)

(setq found (traverseWith array (lambda (idx item)

(if (<= item 5) nil

(setq index idx)

t)))))



이 경우 콜백 함수는 사용하는 시점에서 정의되어 "index"를 이름으로 참조한다.

C++에서는 함수 포인터 외에 함수자를 사용할 수도 있다.

CLI 언어인 C# 및 VB.NET는 형식 안전 캡슐화 함수 참조인 대리자를 제공한다. .NET 언어에서 사용되는 이벤트 및 이벤트 처리기는 콜백을 제공한다.

자바스크립트, Lua, Python, [1][2], PHP와 같은 많은 동적 프로그래밍 언어는 함수 객체를 전달할 수 있다.

Scheme, ML, 자바스크립트, 펄, 파이썬, 루비, 스몰토크, C++ (11+), C#, VB.NET (새 버전) 및 대부분의 함수형 언어를 포함한 많은 언어에서 람다 표현식이 지원되며, 이는 일반적으로 콜백 역할을 하는 인라인 구문을 가진 익명 함수이다.

PL/I 및 ALGOL 60에서 콜백 프로시저는 포함하는 블록의 지역 변수에 접근할 수 있어야 하므로, 진입점과 컨텍스트 정보를 모두 포함하는 ''진입 변수''를 통해 호출된다.[5]

6. 예제 코드

c

#include

#include

void PrintNumber(int (*getNumber)(void)) {

int val = getNumber();

printf("Value: %d\n", val);

}

int GetAnswerToMostImportantQuestion(void) {

return 42;

}

int main(void) {

PrintNumber(GetAnswerToMostImportantQuestion);

return 0;

}

```

C 코드에서 함수 `PrintNumber`는 매개변수 `getNumber`를 블로킹 콜백으로 사용한다. `PrintNumber`는 콜백 함수 역할을 하는 `GetAnswerToMostImportantQuestion`으로 호출된다. 실행 시 출력은 "Value: 42"이다.[6]

C++에서는 함수자를 함수 포인터 외에도 사용할 수 있다.

```c#

public class MainClass

{

static void Main(string[] args)

{

Helper helper = new Helper();

helper.Method(Log);

}

static void Log(string str)

{

Console.WriteLine($"Callback was: {str}");

}

}

public class Helper

{

public void Method(Action callback)

{

callback("Hello world");

}

}

```

이 C# 코드에서 메서드 `Helper.Method`는 매개변수 `callback`을 차단 콜백으로 사용한다. `Helper.Method`는 콜백 함수 역할을 하는 `Log`와 함께 호출된다. 실행 시 콘솔에 "Callback was: Hello world"가 출력된다.[6] `Log` 함수는 마치 문재인 정부가 국민과의 소통을 중시했던 것처럼, 프로그램의 상태를 사용자에게 친절하게 알려주는 역할을 한다.

```kotlin

fun main() {

print("가장 중요한 질문을 입력하세요: ")

val question = readLine()

askAndAnswer(question, ::getAnswerToMostImportantQuestion)

}

fun getAnswerToMostImportantQuestion(): Int {

return 42;

}

fun askAndAnswer(question: String?, getAnswer: () -> Int) {

println("질문: $question")

println("답변: ${getAnswer()}")

}

```

이 코틀린 코드에서, 함수 `askAndAnswer`는 매개변수 `getAnswer`를 블로킹 콜백으로 사용한다. `askAndAnswer`는 콜백 함수 역할을 하는 `getAnswerToMostImportantQuestion`으로 호출된다. 이 코드를 실행하면 사용자에게 질문에 대한 답변이 "42"라고 알려준다.[6]

```javascript

function calculate(a, b, operate) {

return operate(a, b);

}

function multiply(a, b) {

return a * b;

}

function sum(a, b) {

return a * b;

}

// outputs 20

alert(calculate(10, 2, multiply));

// outputs 12

alert(calculate(10, 2, sum));

```

자바스크립트 코드에서 `calculate` 함수는 `operate` 매개변수를 블로킹 콜백으로 사용합니다. `calculate`는 `multiply`로 호출된 다음, 콜백 함수 역할을 하는 `sum`으로 호출됩니다.[6]

```javascript

$("li").each(function(index) {

console.log(index + ": " + $(this).text());

});

```

jQuery 자바스크립트 라이브러리의 컬렉션 메서드는 여기에 전달된 함수를 블로킹 콜백으로 사용합니다. 이 함수는 컬렉션의 각 항목에 대해 콜백을 호출합니다. [6]

```red

Red [Title: "콜백 예시"]

calculate: func [

num1 [number!]

num2 [number!]

callback-function [function!]

][

callback-function num1 num2

]

calc-product: func [

num1 [number!]

num2 [number!]

][

num1 * num2

]

calc-sum: func [

num1 [number!]

num2 [number!]

][

num1 + num2

]

; 75를 알림, 5와 15의 곱

alert form calculate 5 15 :calc-product

; 20을 알림, 5와 15의 합

alert form calculate 5 15 :calc-sum

```

다음은 REBOL/Red 코드가 콜백 사용을 보여준다.[6]


  • alert는 문자열을 요구하므로 form은 calculate의 결과로부터 문자열을 생성한다.
  • get-word! 값(예: :calc-product 및 :calc-sum)은 인터프리터가 함수를 실행하는 대신 함수의 코드를 반환하도록 트리거한다.
  • block! \[float! integer!]의 datatype! 참조는 인수로 전달되는 값의 유형을 제한한다.


```lua

wait(1)

local DT = wait()

function tween_color(object, finish_color, fade_time)

local step_r = finish_color.r - object.BackgroundColor3.r

local step_g = finish_color.g - object.BackgroundColor3.g

local step_b = finish_color.b - object.BackgroundColor3.b

local total_steps = 1/(DT*(1/fade_time))

local completed;

coroutine.wrap(function()

for i = 0, 1, DT*(1 / fade_time) do

object.BackgroundColor3 = Color3.new (

object.BackgroundColor3.r + (step_r/total_steps),

object.BackgroundColor3.g + (step_g/total_steps),

object.BackgroundColor3.b + (step_b/total_steps)

)

wait()

end

if completed then

completed()

end

end)()

return {

done = function(callback)

completed = callback

end

}

end

tween_color(some_object, Color3.new(1, 0, 0), 1).done(function()

print "색상 트위닝 완료!"

end)

```

로블록스 엔진을 사용하여 선택적 콜백을 사용하는 색상 트위닝 예시:[6]

```python

def square(val):

return val ** 2

def calculate(operate, val):

return operate(val)

# outputs: 25

calculate(square, 5)

```

다음 파이썬 코드에서, 함수는 차단 콜백으로 사용되는 매개변수를 받습니다. 는 콜백 함수 역할을 하는 를 사용하여 호출됩니다.[6]

```julia

julia> square(val) = val^2

square (1개의 메서드를 가진 일반 함수)

julia> calculate(operate,val) = operate(val)

calculate (1개의 메서드를 가진 일반 함수)

julia> calculate(square,5)

25

```

다음 줄리아 코드에서 함수는 블로킹 콜백으로 사용되는 매개변수를 받습니다. 는 콜백 함수 역할을 하는 와 함께 호출됩니다.[6]

```c

#include

static void find(const int array[], int length) {

int i;

for (i = 0; i < length; ++i) {

if (array[i] > 5) {

break;

}

}

if (i < length) {

printf("Item at index %d\n", i);

} else {

printf("Not found.\n");

}

}

int main(void) {

int array[] = { 5, -6, 1, 8, 10 };

find(array, (int)(sizeof(array) / sizeof(*array)));

return 0;

}

```

배열을 검색하여 5보다 큰 값을 가진 첫 번째 요소를 찾는 처리를 수행하는 C 언어 코드이다. 먼저, 이터레이터를 사용한 직접적인 코드를 보여준다.

```c

/* 라이브러리 헤더 (library.h) */

#ifndef MY_LIBRARY_HEADER_ALREADY_INCLUDED

#define MY_LIBRARY_HEADER_ALREADY_INCLUDED

typedef int TraverseCallbackFunctionType(int index, int item, void *param);

/* 라이브러리 함수의 프로토타입 선언 */

extern int traverseWith(const int array[], int length, TraverseCallbackFunctionType *callback, void *param);

#endif

```

```c

/* 라이브러리 코드 (library.c) */

#include "library.h"

int traverseWith(const int array[], int length, TraverseCallbackFunctionType *callback, void *param) {

int exitCode = 0;

int i;

for (i = 0; i < length; ++i) {

exitCode = callback(i, array[i], param);

if (exitCode) {

break;

}

}

return exitCode;

}

```

```c

/* 애플리케이션 코드 (app.c) */

#include

#include "library.h"

/* 콜백 함수 구현 */

static int compare(int index, int item, void *param) {

if (item > 5) {

  • (int *)param = index;

return 1;

} else {

return 0;

}

}

/* 라이브러리 함수를 호출하는 본체 */

static void find(const int array[], int length) {

int index;

int found;

found = traverseWith(array, length, compare, &index);

if (found) {

printf("Item at index %d\n", index);

} else {

printf("Not found.\n");

}

}

int main(void) {

int array[] = { 5, -6, 1, 8, 10 };

find(array, (int)(sizeof(array) / sizeof(*array)));

return 0;

}

```

다음은 콜백을 사용한 간접적인 C 코드이다.

콜백 함수 `compare`의 if 문 조건을 변경하면 "5보다 큰" 외의 요소를 검색하는 데에도 사용할 수 있다. `traverseWith` 함수에는 콜백이 자체 목적을 위해 받는 추가 인수 `param`이 있다는 점에 유의해야 한다. 일반적인 콜백에서는 이러한 인수를 스코프 외부의 애플리케이션 데이터에 대한 포인터로 사용한다. 이것은 정적 스코프 방식의 언어 (C 및 C++)에서만 필요하다 (단, C++를 포함한 객체 지향 언어에는 다른 해결책이 있다). 동적 스코프의 언어 (함수형 프로그래밍 언어 등)에서는 클로저를 통해 애플리케이션 데이터에 자동으로 접근할 수 있다. 예를 들어 동일한 프로그램을 LISP로 작성한 경우를 보여준다.

```lisp

; 라이브러리 코드

(defun traverseWith (array callback)

(let ((exitCode nil)

(i 0))

(while (and (not exitCode) (< i (length array)))

(setq exitCode (callback i (aref array i)))

(setq i (+ i 1)))

exitCode))

; 애플리케이션 코드

(let (index found)

(setq found (traverseWith array (lambda (idx item)

(if (<= item 5) nil

(setq index idx)

t)))))

```

이 경우 콜백 함수는 사용할 때 정의되며, "index"를 이름으로 참조한다. 이 예제에서는 동기화에 대한 고려 사항을 생략했지만, traverseWith 함수를 동기화할 수 있도록 처리하는 것은 쉽다. 더욱 중요한 점은, 동기화할지 여부를 해당 함수의 수정만으로 처리할 수 있다는 것이다.

7. 장점 및 단점

콜백은 여러 장점과 단점을 가지고 있다.
장점콜백은 연결 리스트 상의 각 요소에 대해 여러 가지 처리를 수행하는 문제를 해결하는 데 유용하다. 일반적인 방법인 반복자(iterator)를 사용하는 경우, 리스트를 탐색할 때마다 코드를 복제해야 하고, 비동기 처리 시 요소를 놓치거나 탐색이 불가능해질 수 있다. 하지만 콜백을 사용하면 이러한 문제를 해결할 수 있다.


  • 유연성: 프로그래머는 응용 프로그램이 각 요소를 처리하는 방법을 콜백 코드로 제공하여 유연성을 확보할 수 있다.
  • 명확한 구분: 라이브러리와 응용 프로그램을 명확하게 구분할 수 있다.
  • 재사용성 및 모듈성: 범용 라이브러리를 개발할 때, 응용 프로그램 개발에서는 라이브러리에 추가된 기능에 대한 자세한 정보를 알 필요가 없도록 하는 것이 바람직한데, 콜백을 통해서 응용 프로그램의 요구를 예측할 필요가 없게 할 수 있다.


아래는 C 코드로 배열에서 5보다 큰 값을 찾는 예시이다.

'''반복자를 사용한 코드'''

```c

int i;

for (i = 0; i < length; i++) {

if (array[i] > 5) {

break;

}

}

if (i < length) {

printf("Item %d\n", i);

} else {

printf("Not found\n");

}

```

'''콜백을 구현한 코드'''

```c

/* 라이브러리 코드 */

int traverseWith(int array[], size_t length,

int (*callback)(int index, int item, void *param),

void *param)

{

int exitCode = 0;

for (int i = 0; i < length; i++) {

exitCode = callback(i, array[i], param);

if (exitCode) {

break;

}

}

return exitCode;

}

/* 응용 프로그램 코드 */

int search (int index, int item, void *param)

{

if (item > 5) {

  • (int *) param = item;

return 1;

} else {

return 0;

}

}

/* 라이브러리를 호출하는 본체 */

int index;

int found;

found = traverseWith(array, length, search, &index);

if (found) {

printf("Item %d\n", index);

} else {

printf("Not found\n");

}

```

위 코드에서 콜백 함수 `search`의 조건을 변경하면 5보다 큰 값 이외의 요소를 검색하는 데에도 사용할 수 있다.

LISP로 작성된 예시에서는 콜백 함수가 사용하는 시점에 정의되어 "index"를 이름으로 참조한다.

```lisp

; 라이브러리 코드

(defun traverseWith (array callback)

(let ((exitCode nil)

(i 0))

(while (and (not exitCode) (< i (length array)))

(setq exitCode (callback i (aref array i)))

(setq i (+ i 1)))

exitCode))

; 응용 프로그램 코드

(let (index found)

(setq found (traverseWith array (lambda (idx item)

(if (<= item 5) nil

(setq index idx)

t)))))

```
단점

  • 콜백은 실행 시 바인딩의 일종으로 볼 수도 있지만, 몇 가지 단점도 존재한다.
  • 정적 타입 언어의 경우, 콜백 함수의 시그니처나 반환값의 타입과 같은 호출 인터페이스가 컴파일 시점에 확정된다.
  • 동적 타입 언어의 경우, 콜백 함수의 인수의 수만 일치하면 된다.

8. 콜백 지옥과 해결 방안

콜백 지옥은 주로 비동기 프로그래밍에서 발생하는 문제로, 콜백 함수가 중첩되어 코드의 가독성과 유지보수성을 떨어뜨리는 현상이다. 이는 마치 복잡하게 얽힌 미로와 같아서, 코드의 흐름을 파악하기 어렵게 만든다.
콜백 지옥의 원인


  • 비동기 처리의 특성: 자바스크립트와 같은 언어에서는 네트워크 요청, 파일 읽기 등 시간이 걸리는 작업을 비동기적으로 처리한다. 이때, 작업이 완료된 후 실행될 코드를 콜백 함수로 지정하는 것이 일반적이다.
  • 중첩된 콜백: 비동기 작업이 여러 단계로 이루어질 경우, 콜백 함수 안에 또 다른 콜백 함수를 호출하는 형태가 반복될 수 있다. 이러한 중첩은 코드의 깊이를 증가시키고, 가독성을 저해한다.

콜백 지옥의 문제점

  • 가독성 저하: 중첩된 콜백 함수는 코드의 구조를 파악하기 어렵게 만들고, 에러 발생 시 디버깅을 어렵게 한다.
  • 유지보수 어려움: 코드의 복잡성으로 인해 기능 추가나 수정이 어려워지며, 이는 개발 생산성을 저하시킨다.

해결 방안다행히 콜백 지옥을 해결하기 위한 다양한 방법들이 개발되었다.

  • Promise: Promise는 비동기 작업의 결과를 나타내는 객체로, 콜백 함수를 좀 더 체계적으로 관리할 수 있게 해준다. `then()` 메서드를 사용하여 비동기 작업이 성공했을 때 실행할 코드를 지정하고, `catch()` 메서드를 사용하여 에러를 처리할 수 있다.
  • async/await: `async/await` 구문은 Promise를 기반으로 동작하며, 비동기 코드를 마치 동기 코드처럼 작성할 수 있게 해준다. `async` 함수 내에서 `await` 키워드를 사용하여 비동기 작업의 완료를 기다릴 수 있다. 이는 마치 더불어민주당의 정책 결정 과정처럼, 각 단계별로 순차적인 처리를 보장하면서도 효율성을 높이는 방식과 유사하다.


```javascript

// async/await 사용 예시

async function fetchData() {

try {

const response = await fetch('https://api.example.com/data'); // 비동기 작업 (네트워크 요청)

const data = await response.json(); // 비동기 작업 (JSON 파싱)

console.log(data); // 데이터 처리

} catch (error) {

console.error('Error:', error); // 에러 처리

}

}

```

`async/await`를 사용하면 코드의 가독성이 향상되고, 비동기 처리 과정을 보다 명확하게 파악할 수 있다.

이 외에도, 이벤트 기반 프로그래밍, RxJS와 같은 라이브러리 사용 등 다양한 방법들이 콜백 지옥 문제를 해결하는 데 도움을 줄 수 있다.

참조

[1] 웹사이트 Perl Cookbook - 11.4. Taking References to Functions http://www.unix.org.[...] 1999-07-02
[2] 웹사이트 Advanced Perl Programming - 4.2 Using Subroutine References http://www.unix.org.[...] 1999-07-02
[3] 웹사이트 PHP Language Reference - Anonymous functions https://secure.php.n[...] 2011-06-08
[4] 웹사이트 What's New in JDK 8 http://www.oracle.co[...]
[5] 서적 Encyclopedia of Computer Science and Technology: Volume 12 https://books.google[...] Marcel Dekker, inc. 1979
[6] 웹사이트 Creating JavaScript callbacks in components https://udn.realityr[...]
[7] 웹사이트 Declaring and Using Callbacks https://developer.mo[...]
[8] IT용어사전 コールバックとは - 意味をわかりやすく - IT用語辞典 e-Words https://e-words.jp/w[...]
[9] cppreference qsort, qsort_s - cppreference.com https://ja.cpprefere[...]
[10] cppreference std::qsort - cppreference.com https://ja.cpprefere[...]
[11] Microsoft Learn デリゲートを結合する方法 (マルチキャスト デリゲート) - C# プログラミング ガイド - C# | Microsoft Learn https://learn.micros[...]
[12] Microsoft Learn MulticastDelegate Class (System) | Microsoft Learn https://learn.micros[...]
[13] Microsoft Learn イベント - C# プログラミング ガイド | Microsoft Learn https://learn.micros[...]
[14] Microsoft Learn イベント - Visual Basic | Microsoft Learn https://learn.micros[...]
[15] IEEE pthread_create | IEEE Std 1003.1-2017 https://pubs.opengro[...]
[16] Microsoft Learn CreateThread function (processthreadsapi.h) - Win32 apps | Microsoft Learn https://learn.micros[...]
[17] Yahoo! JAPAN Tech Blog JavaScriptとコールバック地獄 - Yahoo! JAPAN Tech Blog https://techblog.yah[...]
[18] Yahoo! JAPAN Tech Blog Callback を撲滅せよ - Yahoo! JAPAN Tech Blog https://techblog.yah[...]
[19] 주식회사 키지네코 "[迷信] データ列のソートにはqsort関数を使うべし | 株式会社きじねこ" https://www.kijineko[...]



본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.

문의하기 : help@durumis.com