클로저

포획된 블락과 프록 리터럴은 지역 변수와 self를 클로저로 저장합니다. 예시가 말보다는 이해하기 쉬울 것입니다.

x = 0
proc = ->{ x += 1; x }
proc.call #=> 1
proc.call #=> 2
x         #=> 2

메서드에서 반환한 프록을 사용할 수도 있습니다.

def counter
  x = 0
  ->{ x += 1; x }
end

proc = counter
proc.call #=> 1
proc.call #=> 2

위 예시에서, x는 지역 변수이지만 프록 리터럴에 의해 포획되어 있습니다. 이 경우 컴파일러는 x를 힙에 할당하여 프록이 동작하기 위한 맥락 데이터로 사용합니다. 일반적인 지역 변수는 스택에 저장되므로 메서드가 반환할 때 소멸하기 때문입니다.

클로저로 저장된 변수의 타입

컴파일러는 보통 지역 변수의 타입을 알아서 적당히 추론합니다.

def foo
  yield
end

x = 1
foo do
  x = "안녕"
end
x # : Int32 | String

컴파일러는 블락이 끝난 후 x는 Int32일 수도, String일 수도 있다는 사실을 알아냅니다. (메서드가 항상 yield를 사용하므로 x가 항상 String이라는 것을 추론할 수도 있지만, 이는 추후 개선될 사항입니다.)

블락이 지나서 x에 다른 값이 할당된다면 컴파일러는 이 또한 알아차립니다.

x = 1
foo do
  x = "안녕"
end
x # : Int32 | String

x = 'a'
x # : Char

하지만 x가 프록에 클로저로 저장된다면 그 타입은 할당된 모든 값의 공용체입니다.

def capture(&block)
  block
end

x = 1
capture { x = "안녕" }

x = 'a'
x # : Int32 | String | Char

이는 포획된 블락이 클래스나 인스턴스 변수로 저장되어 다른 스레드에서 실행될 가능성이 있기 때문입니다. 컴파일러는 이런 사항을 모두 분석하지는 않기 때문에, 어떤 변수가 프록에 의해 포획된다면 그 프록이 호출될 시점에서는 변수의 타입을 알 수 없다고 가정할 뿐입니다.

이는 평범한 프록에 대해서도 동일합니다. 프록이 절대로 호출되거나 저장되지 않음이 분명하더라도 같습니다.

def capture(&block)
  block
end

x = 1
->{ x = "안녕" }

x = 'a'
x # : Int32 | String | Char

results matching ""

    No results matching ""