Engineering

자바스크립트로 배우는 SICP - 함수를 이용한 추상화(1)

💡 다수의 단순 관념을 하나의 복합 관념으로 조합한다. 복잡한 관념들은 모두 이런식으로 만들었다. 둘째는 두 관념을 가져와 또 다른 관념으로 설정함으로써 그 둘을 하나의 관념으로 통합하지 않고도 두 관념을 한번에 볼 수 있게 만드는 것이다.

M
Mason
Software Engineer · · 11분

💡 다수의 단순 관념을 하나의 복합 관념으로 조합한다. 복잡한 관념들은 모두 이런식으로 만들었다. 둘째는 두 관념을 가져와 또 다른 관념으로 설정함으로써 그 둘을 하나의 관념으로 통합하지 않고도 두 관념을 한번에 볼 수 있게 만드는 것이다. 관계에 관한 관념은 모두 이런식으로 만들어 진다. 셋째는 하나의 관념을 그 실제 존재에 수반하는 다른 모든 관념으로 부터 분리하는 것이다. 이를 추상화 라고 하며 일반적인 관념은 모두 이런식으로 만들어 진다. - 존 로크 (인간지성론)

들어가면서

이 장에서는 계산적 과정이라는 아이디어에 대해서 공부했습니다. 계산적 과정은 컴퓨터안에 사는 추상적 존재라고 표현했습니다. 이 과정이 점차 전개 되며 데이터를 조작하게 되는데 이러한 패턴을 프로그램이라고 합니다.

인상깊었던 문장중 하나는 "본질적으로 프로그래머는 자신의 주문들로 컴퓨터의 영혼을 불러낸다. 불러낸 영혼은 볼 수도 없고 만질 수도 없으며 어떤 물질로 만든게 아니지만, 마법사가 보기에 그 영혼은 존재한다. 마법사가 불러낸 영혼처럼 계산적 과정은 어떤 지적인 일을 수행한다. 질문에 답하거나 은행에서 돈을 지급하고 공장에서 로봇팔을 제어해서 세상에 영향을 끼친다. 우리가 계산적 과정을 불러내기위해 사용하는 프로그램은 마법사의 주문과 비슷하다. 주문을 하기 위한 수단을 우린 프로그래밍 언어라고 한다."

"하지만 계산적 과정은 프로그램들이 엄밀하고 정확하게 수행하기 때문에 마법사의 제자 처럼 초보 프로그래머는 자신이 불러낸 과정이 어떤 일을 하는지 이해하고 예측하는 방법을 배워야 한다. 사소한 오류가 있으면 복잡하고 예측하지 못한 결과가 빚어질 수 있다."

자바스크립트 프로그래밍

일상적 생각들을 자연어(한국어, 영어, 일본어, 중국어)로 표현하고, 정략적인 현상들을 수학적 표기법으로 서술하듯, 계산적 과정에 관한 절차적 생각들을 여기서는 자바스크립트로 표현합니다. 지난 장에서 설명했지만 자바스크립트는 프로그래밍 언어인 스킴과 셀프의 핵심 기능을 물려받았습니다. 어휘순 범위(Lexically scoped), 일급 함수나 동적 형식 적용 같은 자바스크립트의 가장 근본적인 설계 원칙들을 스킴에서 물려받았습니다.

ECMAScript2015 에서 어휘순 일급 함수를 람다 표현식을 통해 문법적으로 지원하는 덕분에 함수적 추상들에 직접적으로 간결하게 접근할 수 있으며, 동적 형식 적용 덕분에 스킴을 이용한 SICP와 최대한 비슷하게 설명할수 있게 됐다고 설명합니다. (이 책에서는 ECMAScript 2015를 기본적으로 적용합니다.)

1.1 프로그래밍 기본 요소

