sicp.io
제2장 · 점검

인터페이스를 따르고 불변식을 지키고 표를 확장하세요.

실행 프로그램 스무 개로 제2장의 모든 수업을 다시 연결합니다. 추상화 장벽, 프로시저 데이터, 수열과 트리, 정체성, 기호 구조, 집합, 범용 디스패치, coercion과 내리기, 희소 대수, 페인터와 프레임을 함께 점검합니다.

생각해 볼 질문

데이터의 추상 계약과 표현 불변식과 정체성과 기호 구조와 등록된 메서드와 변환 경로와 기하 프레임을 뒤섞지 않고 설명할 수 있나요?

  • 생성자와 선택자 뒤에 표현 숨기기
  • 동작 계약을 보존하는 프로시저 데이터 표현 알아보기
  • 수열의 cdr을 따라가기
  • 깊이를 가정하지 않고 트리의 두 갈래 따라가기
  • 공유 객체와 같은 내용을 가진 별도 객체 구분하기
  • 열거·필터·매핑·누적 합성하기
  • 단순화 생성자로 기호 식 데이터 변환하기
  • 인용과 정체성과 재귀적 구조 동등성 구분하기
  • 타입 태그로 범용 연산 고르기
  • 구간의 하한과 상한 전파하기
  • 무순서·정렬·트리 집합 표현 비교하기
  • 한 허프만 트리로 인코딩과 디코딩하기
  • 복소수 인터페이스 뒤에서 직교형과 극형 선택하기
  • 명시적인 coercion이나 올리기로 혼합 수치 타입 맞추기
  • 범용 태그 메서드로 희소 다항식 더하고 곱하기
  • 단위 정사각형 페인터를 여러 프레임에 옮기기
  • 변환된 페인터를 합성해 유한한 재귀 선분 집합 만들기
  • 범용 디스패처를 고치지 않고 새 표현 설치하기
  • 공통 산술 메서드를 찾도록 값을 올리기
  • 투영과 다시 올리기가 정보를 보존할 때만 결과 내리기

유리수와 구간과 복소수는 구체적인 내용을 생성자와 선택자 뒤에 숨깁니다. 프로시저 데이터 수업은 계약을 더 직접적으로 보여 줍니다. 같은 관찰 가능한 연산을 제공한다면 쌍이나 점을 클로저로 표현할 수 있습니다.

수열과 트리는 데이터 모양을 따라 재귀합니다. 공유 정체성은 할당에 대한 별도 질문을 더하고 인용은 식처럼 생긴 데이터와 실행할 적용을 나눕니다. eq?와 equal?은 정체성과 구조에 서로 다른 질문을 합니다.

파이프라인과 기호 미분은 이름 붙인 경계에서 표현을 넘깁니다. 집합 수업은 무순서 전체 순회와 정렬된 조기 종료와 트리의 한 갈래 탐색을 비교하면서 추상 집합 연산과 표현별 순회를 분리합니다.

연산 표 예제는 설치와 적용을 나눕니다. 패키지는 연산·타입 키 아래 메서드를 등록하고 새 표현이 들어와도 apply-generic은 바뀌지 않습니다. 산술 tower도 같은 타입 산술과 올리기·내리기 정책을 분리합니다.

허프만과 복소수와 coercion과 다항식은 각각의 구조 불변식을 보존합니다. 범용 산술 수업은 투영한 결과를 다시 올려 원래 값과 같을 때만 더 단순한 타입으로 내립니다.

그림 언어는 같은 추상화 생각을 기하에 적용합니다. 페인터는 단위 정사각형 선분을 설명하고 변환은 하위 프레임과 새 페인터를 만들며 마지막 적용에서만 SEG 줄을 출력합니다. 흑백 SVG는 별도 TypeScript 그림이 아니라 그 transcript에서 파생됩니다.

