개발자 노트

closure 본문

컴퓨터 언어/함수형 프로그래밍

closure

jurogrammer 2021. 8. 8. 16:24

도입

closure를 영단어로 검색해봅시다.

영사전

  1. an act or process of closing something, especially an institution, thoroughfare, or frontier, or of being closed.

폐쇄의 의미를 가지고 있죠. 그래서 처음 closure를 공부했을 때 전혀 와닿지 않았습니다. 뭐가 폐쇄되있다는 거죠? 오늘은 이 베일을 벗겨보겠습니다!

closure 개념

lambda calculus는 1930년도에 나온 개념이고 closure는 1960년도에 나온 개념입니다.

Peter J. Ladin이라는 사람이 1964년도에 labmda calculus expression을 평가하는 머신을 고안하는데 처음 정의되었습니다. 그후 1970년도에 Joel Moses가 현대 closure 개념이 정립되고 구현되었죠.

그 개념을 말씀드리기 위해 다음 문제를 살펴볼게요.

x ⇒ y ⇒ x^2 + y^2

에서 x에 5를 집어넣으면 수학적으로는

y ⇒ 25 + y^2 이라는 함수가 반환된다고 생각할 수 있죠?

그런데 컴퓨터는요? 보통 call by value로, y값을 apply하는 시점에 평가가 진행됩니다.

다시 말해서 컴퓨터는 x = 5를 집어 넣는다 하더라도 y = x^2 + y^2 라는 함수를 반환하죠.

x = 5 를 집어넣었던 값을 어떻게 처리해야하겠죠?? 나중에 y값을 apply하는 시점에 x = 5값이 적용되도록 말이에요.

좀 더 정확히 이해하기 위해서 사전지식을 하나 더 배워보겠습니다. 바로 scope입니다. scope는 블록 scope 이거 말씀드리는 것 맞습니다.

scope

사전 정의

the extent of the area or subject matter that something deals with or to which it is relevant.

번역: 관련있거나 다루고 있는 영역 또는 주제에 대한 범위

다시 말해서 관심사에 대한 범위를 scope라고 합니다.

telescope: 멀리있는 대상을 보고 싶다. 망원경

microscope: 가까이 있는 대상을 보고 싶다. 현미경

두가지 키워드: name binding, part of a program

scope치면 scope라고만 나오는게 아니라 scope of name binding이라고 나옵니다.

the scope of name binding is the part of a program where the name binding is valid.

프로그램의 특정 부분(영역)인데, name binding이 유효한 프로그램의 특정 범위래요.

이름이 유효한 특정 영역을 scope라고 부릅니다.

name binding

int a = 10;

표현: 이름 a를 10이란 entity에 bind하였다.라고 합니다.

part of a program

일반적으로 프로그램의 일부분이나, 특정 영역에서 유효한 범위는 결국 source code의 범위를 의미하죠.

a * 10이라고 했을 때 이 a가 유효한 범위는 어디에요? ⇒ block 내에서만 유효하죠?

a라는 이름이 10이라는 entity로 바인드되는 것이 유효한 범위를 part of program이라고 부릅니다. 제가 코드 레벨로 그 범위를 지정하고 있죠?

이처럼, 소스 코드 레벨로 scope를 지정하는 방법을 lexcial scope라고 부릅니다. 이와 반대로 런타임 레벨로 범위를 지정한다면 dynamic scope라 부르죠. 대부분 scope는 lexcial scope를 의미한다고 보면 되요.

다들 이름만 들어도 아시다시피 lexical 스코프는 다음 5가지로 나뉠 수 있어요.

level of scope

expression scope

block scope

function scope

file scope

module scope

global scope

그래서 Closure?

x ⇒ y ⇒ x^2 + y^2

에서 x = 5를 집어 넣는 순간, y ⇒ x^2 + y^2 이라는 lambda expression에서 x는 5로 bind되어야 합니다. 이 x = 5 라는 name binding 정보를 어딘가의 scope에 저장해야 하죠.

그리고 y ⇒ x^2 + y^2에서 y에 3을 적용해볼게요. 이때 x = 5는 어떤 scope에서 binding했나요?

1) block scope? 블록 내 선언된 변수가 x가 아니죠.

2) function scope? function이 종료되는 시점에 x는 없어져야 하는데요? 종료된 이후에도 x에 5를 binding하고 있잖아요.

3) global scope? x 선언하지 않고 x = 5 바인딩을 할 수 있습니다.

다 아니죠~!

outer function의 scope를 지닐 수 있는 새로운 scope가 필요하다는 뜻이 됩니다.

바로 이 name binding하는 lexical scope를 지닌 function을 closure라고 부릅니다.