프로그래밍 언어는 우리가 과정에 관한 생각을 조직화 하는 틀로도 작용합니다. 프로그래밍 언어를 고찰할 때 단순한 아이디어들을 조합해서 좀 더 복잡한 아이디어를 만드는데 사용하는 수단이라는 점을 생각해야 합니다. 모든 언어는 이를 위해 세가지 매커니즘을 제공해야 합니다.

  • 원시 표현식 - 언어와 관련된 가장 단순한 객체(entity)를 나타낸다.
  • 조합 수단 - 단순한 요소들로부터 복합적인 요소를 만드는데 쓰인다.
  • 추상화 수단 - 복합적인 요소들에 이름을 붙여서 하나의 단위로 다루는데 쓰인다.

프로그래밍에서 다루는 요소는 크게 함수와 데이터로 나뉘는데, 좋은 언어는 반드시 원시 데이터와 원시 함수를 서술하는 기능과 그런 함수들과 데이터를 조합하고 추상화는 수단도 제공이 되어야 합니다.

1.1.1 표현식

이 책에서는 프로그래밍 테스트를 위해 대화형 방식의 인터프리터를 사용할것을 권장하고 있습니다. 인터프리터를 통해 프롬프트에 하나의 문장(statement)를 입력하여 인터프리터가 문장을 평가(evaluation) 하여 결과를 화면에 표시해줍니다. nodejs 를 이용하여 콘솔 환경에서 대화형 인터프리터를 사용하거나 main.js 를 만들어서 실행시켜도 좋습니다.

자바스크립트에 아래와 같은 명령을 내리면

486;

인터프리터는 아래와 같은 평가를 출력합니다

486

수를 나타내는 표현식들을 연산자로도 조합할 수 있습니다. 그 결과는 연산자들에 해당하는 원시 함수를 해당 수들에 적용하는 복합 표현식입니다.

137 * 349;
486 1000 - 334;
666 5 * 99;
495 10 / 4;
2.5 2.7 + 10;12.7

이렇게 다른 표현식을 구성요소로 담고 있는 표현식을 조합이라고 하며 가운데 연산자가 있고 왼쪽 오른쪽에 피연산자 표현식이 있는 형태의 조합을 연산자 조합이라고 부릅니다. 연산자 조합의 값은 연산자로 지정된 함수를 인수들, 즉 피연산자 값들에 적용해서 구할수 있습니다.

연산자를 두 피연산자 사이에 배치하는것을 중위 표기법이라고 합니다.

(3 * 5) + (10 - 6);
19

수학처럼 자바스크립트도 연산 순서의 혼란을 피하기 위해 소괄호로 연산자 조합을 묶을수 있습니다. 소괄호를 생략해도 자바스크립트 인터프리터는 통상적인 방법에 따라 연산순서를 정합니다. 곱셉과 나눗셈 그리고 덧셈과 뺼셈 순으로요.

예를 들어 다음은

3 * 5 + 10 / 2

다음에 해당합니다.

(3 * 5) + (10 / 2);

이런 관례를 연산자가 왼쪽 결합됐다고 합니다. 자바스크립트 인터프리터가 평가할 수 있는 표현식의 복잡도는 제한이 없지만 사람이라면 이런 예가 헷갈릴수 있습니다.

3 * 2 * (3 - 5 + 4) + 27 / 6 * 10

자바스크립트는 이것을 57로 평가하겠지만 다음처럼 표현식의 주요 구성요소를 시각적으로 명확하게 구분해서 표기한다면 덜 헷갈릴 수 있습니다.

3 * 2 * (3 - 5 + 4)+27 / 6 * 10;

이렇게 인터프리터에는 복잡한 문장이 주어져도 하나의 사이클로 동작하며 인터프리터는 사용자가 입력한 문장을 릭고, 평가하여 출력해주데 이러한 주기를 REPL(read-evaluate-print loop) 라고 부릅니다.

1.1.2 이름 붙이기와 환경