SICP 코드UTF-8 346 / 1,048,576바이트
예제
결과
출력
진단
실행 추적0 / 0 개 이벤트
    실행은 브라우저 안에서 이루어지며 프로그램 결과와 실행 추적을 보여줍니다.
    예상 결과

    기존 열다섯 프로그램은 각 수업의 결과를 유지합니다. 프로시저 데이터는 (left right 1 2 3), 인용은 ((+ x 3) + x (+ 10 3) #t #t #f), 집합 표현은 ((1 3 5) (4 1 3 5) #f (1 3 4 5 7)), 데이터 지향 설치는 (4 8 rectangular swapped 3 4 25 3 4 25)를 반환합니다. 산술 tower는 ((rational 7 2) (integer 1) (integer 5) (complex (rational 3 1) (rational 1 1)))을 반환합니다. 그림 예제는 고정 런타임 한도에서 시각화용 SEG transcript를 출력합니다.

    실행 추적에서 볼 점

    생성자·선택자, 클로저 생성과 메시지, 수열·트리 재귀, 할당 정체성, quote 경계, 구조 비교, 집합 조기 종료, 연산 표 put/get, 타입 태그 디스패치, raise와 project, 다항식 항 병합, 프레임 좌표 변환, 하위 프레임 생성, 마지막 SEG 출력을 찾아보세요. 모든 실행 흐름은 선택한 유한 실행만 설명합니다.

    복습

    데이터 시스템을 생각하는 질문 스무 개

    추상화 장벽은 어떤 변경에서 우리를 보호해 줄까요?

    정답 순서쌍은 유용한 구현 장치이고 공개 개념은 인터페이스입니다. 표현 방식에 대한 지식을 안정된 인터페이스 뒤에 두면 나중의 변경이 다른 코드까지 퍼지지 않습니다.

    재귀 프로시저는 리스트의 모양을 어떻게 따라갈까요?

    정답 벡터는 인덱스를 사용하는 수열 약속입니다. vector-ref는 위치로 항목을 고르고 vector-set!은 전체 벡터를 다시 만들지 않고 한 위치를 바꿉니다.

    하나의 프로시저가 트리의 모든 깊이에서 어떻게 동작할까요?

    정답 제어 구조가 데이터 정의와 같은 모양을 가집니다. 그래서 얕은 리스트와 깊게 중첩된 트리를 깊이마다 따로 처리하지 않고 같은 프로시저로 다룰 수 있습니다.

    내용이 같은 것과 하나의 객체를 공유하는 것은 왜 다를까요?

    정답 출력의 #0=는 공유 객체를 처음 소개하고 #0#는 바로 그 객체를 다시 가리킵니다. 이런 표시는 일반 리스트 표기가 감출 수 있는 정체성을 보존합니다.

    수열 파이프라인은 중첩된 재귀를 어떻게 이름 붙은 단계로 바꿀까요?

    정답 accumulate는 +와 초기값 0으로 바뀐 수열을 값 하나에 모읍니다. 전체 프로그램은 여전히 재귀하지만 제어가 하나의 특수 목적 프로시저에 뒤섞이지 않고 다시 쓸 수 있는 단계에 나뉘어 있습니다.

    데이터 생성자는 대수 단순화와 미분 규칙을 어떻게 나눌까요?

    정답 make-sum과 make-product가 표현 정리를 맡습니다. 0을 더하는 경우와 0 또는 1을 곱하는 경우를 없애고 숫자끼리는 계산하므로 미분 분기에서는 수학 규칙을 단순화 세부 사항과 섞지 않아도 됩니다.

    연산 표는 서로 다른 표현에서도 하나의 인터페이스를 어떻게 유지할까요?

    정답 apply-generic은 get이 맞는 프로시저를 고른 뒤에야 태그를 떼고 내용만 넘깁니다. 사용하는 코드는 magnitude와 태그가 붙은 데이터만 주면 되며 어떤 표현인지 직접 묻지 않습니다. 새 표현을 더할 때 바뀌는 곳은 범용 인터페이스가 아니라 표입니다.

    산술은 두 경곗값의 저장 방식에 기대지 않고 범위를 어떻게 사용할까요?

    정답 덧셈은 두 아래 경곗값과 두 위 경곗값을 각각 더합니다. 곱셈은 구간이 0을 지날 때 가장 작거나 큰 결과가 다른 끝점 조합에서 나올 수 있으므로 네 끝점 곱을 모두 비교해야 합니다. 결과는 가능한 곱 전체를 포함하는 구간 포락입니다.

    집합 표현이 오름차순을 약속할 때 어떤 일을 더 하지 않아도 될까요?

    정답 union-ordered-set은 두 머리를 비교합니다. 더 작은 값을 남기고 그 리스트만 진행하며 머리가 같으면 값 하나만 남기고 양쪽을 모두 진행합니다. 재귀 단계마다 현재 머리 하나 이상을 소비하므로 처음부터 다시 찾지 않고 정렬 순서를 유지합니다.

    가중치가 있는 트리 하나가 우리가 쓰는 비트와 다시 복원하는 기호를 어떻게 함께 결정할까요?

    정답 인코딩은 다음 기호가 왼쪽 가지에 있는지 오른쪽 가지에 있는지 묻고 아래로 내려가기 전에 0이나 1을 기록합니다. 디코딩은 같은 결정을 반대 방향으로 소비합니다. 잎에 닿으면 그 기호를 내보내고 다음 부호를 읽기 위해 루트로 돌아갑니다. 같은 가중치에는 다른 올바른 트리가 있을 수 있지만 여기서는 명시적인 삽입 규칙이 이번 실행의 트리를 고정합니다.

    복소수 인터페이스 하나가 직교형과 극형 표현의 장점을 동시에 보존하려면 어떻게 해야 할까요?

    정답 첫 프로그램은 3 + 4i를 두 표현으로 각각 만들고 같은 네 선택자로 관찰합니다. close?는 삼각함수를 거친 부동소수점 값이 선언한 허용 오차 안에서 일치한다는 사실을 기록합니다. 두 번째 프로그램은 실수부와 허수부를 더해 직교형 결과를 만들고, 크기를 곱하고 각도를 더해 극형 결과를 만듭니다. 공통 선택자는 어느 결과에서도 내부 내용을 노출하지 않고 추상적인 값을 확인합니다.

    범용 산술은 실패한 coercion을 숨기거나 동급 표현 사이에서 무한히 왕복하지 않으면서 공통 타입을 어떻게 고를까요?

    정답 두 번째 프로그램은 타입 쌍마다 변환을 고르는 대신 순서가 있는 tower를 사용합니다. rank는 정수와 유리수와 복소수의 층을 정하고, raise는 바로 다음 층으로만 이동하며, raise-to는 두 값이 더 높은 입력 층에 닿을 때까지 반복합니다. 정수와 유리수는 유리수 덧셈을 사용하고 유리수와 복소수는 한 번 더 올린 뒤 복소수 덧셈을 사용합니다. 관련 없는 polynomial 태그는 rank가 없으므로 no-common-type을 반환합니다. tower는 이 세 타입의 순서와 승격 경로를 고정합니다.

    범용 산술 시스템은 항 목록의 세부 표현을 클라이언트 코드에 퍼뜨리지 않고 다항식 구조를 어떻게 다룰까요?

    정답 두 번째 프로그램은 한 항을 다른 다항식의 모든 항에 곱하고, 차수는 더하고 계수는 곱한 뒤, 부분 곱을 add-terms로 병합합니다. (x + 1)(x − 1)에서는 가운데 두 항이 상쇄되어 x² − 1만 남습니다. 별도 평가기는 패키지 선택자를 사용해 x = 3에서 8을 보고합니다. 이 수업은 태그 기반 덧셈과 곱셈과 정규화와 평가로 희소 단일 변수 정수 계수 다항식을 다룹니다.

    페인터 하나가 정사각형과 기울어진 프레임과 크기가 다른 프레임 안에서 같은 그림을 어떻게 설명할까요?

    정답 첫 실행은 테두리와 대각선 페인터를 정사각형 프레임에 놓습니다. 두 번째 실행은 마름모 페인터를 기울어진 프레임에 적용합니다. 페인터 정의에는 마지막 화면 좌표가 들어 있지 않습니다. 실행은 옮긴 선분마다 SEG 줄 하나를 출력하고 앱 로컬 시각화가 transcript 줄을 흑백 SVG로 읽습니다. 반환되는 (segments n) 값은 별도 관찰로 남습니다.

    몇 가지 프레임 변환만으로 재사용할 수 있는 그림 조합 언어를 어떻게 만들까요?

    정답 첫 실행은 변환한 갈매기 모양 네 개를 한 정사각형에 놓습니다. 두 번째는 right-split을 재귀적으로 정의합니다. 왼쪽 절반에는 원래 페인터를 두고 오른쪽 절반에는 작은 복사본 두 개를 위아래로 쌓습니다. 깊이 3에서 선분 세 개짜리 기본 페인터는 옮긴 선분 45개를 만듭니다. 시각화는 리스펙스가 출력한 SEG 줄만 그리며 브라우저 렌더링 제한은 런타임 자체의 실행 한도와 구분됩니다.

    생성자와 선택자가 같은 계약을 만족한다면 추상 데이터 값은 반드시 한 가지 물리 표현을 가져야 할까요?

    정답 점 예제는 메시지 디스패치를 사용합니다. make-point는 x, y, sum 메시지에 답하는 프로시저를 반환합니다. 선택자는 그 메시지만 압니다. 같은 관찰 가능한 생성자·선택자 동작을 제공한다면 보통 쌍이나 벡터나 다른 클로저로 구현을 바꿀 수 있습니다.

    평가기에게 (+ x 3)이 실행할 적용인지 살펴볼 리스트인지 어떻게 알려 줄까요?

    정답 eq?는 두 피연산자가 같은 기호나 객체 정체성을 나타내는지 묻습니다. equal?은 복합 데이터 안으로 내려가 구조와 잎을 비교합니다. 따라서 두 번째 프로그램은 하나의 공유 리스트와 내용이 같은 별도 리스트를 구분하면서 구조적 동등성은 인정합니다.

    집합 표현이 순서나 탐색 트리 모양을 약속하면 어떤 연산이 더 적은 일을 할 수 있을까요?

    정답 이진 탐색 트리는 더 작은 원소를 왼쪽에, 더 큰 원소를 오른쪽에 둡니다. 각 비교는 모든 원소를 훑지 않고 한 갈래를 고릅니다. tree->list는 중위 순회로 정렬 수열을 되찾습니다. 직접 만든 트리는 균형 모양에서의 탐색 규칙을 보여 주고 한쪽으로 기운 트리는 선형 경로를 드러냅니다.

    하나의 중앙 조건식을 다시 열지 않고 범용 시스템에 새 표현이나 연산을 어떻게 더할 수 있을까요?

    정답 두 번째 프로그램은 integer 설명 메서드 하나로 시작합니다. rational 값은 처음에는 no-method를 만듭니다. 표 항목 하나를 더 설치하면 같은 describe 디스패처가 그 값을 받아들이며 디스패처나 integer 패키지는 수정하지 않습니다. 여기서 가산성은 새 키 아래 메서드를 등록하는 명시적이고 유한한 확장입니다.

    혼합 산술은 어떻게 공통 타입을 찾고 정보를 잃지 않으면서 다시 더 단순한 타입으로 돌아갈까요?

    정답 drop은 반대 방향을 보수적으로 시도합니다. 유리수는 분모가 1일 때만 정수로 투영되고 복소수는 허수부가 정확히 0일 때만 실수 성분으로 투영됩니다. 투영한 값을 다시 올려 원래 값과 비교한 뒤에만 단순화를 계속하므로 0이 아닌 허수 성분은 사라지지 않습니다.