2021년 1월에서 3월까지 컴퓨터 과학계에서 유명한 SICP를 그룹 스터디를 통해 공부하게 되었습니다. 1장까지 같이 공부를 했었고 이후에는 개인적으로 2장과 3장의 연습문제를 풀면서 학습했습니다.

저는 공부를 시작하기 전에 아래와 같은 소개글과 영상으로 미리 SICP에 대한 정보를 간접적으로 알게 되었습니다.

책을 3장까지 공부해보니 1~2 장까지의 내용은 프로그램을 개발하면서 충분히 고민해볼 만한 내용으로 소프트웨어 공학과 관련한 추상화, 재사용 등의 이야기를 다루고 있었습니다.

책을 펼쳤을 때 첫인상으로 책의 두께가 묵직해서 부담을 많이 느꼈습니다. 학부 4학년 과정 수준의 수업으로 직접 언어 인터프리터를 만드는 난이도가 높은 책이라고 생각했습니다. 그렇지만 직접 읽어보면서 프로그래밍 언어론에 관심이 없어도, 책에 담긴 소프트웨어 공학에 대한 철학이 정말 실용적인 내용이라 프로그램을 개발해본 학부 1, 2학년을 대상으로 권해도 될만하다고 느꼈습니다.

그런 이유로 2장까지의 내용 중 일부 주제를 톺아보는 식으로 블로그에 공유를 결심하게 되었습니다.

상향식 교수법

책 초반에 프로그래밍과 연관된 수학 기초에 대해 이야기하는 점이 인상 깊었습니다. 대부분의 대학 전공 서적은 과정보다 답을 먼저 알려주는 경우가 많아 아쉬웠는데, SICP는 프로그래밍 기초에 필요한 수학으로 사칙연산, 프로시저, 재귀를 먼저 차근차근 알려주어서 마음에 들었습니다.

제곱근을 근삿값으로 구하기 aka. 뉴턴 법

제곱근의 실제 값을 어떻게 구하는지 아시나요? 저는 처음 이 질문을 책에서 보고 사칙연산 같이 바로 계산이 가능한 연산으로 착각을 생각을 했었습니다. 하지만 그렇게 쉽게 구해지지 않은 형태의 값이였습니다.

책에서는 x의 제곱근에 가까운 값 y를 임의로 정하고, yx/y 의 평균을 거듭 구하여 제곱근에 가까운 근삿값을 구할 수 있다고 하는데요, 예컨대 2의 제곱근은 다음처럼 구하고 있습니다:

Guess y   Quotient      Average

1         (2/1)  = 2    ((2 + 1)/2)  = 1.5

1.5       (2/1.5)       ((1.3333 + 1.5)/2)
            = 1.3333      = 1.4167

1.4167    (2/1.4167)    ((1.4167 + 1.4118)/2)
            = 1.4118      = 1.4142

1.4142    ...           ...

출처 : 1.1.7 Example: Square Roots by Newton’s Method

코드를 같이 첨부를 하고 싶은데, 아는 사람이 몇 없는 리스프 코드라서 개념 정도만 올리게 되었습니다.

접선의 기울기를 통해 제곱근 구하기

고등학교 때 수학을 관두어 미분을 이해하기 어려웠지만 접선의 기울기를 구하는 개념을 다시 공부해서(aka. 순간 변화율) 이해하게 되었습니다.

자세한 내용은 Approximating Square Roots w/ Newton’s Method 참고 바랍니다.

임의의 근삿값 x를 구하고 그 접선으로 또 다른 근삿값을 구하는 식으로 범위를 좁혀 실제 제곱근에 가까운 값을 얻을 수 있습니다.

간추리기

프로그래머는 언제나 스스로 짠 프로그램을 놓고 그 속에서 간추릴 게 무엇인지 찾아내어,이로부터 더 수준 높은 표현 수단을 만들어 내려고 애써야 한다.

몇 가지 문제 풀이 방법을 간추려서 다른 문제 풀이에도 다시 쓸 수 있도록 일반적인 표현 수단을 만들려고 애쓰는 버릇은 참말 중요하다. 차수 높은 프로시저, 곧 프로시저를 값데이터으로 쓰는 프로시저가 중요 한 까닭도 여기에 있다.

