20250113

클로저(closure) 에 대하여 - golang

클로저(closure)의 개념

클로저는 함수가 외부 환경의 변수를 캡처하여 기억하는 기능입니다.

- 함수 + 지역변수 값 = 변수에 저장해두는 개념 입니다.

값을 기억하고 있는, 함수라고 말할수 있습니다. return되는 함수를(공식을) 들고 있는 변수 개념.

클로저는 함수와 해당 함수가 선언된 환경(context)을 함께 저장하는 구조입니다.

함수가 외부 함수의 변수(지역 변수 포함)를 기억하고 사용할 수 있습니다.

클로저로 만들어진 함수는 호출될 때마다 상태를 유지합니다.

intSeq 함수에서 반환된 익명 함수 intSeq 내부의 지역 변수 i를 참조하므로, 이 익명 함수는 i를 기억하고 유지할 수 있습니다. 클로저는 같은 함수 내에 정의된 변수를 참조할 수 있는 함수 객체입니다.

func intSeq() func() int {
i := 0 // 함수 내부에서 선언된 i는 지역 변수, 이 함수가 호출될 때마다 새로 초기화됩니다.
// 이 변수는 intSeq의 반환값으로 리턴되는 익명 함수(클로저)에서 참조됩니다.
// 익명 함수란 이름이 없는 함수입니다.
// 반환값: func() int
// 고차 함수란 함수를 매개변수로 받거나, 함수를 반환하는 함수입니다.
return func() int {
i++
return i
}
}

func intSeq() func() int

- 이 선언은 intSeq라는 함수의 정의 입니다.

- intSeq는 클로저를 반환하는 함수입니다.

- intSeq는 반환값으로 함수를 리턴합니다.

고차 함수란 함수를 매개변수로 받거나, 함수를 반환하는 함수입니다.

- 반환하는 함수는 func() int 타입으로, 매개변수를 받지 않고 int 값을 반환합니다.


return func() int { i++; return i }

- intSeq 함수는 익명 함수 func() int { i++; return i } 를 반환합니다. **

- 이 익명 함수는 i 값을 증가시킨 후, 증가된 값을 반환합니다.

  (이 익명 함수는 intSeq 내부에서 선언된 i를 기억합니다.)

  (중요한 점은 익명 함수가 intSeq 함수 내부의 i를 계속 기억한다는 점입니다.)

- 반환된 익명 함수는 i라는 변수를 계속 사용할 수 있는 특별한 함수입니다. 이 특성을 클로저(closure)의 핵심 이라고 합니다.


func main() {
nextInt := intSeq() // 클로저를 반환받아 저장. func() int { i++; return i } 
//intSeq()를 호출하면 i := 0이 생성되고 이를 기억하는 함수가 반환됩니다.
fmt.Println(nextInt()) // 1 nextInt()는 반환된 함수로, 호출할 때마다 i를 1씩 증가시킴
fmt.Println(nextInt()) // 2 nextInt()는 i값 기록있는 func() int { i++; return i }가 실행됨
fmt.Println(nextInt()) // 3 nextInt()는 i값 기록있는 func() int { i++; return i }가 실행됨

// 새로운 클로저 생성- 완전 독립적이며, 로컬변수 i := 0 이 실행됨
anotherInt := intSeq() // anotherInt 는 새로운 클로저로, 별도의 i 값을 유지합니다.
fmt.Println(anotherInt()) // 1 i값 기록있는 func() int { i++; return i }가 실행됨
fmt.Println(anotherInt()) // 2
}

intSeq()를 호출하면 i가 0으로 초기화된 상태로 익명 함수가 반환됩니다.

- nextInt 변수는 이 반환된 익명 함수를 참조합니다.

- nextInt는 i 값을 증가시키는 작업을 수행합니다.

nextInt()를 호출할 때마다 클로저 내부의 i가 증가하고 반환됩니다.

- 첫 번째 호출: i가 0에서 1로 증가 → 1 반환

- 두 번째 호출: i가 1에서 2로 증가 → 2 반환

- 세 번째 호출: i가 2에서 3으로 증가 → 3 반환

새로운 클로저를 생성하면 새로운 i가 초기화됩니다.

새로운 클로저를 생성하면 독립적인 상태를 가지는 새로운 변수를 생성합니다.

- anotherInt := intSeq()는 새로운 i를 생성하며, 값은 0으로 시작합니다.

- 이 클로저는 이전의 nextInt와 독립적으로 작동합니다.


Go 언어에서 클로저는 자신이 참조하는 변수(i)를 메모리에 유지합니다.

이 때문에 intSeq 내부의 i는 nextInt가 존재하는 한 메모리에서 유지되며, 호출 시마다 상태가 업데이트됩니다. (호출때마다 기록값이 있는 함수가(공식이) 작동합는 원리), 변수값에 return 되는 함수를 넣어두고 있는 개념.

---------------------------------------------------------------------------------------

func createAdder(x int) func(int) int {
return func(y int) int {
return x + y
}
}

createAdder 함수: 