closure는 닫혀있다는 뜻인데, open lambda expression과 closed lambda expression을 비교하여 설명한 것입니다.

단순히 y ⇒ x^2 + y^2에서 x는 어떠한 값이 될 수 있으므로(free variable) open lambda expression,

x ⇒ (y ⇒ x^2 + y^2) 에서 y ⇒ x^2 + y^2은 x가 outer function에 의해 더이상 free variable이 아닌 bind variable이 되어 있으므로 얘는 closed lambda expression이라고 부릅니다.

이 closed labmda expression를 closure라고 부르는 것이죠.

이제야 좀 닫혀있다는 뜻이 감이 오나용?

javascript에서는 언제 closure가 생성될까?

기술적으로 언제 clsoure가 생성되느냐? 바로 함수가 호출될 때죠.

in mdn javascript

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures

function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

console.log(add5(2));  // 7
console.log(add10(2)); // 12

// add5 and add10 are both closures.

이거 익명함수 아닌가? 라고 할 수 있는데요.

익명함수라고 하기엔 x라는 이름을 5나 10이라고 binding할 수 있는 outer function에 대한 lexical scope도 같이 반환되죠.

따라서 익명함수와는 다릅니다.

이번엔 실용적인 관점에서 바라봅시다. closure를 어떻게 활용할 수 있을까요.

사용 예

1.function factory

객체지향 언어에서 클래스를 통해 인스턴스를 생성하는 것처럼 function을 반환할 때 사용할 수 있습니다.

이건 자바코드인데용, bornWith라는 static factory method로 객체를 생성하고 있습니다.

public class Main {
  public static void main(String[] args) {
      Person jurogrammer = Person.bornWith("jurogrammer");
      Person honggildong = Person.bornWith("gildong");

      jurogrammer.sayHello();
      honggildong.sayHello();
  }

  static private class Person {
      private String name;

      public Person(String name) {
          this.name = name;
      }

      public static Person bornWith(String name) {
          return new Person(name);
      }

      public void sayHello() {
          System.out.println("hello~ my name is " + name);
      }
  }
}

위를 자바스크립트에서 closure를 이용하여 유사하게 구현했습니다. 여기선 인스턴스를 생성하진 않고 인사를 하는 함수를 생성하고 있죠.

function bornWith(name) {
  return () => console.log("hello, my name is " + name")

let jurogrammerHelloFunc = bornWith("jurogrammer");
let honggildongHelloFunc = bornWith("gildong");

juinjaeHelloFunc();
honggildongHelloFunc();

 

private variable

위 예제에서 jurogrammerHelloFunc은 사용할 수 있는데, name엔 접근할 수 없잖아요? name이 private variable이 된 것이죠. 이처럼 private variable을 만들 때 사용할 수 있습니다.

delay evaluation

function f(x) {
    return x + 2;
}

function g(x) {
    return x + 5;
}

// x =>... 라는 closure 를 반환한다. x에 값을 넣기 전까지는 x가 평가되지 않는다.
function compose(functions) {
    return x => functions.reduce((accReturnVal, curFunc) => curFunc(accReturnVal), x);
}

// 위와 동일한 형태
function compose(f,g) {
    return x => f(g(x));
}

이전에 합성함수에 대해 말씀드린 적이 있는데요. f(g(x)) 형태를 만들기 어렵다고 말씀드렸죠. 타입 문제도 있지만, x를 eager하게 평가하기 때문에도 문제라고 말씀드렸어요.

closure를 이용하면 x를 lazy하게 평가시킬 수 있어서 f(g(x))라고 작성할 수 있게 됩니다.

 

마지막...애매한 것 - 함수? record?

애매한 게 있긴해요. wiki에서 closure를 함수와 환경을 저장하고 있는 record(저장소)라고 정의합니다. mdn에서 그렇게 정의하기도 하구요. 그런데 또 보다보면 함수를 closure라고도 부르기도 해요. record와 함수는 다른 것 같은데 말이죠. kotlin에서도 closure를 함수라고 부릅니다. closed lambda expresssion이 함수니깐... 이 글에선 해당 논란은 다루진 않았습니다. 함수라고 가정하고 설명드렸어용~

반응형

'컴퓨터 언어 > 함수형 프로그래밍' 카테고리의 다른 글

Stream API 연습들  (1) 2022.10.13
lazy evaluation  (0) 2021.08.08
functional하게 decorator pattern 구현  (0) 2021.06.19
lambda calculus - boolean 연산  (0) 2021.03.21
Lambda Calculus - Formal 정리 (Reduction)  (0) 2021.02.07
Comments