데이터 표현에 영향을 받는 것을 프로그램 부품 몇몇으로만 제한되도록 설계해 두지 않으면, 큰 프로그램을 짤 때 아주 많은 시간을 들이고 대가를 치러야 한다.

책에서 간추리기(추상화)로 표현되는 “abstraction” 은 소프트웨어 개발에서 중요한 개념입니다. 높은 수준의 추상화는 코드의 의존성이 높지 않아 다른 곳의 영향을 덜 끼치는 실용적인 개념이기 때문입니다.1 SICP에서는 1장과 2장에 거치면서 중요하게 강조하는 개념입니다.

제곱근을 구하는 프로그램을 작성할 때 간추리기에 대한 몇가지 지침이 인상이 깊어서 인용을 했습니다.

1급 함수와 함수형 프로그래밍

SICP는 첫 장의 마지막 소 챕터에서 함수형 프로그래밍 언어의 개념인 “일급 함수"를 소개하고 그 특징을 4가지로 소개하고 있습니다. 첫장 서문에도 함수형 프로그래밍에 대한 칭송이 다분했는데, 그래서 책의 저자가 함수형 프로그래밍에 진심인 걸 느꼈습니다.

  • 함수를 변수로 지을 수 있다
  • 함수의 매개변수로 넣을 수 있다
  • 함수의 반환 값으로 함수를 보낼 수 있다.
  • 데이터 구조 속에 집어넣을수 있다. 🤔…?

클로저를 활용한 튜플 만들기

일급 함수의 정의에서 “데이터 구조 속에 집어넣을 수” 있다는데, 저는 처음에는 함수를 어떻게 데이터 구조 속에 집어넣을 수 있는지 궁금했었습니다. 챕터 2의 한 연습 문제에서 어떻게 가능한지 소개되어 있습니다.

리스프 코드를 읽을 수 없는 분을 배려하고자 자바스크립트로 클로저로 연습 문제를 준비했습니다.

function cons(x, y) {
  return function (m) {
    return m(x, y)
  }
}

cons 함수는 한 쌍의 튜플을 함수로 표현하기 위한 고차 함수입니다. m이라는 함수를 반환하면 그 함수가 xy 중 하나를 반환하게 하는 것이 목표입니다. 우선 주어진 코드는 아래와 같습니다.

const pair = cons(2, 1)

const car = (x, y) => {} // 함수 본문을 어떻게 채워야 할까요...?
const cdr = (x, y) => {} // 함수 본문을 어떻게 채워야 할까요...?

console.assert(car(pair) === 2)
console.assert(cdr(pair) === 1)

정답은 두 가지 매개변수 x, y 중에 하나만을 반환하게 하면 됩니다. 이런 식으로 함수만으로 데이터 구조를 표현할 수 있다고 합니다.

const car = (x, y) => x
const cdr = (x, y) => y

출처: What Is Meant by Data? (Exercise 2.4)

마치며

SICP를 읽으면서 추상화와 구조 개선에 대해 눈에 익도록 읽히면서 소프트웨어 개발의 기본 지침에 대해 배울 수 있었습니다. ⬇️ 프로그램을 개발해본 학부 1, 2학년 수준에서 권해도 괜찮다고 느꼈지만… MIT 학생이 아닌 대학교 신입생이 바로 읽기에는 난이도가 있어 보였습니다.

SICP 저자가 쓴 새 책으로 “Software Design for Flexibility” 가 있는데 나중에 번역서가 나오면 한번 읽어보려고 기다리고 있습니다. 😋

참고 자료


  1. 저는 이런 추상화가 의존 관계 역전Dependency Inversion, 더 나아가 의존성 주입Dependency Injection과 연관이 있다고 생각을 합니다. 구체적인 구현에 직접 의존하지 않고 추상적인 인터페이스에 의존하기 때문입니다. 탐정토끼님의 “의존 관계 역전과 의존성 주입 - 프런트엔드에서 의존성을 제어하는 법” 글을 참고 해서 생각해낸 의견입니다. ↩︎