- createAdder는 함수를 반환하며, 반환하는 함수는 정수(int)를 매개변수로 받고, 

   정수를 반환합니다.

- 매개변수: x int (정수형 x를 입력받음)

- 반환값: func(int) int


func(y int) int 익명 함수:

- 익명 함수 내부에서 입력받은 y와 외부에서 전달된 x를 더합니다

- return x + y: 클로저(closure)를 이용하여 x 값을 유지합니다


func main() {
add10 := createAdder(10) // x값 + 익명함수 = 변수 add10에 저장해둠
result := add10(5)
fmt.Println(result) // 출력: 15
}

createAdder 호출:

- createAdder(10)을 호출하면, 10을 더하는 함수를 반환합니다.

- 반환된 함수는 add10 변수에 저장됩니다 add10 := createAdder(10)


반환된 함수 호출:

- add10(5)를 호출하면, 반환된 함수의 내부 동작에 따라 10 + 5가 계산됩니다

   result := add10(5)


결과 출력:

- result 변수에 저장된 값을 출력합니다. 결과는 15입니다

- fmt.Println(result) // 출력: 15


package main

import (
"fmt"
)

// 두 수의 합을 계산하는 함수를 반환하는 함수
func createAdder(x int) func(int) int {
return func(y int) int {
return x + y
}
/* 아래와 같이, x값이 10으로 된 상태로, 아래 함수 내용이 add10 변수에 저장되는 원리
변수 호출시, 아래에 x값이 (고정 or 누적)적용된 리턴만 실행되는 원리.
return func(y int) int {
return 10 + y
}
*/
}

func main() {
// 10을 더하는 함수를 생성
add10 := createAdder(10) //add10은 x값 10과 함께 익명함수까지 통째로~ 기억합니다.
// add10을 사용하여 5를 더함
result := add10(5) // 변수에 5를 적용시, 리턴값에 y인자로 들어가, 10+5 =15
fmt.Println(result) // 출력: 15
}

createAdder가 호출되면, 매개변수 x에 값 10이 전달됩니다.

함수 내부에서 익명 함수 func(y int) int { return x + y }를 반환합니다.

반환된 익명 함수는 x = 10이라는 상태를 기억합니다.

간단하게 설명하면: x값 인자로 10을 받으면, x값 10은 계속 유지되고, add10(5)로 y값 5를 받으면,

10 + 5 = 15 가 됩니다.

* 클로저 내부에서 사용되는 변수는 함수가 선언된 환경(scope)에서 캡처된 상태로 저장됩니다.

  클로저에 의해 캡처된 변수는 스택이 아닌 힙에 저장됩니다. 일반 함수는 호출이 끝나면 메모리에서 제거되지만,

  createAdder 함수에서 x는 로컬 변수로 선언되지만, 반환된 익명 함수가 x를 참조합니다

  익명 함수가 x를 참조하는 순간, Go는 x를 힙 메모리에 복사하여 유지합니다.

  이후 반환된 함수는 힙에 저장된 x를 계속 사용할 수 있습니다.

  인자로 받은 x값과 익명함수를 한꺼번에 변수로 기억해두는 원리입니다.

* 힙 메모리에 할당된 클로저 변수는 자동으로 관리됩니다.

  Go의 가비지 컬렉션(GC)이 클로저 변수를 추적하고, 더 이상 참조되지 않는 경우 이를 자동으로 제거합니다.

  직접 힙 메모리에 할당된 변수를 제거할수는 없습니다.(참조하는걸 끊어두면, GO가 자동으로 제거합니다.)

  add10 = nil로 설정하면 클로저 함수에 대한 참조가 사라집니다.

  또는, { } 블록 안에서만 유효하기에, 블록을 벗어나면 참조가 사라져, 클로저는 제거됩니다.

  클로저 변수는, 전역변수로 저장하면 안됩니다. 프로그램이 종료 될때까지, 가비지 컬렉터가 제거하지 못해요.

  클로저를 slice나 map에 저장하면, 자료구조에서 해당 항목을 삭제하지 않는 한 클로저는 유지됩니다.


실행 순서:

createAdder 호출

- createAdder(10)은 익명 함수 func(y int) int { return x + y }를 반환하며, 

  x 값은 10으로 고정됩니다.

- 함수를 변수에 저장

  반환된 함수가 add10 변수에 저장됩니다.

- add10(5) 호출

  호출 시, y 값으로 5를 전달합니다.

  클로저를 통해 x 값(10)은 기억된 상태입니다.

  결과: 10 + 5 = 15

- 결과 출력

  fmt.Println을 통해 결과를 출력합니다.


클로저(Closure)란?

- 클로저는 함수가 외부 환경의 변수를 캡처하여 기억하는 기능입니다.

- createAdder에서 반환된 익명 함수는 x 값을 캡처하여 반환된 후에도 

   계속 사용할 수 있습니다.

   add10 := createAdder(10) // x는 10으로 고정됨

   fmt.Println(add10(5))    // 출력: 15 (x = 10, y = 5)

   fmt.Println(add10(20))   // 출력: 30 (x = 10, y = 20)