2019년 4월 18일 목요일

[SICP][scheme][javascript] 아무것도 없는 것에서 무언가를 추상화할 수 있을까?

출처 : https://youtu.be/ymsbTVLbyN4

SICP의 강의는 손이 잘 가지 않는 영상이다.
난 항상 SICP를 읽어보려고 하지만 일주일이 지나면 역시나 다른책을 꺼내 읽고 있다. 이 못난 학생은 그럼에도 황새를 쫓아가보겠다고 공부를 하다. 그러다 나는 아래의 코드를 보게 되었다.

(define (cons a b)
  (lambda (pick)
    (cond ((= pick 1) a)
          ((= pick 2) b))))

(define (car x) (x 1))
(define (cdr x) (x 2))

;; 설명을 위해 더해진 것.
(define A (cons 1 2))
(print (car A))
(print (cdr A))
이 비디오를 보는 사람들 중 몇몇은 이미 이 내용을 설명할 때, 경악을 금치 못했다.
이 코드가 대체 무엇이길래 그러는가. 난 이 코드에서 모든 추상화가 시작되진 않았을까 상상해본다.
데이터 추상화란 무엇인가.

1. "부를 수 있게 된다"는 것이다. (그것은 이름업슨 데이터에 이름을 부여함으로써 시작된다. 하여 머릿속으로도 다룰 수 있게 된다.) 2. 그것은 마치 마술사가 주문에 이름을 부여하는 것처럼 마법과 같다.
3. 우리는 이 개념의 정확한 정의를 나중에 내릴 수 있게 된다. (일단 이름을 부름으로써 나중에 해당 개념에 대한 정의가 바뀌면 그 정의만 바꾸면 되는 것이다.)
4. (추가적인 특징) 우리는 이 추상화된 데이터를 강제할 수 있다.

rational number

이 책에서 설명하는 rational number에 대해 알아보자.
우리는 일단 데이터를 추상화 해야 한다.
분자 n, 분모 d가 있다.
이것들을 make-rat에 넣어서 하나의 패키지를 생성하는 것이다.
(make-rat n d) ; rational-number (n,d의 저장형태는 알 필요없음)
(numer rational-number) ; numerator 분자
(denom rational-number) ; denominator 분모
여기서 make-rat는 constructor, numer/denom은 selector라고 불린다.
하나의 데이터는 이렇게 constructor와 selector로 추상화될 수 있다.

이렇게 유리수를 make-rat, numer, denom으로 추상화 하였다.
한번 더하기를 해보자.
(define (+rat x y)
  (make-rat
    (+ (* (numer x) (denom y))
       (* (numer y) (denom x)))
    (* (denom x) (denom y))))
여기서 x,y는 rational-number이다. 우리는 x,y가 어떻게 구현되어 있는지는 모르지만, make-rat으로 유리수를 만들 수 있고, numer/denom으로 꺼낼 수 있는 것은 알고 있다.
곱하기도 만들자.
(define (*rat x y)
  (make-rat)
  (* (number x) (number y))
  (* (denom x) (denom y)))
자 이제 유리수를 구현해보자. 어떻게 담아야 할까?
(define (make-rat n d) (cons n d))
(define (numer x ) (car x))
(define (denom x) (cdr x))
이렇게 cons를 이용하여 pair(리스트)를 만든다.
그러니 rational-number란 무엇인가?
이것은 사실 pair일 뿐이다. 첫번째와 두번째라는 박스가 있는 자료구조일 뿐이다.
그렇다면 pair라는 것은 무엇인가? 이것도 또한 두개의 값이 쌍으로 있는 추상적인 개념이다.
쌍의 개념은 cons라는 constructor와 car, cdr이라는 selector로 표현할 수 있다.
여기서 car는 첫번째 값을 말하며, cdr는 두번째 값을 지칭한다.

아무것도 없는 곳에서 추상화하기

그렇다면 pair라는 추상적인 개념은 무엇을 기반으로 만들어진 개념일까?(무엇 위에서 쌓여 올라왔는가)
여기서 저자는 아무것도 없는 곳에서 생성된다고 한다.

(define (cons a b)
  (lambda (pick)
    (cond ((= pick 1) a)
          ((= pick 2) b))))

(define (car x) (x 1))
(define (cdr x) (x 2))
여기에 데이터가 보이는가? 어떤 자료구조가 보이는가?
쌩뚱맞게 lambda가 보인다. cons는 일단 procedure를 다시 리턴한다.
그리고 pick이라는 변수를 받는데 그것이 1이면 a를 리턴하고, 2이면 b를 리턴하는 함수를 리턴한다.

car를 보자. (x 1)라는 함수가 있는데, 여기서 1이 매개변수 pick에 매핑될 것이다. 첫번째 값(즉 a)이 리턴
cdr에서는 (x 2)로써, pick=2가 되고 두번째 값(b)가 리턴된다.

대체 어디에 a,b는 어떤 자료구조로 저장되는 건가? 아무것도 아닌 것에 저장되는 느낌이다.
closure를 이해한다고 생각했는데 이 코드를 보자마자 '이해한다는 것이 무엇인지조차 모르고 있었구나' 라는 생각이 들었다.

공부를 하면 할 수록, 항상 겸손해야 한다는 생각을 할 수 밖에 없다.
겸손하지 않으면, 배울 수가 없으니...

강의 교수님이 한 말씀을 여기 기록하지 않을 수가 없다.
So in some sense, we don't need data at all to build these data abstractions.
We can do everything in terms of procedures.

procedures are not just the act of doing something.
procedures are conceptual entities, objects.

강의가 끝나고 한 지적인 학생이 엄청난 질문은 하는데 그것은 바로
(cons 1 2)의 값과 1초 후 동일한 프로시저호출인(cons 1 2)는 동일한 녀석인가? 이다.

이 말은 (+ 2 2)와 4는 동일한 녀석인가?

를 고민하는 문장인데, 한시간의 수업에서 대체 이런 질문이 어떻게 나올 수 있었을까? 라는 생각이 든다.
이 질문에 대해 교수님은 당장은 대답할 수 없지만, 곧 하게 될 것이라는 말을 하며 끝낸다.

언제 저 답을 알 수 있을까? 빨리 다음을 알고 싶다.

여담으로 아래 scheme코드를 js로 변경해보자.
function cons (a , b) {
  return function(pick) {
    switch(pick) {
    case 1 : 
      return a;
    case 2 :
      return b;
    default:
      return;
    }
  }
}

function car(x) { return x(1); }
function cdr(x) { return x(2); }

var procedure = cons(10,20);

car(procedure);
cdr(procedure);

댓글 없음:

댓글 쓰기