계산적 개체에 이름을 붙여서 이름으로 개체를 지칭하는 수단은 언어의 필수 기능입니다. 우리가 선언이라고 불러왔던 변수의 선언을 이 책에서는 계산적 개체에 이름을 붙이는 수단 즉 추상적 수단으로 설명하고 있습니다.

const size = 2;

이 문장에 대해 인터프리터는 2라는 값을 size라는 이름에 연관 시킵니다. 이름이 2라는 수가 연관되면 그때부터 값2를 size라는 이름으로 지칭할수 있게 됩니다. 다른 예를 살펴보겠습니다.

const pi = 3.14159; 
const radius = 10; 
pi = radius * radius;
314.159 
 
const circumference = 2 * pi * radius; 
circumference;
62.8318

복합적인 연산 결과를 간단한 이름인 circumference로 지칭할 수 있다는 점에서 상수의 선언은 가장 단순한 추상화 수단이라고 할 수 있습니다. 일반적으로 계산적 객체는 그 구조가 복잡할 수 있는데 복잡한 구조의 세부 사항을 기억해 두고 객체를 사용할 때마다 그 구조를 거듭 명시하는것은 불편하기 떄문에 인터프리터를 이용하여 이름-객체 간 상호작용을 통해 점진적으로 만들어 나갈 수 있으므로 단계적 프로그래밍이 편해집니다.

이름과 값을 연관 시키고 이름으로부터 값을 조회할 수 있으려면 인터프리터는 반드시 이름-객체 쌍을 저장하는 메모리 공간을 갖게 되는데 이런 메모리 공간을 프로그램 환경(program environment) 라고 부릅니다.

1.1.3 연산자 조합의 평가

이번장의 목표중 하나는 절차적 사고에 관한 논점을 잘 구분하는것, 예를 들어 연산자 조합을 평가할떄 해석기가 따르는 다음과 같은 절차를 보겠습니다.

  • 주어진 연산자 조합을 평가하기 위해 다음을 수행한다.
  1. 조합의 피연산자 표현식들을 평가한다.
  2. 연산자가 나타내는 함수를 인수들에 적용한다.

단계 1은 주어진 조합의 평가 과정을 완료하기 위해서는 먼저 조합의 각 피연산자를 평가해야 함을 이야기 해줍니다. 이는 규칙의 한 단계에서 규칙 자신을 수행해야 함을 뜻하며 이 평가 규칙은 재귀적이라고 할 수 있습니다.

재귀라는 개념 덕분에 깊에 중척된 조합의 평가 규칙도 간결하게 표현할 수 있습니다. 재귀가 없다면 평가 과정을 복잡하게 서술해야 했어야 했습니다.

(2 + 4 * 6) * (3 + 12);

이 복합 연산자 조합을 평가하려면 서로 다른 네가지 조합에 평가 규칙을 적용해야 합니다. 그 과정을 하나의 트리 형태로 시각화 할수 있는데. 각 노드는 하나의 조합을 나타내고 노드에서 뻗어 나온 갈래들은 그 조합의 연산자와 피연산자들로 이어집니다. 이러한 트리 표현에서 조합의 평가 과정은 트리의 말단 노드에서 출발해서 피연산자 값들을 해당 연산자에 따라 결합해서 점차 위쪽 노드로 올려보내는 과정에 해당합니다. 일반적으로 재귀는 이처럼 트리 형태의 위계 구조로 조직화된 객체들을 다루는데 대단히 강력한 기법이에요. 이를 트리 누산이라고 부릅니다.

연산자 조합 평가 과정의 트리 표현, 각 부분 표현식의 값이 표시되어 있음.

중요한 점은, 단계 1을 재귀적으로 거듭 적용하다 보면 조합이 아니라 원시 표현식을 평가해야하는 지점에 도달한다는 점입니다. 표현식 평가에는 다음과 같은 규칙들이 적용됩니다.

  • 수치의 값은 해당 숫자들이 나타내는 바로 그 값이다.
  • 이름의 값은 현재 환경에서 그 이름에 연관된 객체이다.

여리서 중요한건 표현식 안의 이름이 뜻하는 바를 환경이 결정한다는 점입니다. 자바스크립트 같은 대화식 언어에서 x + 1같은 표현식의 값을 이야기 하려면 x라는 이름의 의미를 제공하는 환경에 관한 정보가 꼭 필요하게 됩니다. 평가가 일어나는 문맥을 제공하는 환경이라는 일반적 개념은 프로그램의 실행을 이해할때 중요한 역할을 합니다.

주의할점은 평가 규칙이 선언에는 적용되지 않는다는 점입니다. 예를 들어 const x = 3 을 평가할때 인터프리터가 두 인수에 상동 연산자(=)를 적용하는것이 아니며 간단히 말해 const x = 3은 조합이 아닙니다. const는 자바스크립트 키워드이며 키워드를 포함한 문장을 구문형이라고 하는데 구문형 마다 고유의 평가 규칙이 있습니다. 다양한 종류의 문장들과 표현식들은 프로그래밍 언어의 구문론을 형성합니다.

1.1.4 복합 함수

앞서 자바스크립트 프로그래밍의 원시 요소를 살펴 보았는데 정리를 해보자면

  • 수치와 산술 연산은 원시 데이터와 원시 함수에 해당한다.
  • 조합의 중첩은 연산들을 조합하는 수단을 제공한다.
  • 이름과 값을 연관시키는 상수 선언은 제한적이나마 추상화의 수단을 제공한다.

이번에는 함수 선언을 살펴보겠습니다. 복합 연산에 이름을 붙이고 그 연산을 하나의 단위로 지칭하게 해주는 함수 선언은 상수 언언보다 훨씬 강력한 추상화 기법입니다.

제곱에 관한 함수를 살펴보겠습니다.

function square(x) {	
  return x * x;
}

이것은 square라는 이름이 붙은 하나의 복합 함수 입니다. 이걸 하나의 함수 적용 표현식에서 사용할 수 있습니다.

square(21);
441

함수 적용은 표현식들로부터 더 큰 표현식을 만드는 또 다른 종류의 조합입니다. (다른 하나는 연산자 조합입니다.) 함수 적용을 평가할 때 인터프리터는 연산자 조합의 평가 절차와 상당히 비슷한 절차를 따릅니다.

  • 함수의 적용을 평가하려면 다음을 수행한다.
  1. 적용의 부분식들, 즉 함수 표현식과 인수 표현식들을 각각 평가한다.
  2. 함수, 즉 함수 표현식의 값을 인수 표현식 값들에 적용한다.
squre(2 + 5);
49

위와 같은 경우 인수 표현식은 하나의 복합 표현식인 연산자 조합 2 + 5 입니다.

square(square(3));
81

함수 적용 표현식을 담은 함수의 적용 인수 표현식으로 사용이 가능합니다. square를 다른 함수를 정의하는 구축 요소로도 사용이 가능합니다.

square(x) + square(x)

아래는 주어진 두 수의 제곱의 합을 산출하는 함수입니다.

function sum_of_squares(x, y) {	
  return square(x) + square(y);
}
sum_of_squares(3, 4);
25

더 나아가서 sum_of_squares 자체를 또 다른 함수의 구축 요소로 사용될 수 있습니다.

function f(a) {	
  return sum_of_square(a + 1, a * 2);
} 
f(5);
136

이런 복합 함수들 외에 모든 자바스크립트 환경은 인터프리터 자체에 내장된, 또는 표준 라이브러리로부터 적재한 원시함수들도 제공하고 있습니다.

이 글이 어떠셨나요? 이모지로 반응을 남겨주세요
M
Mason
Software Engineer · masonlab

코드와 비즈니스, 그 사이에서 배운 것들을 기록하고 공유합니다.

댓글 0

아직 댓글이 없어요. 첫 댓글을 남겨보세요.

이런 글은 어때요?