(Lispex)sicp.io
2.15 · 그림 언어 · 변환과 합성

기하 변환은 또 다른 페인터를 반환한다.

페인터 변환은 그 페인터에 넘기는 프레임을 바꿉니다. beside와 below와 뒤집기와 회전과 재귀 분할은 프로시저를 합성하고 마지막 실행은 여전히 보통의 유한 선분 목록을 만듭니다.

생각해 볼 질문

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

  • 하위 프레임을 만들어 페인터 변환하기
  • beside와 below를 프로시저 조합기로 정의하기
  • 같은 변환 원시 연산으로 뒤집기와 회전 표현하기
  • 기본 페인터를 바꾸지 않고 네 부분 합성 만들기
  • 깊이에 따라 선분 수가 자라는 재귀 분할 만들기
  • 렌더링한 SVG를 선택한 실행의 선분 transcript와 연결하기

transform-painter는 페인터의 선분 데이터를 다시 쓰지 않습니다. 단위 정사각형의 제어점 세 개를 바깥 프레임으로 옮기고 새 하위 프레임을 만듭니다. 원래 페인터는 그 하위 프레임 안에서 실행됩니다. beside와 below는 변환된 페인터 두 개를 만들고 선분 목록을 이어 붙이며 뒤집기와 회전도 제어점 세 개를 다르게 고른 결과입니다.

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

리스펙스 · SICP 코드SICP에 필요한 Scheme 호환 문법을 리스펙스 SICP 프로필로 실행합니다.
(begin
  (define (make-vect x y) (list x y))
  (define (xcor-vect vector) (car vector))
  (define (ycor-vect vector) (cadr vector))
  (define (add-vect left right)
    (make-vect (+ (xcor-vect left) (xcor-vect right))
               (+ (ycor-vect left) (ycor-vect right))))
  (define (sub-vect left right)
    (make-vect (- (xcor-vect left) (xcor-vect right))
               (- (ycor-vect left) (ycor-vect right))))
  (define (scale-vect factor vector)
    (make-vect (* factor (xcor-vect vector))
               (* factor (ycor-vect vector))))
  (define (make-segment start end) (list start end))
  (define (start-segment segment) (car segment))
  (define (end-segment segment) (cadr segment))
  (define (make-frame origin edge1 edge2)
    (list origin edge1 edge2))
  (define (origin-frame frame) (car frame))
  (define (edge1-frame frame) (cadr frame))
  (define (edge2-frame frame) (caddr frame))
  (define (frame-coordinate-map frame)
    (lambda (vector)
      (add-vect
        (origin-frame frame)
        (add-vect
          (scale-vect (xcor-vect vector) (edge1-frame frame))
          (scale-vect (ycor-vect vector) (edge2-frame frame))))))
  (define (map-segment coordinate-map segment)
    (make-segment
      (coordinate-map (start-segment segment))
      (coordinate-map (end-segment segment))))
  (define (segments->painter segment-list)
    (lambda (frame)
      (let ((coordinate-map (frame-coordinate-map frame)))
        (map (lambda (segment)
               (map-segment coordinate-map segment))
             segment-list))))
  (define (transform-painter painter origin corner1 corner2)
    (lambda (frame)
      (let* ((coordinate-map (frame-coordinate-map frame))
             (new-origin (coordinate-map origin))
             (mapped-corner1 (coordinate-map corner1))
             (mapped-corner2 (coordinate-map corner2)))
        (painter
          (make-frame
            new-origin
            (sub-vect mapped-corner1 new-origin)
            (sub-vect mapped-corner2 new-origin))))))
  (define (beside left right)
    (let ((paint-left
           (transform-painter
             left
             (make-vect 0.0 0.0)
             (make-vect 0.5 0.0)
             (make-vect 0.0 1.0)))
          (paint-right
           (transform-painter
             right
             (make-vect 0.5 0.0)
             (make-vect 1.0 0.0)
             (make-vect 0.5 1.0))))
      (lambda (frame)
        (append (paint-left frame)
                (paint-right frame)))))
  (define (below bottom top)
    (let ((paint-bottom
           (transform-painter
             bottom
             (make-vect 0.0 0.0)
             (make-vect 1.0 0.0)
             (make-vect 0.0 0.5)))
          (paint-top
           (transform-painter
             top
             (make-vect 0.0 0.5)
             (make-vect 1.0 0.5)
             (make-vect 0.0 1.0))))
      (lambda (frame)
        (append (paint-bottom frame)
                (paint-top frame)))))
  (define (flip-horiz painter)
    (transform-painter
      painter
      (make-vect 1.0 0.0)
      (make-vect 0.0 0.0)
      (make-vect 1.0 1.0)))
  (define (flip-vert painter)
    (transform-painter
      painter
      (make-vect 0.0 1.0)
      (make-vect 1.0 1.0)
      (make-vect 0.0 0.0)))
  (define (rotate180 painter)
    (transform-painter
      painter
      (make-vect 1.0 1.0)
      (make-vect 0.0 1.0)
      (make-vect 1.0 0.0)))
  (define (emit-segment segment)
    (let ((start (start-segment segment))
          (end (end-segment segment)))
      (display "SEG ")
      (display (xcor-vect start))
      (display " ")
      (display (ycor-vect start))
      (display " ")
      (display (xcor-vect end))
      (display " ")
      (display (ycor-vect end))
      (newline)))
  (define (paint painter frame)
    (let ((segments (painter frame)))
      (for-each emit-segment segments)
      (list 'segments (length segments))))
  (define chevron
    (segments->painter
      (list
        (make-segment (make-vect 0.1 0.12)
                      (make-vect 0.5 0.9))
        (make-segment (make-vect 0.5 0.9)
                      (make-vect 0.9 0.12))
        (make-segment (make-vect 0.25 0.42)
                      (make-vect 0.75 0.42)))))
  (define quartet
    (below
      (beside chevron (flip-horiz chevron))
      (beside (flip-vert chevron) (rotate180 chevron))))
  (paint quartet
         (make-frame (make-vect 0.04 0.04)
                     (make-vect 0.92 0.0)
                     (make-vect 0.0 0.92)))
)
리스펙스 학습용 런타임리스펙스 SICP 프로필 1.0.0
리스펙스 SICP 런타임 불러오는 중
리스펙스 · SICP 코드UTF-8 4,491 / 1,048,576바이트
예제
결과
출력
진단
보이는 실행 흐름0 / 0 개의 실행 이벤트
    이 브라우저 결과는 리스펙스 바우치나 권한이 아닙니다.wasm —
    실행과 연결된 그림 언어리스펙스가 만든 선분을 그립니다.
    런타임 불러오는 중
    이번 실행에서 올바른 SEG 줄을 만들지 못했습니다.
    로컬 리스펙스 transcript에서 선분 0개를 읽었습니다.
    SVG는 이 브라우저 실행이 출력한 SEG 줄에서 만들어집니다. 하나의 제한된 실행을 교육용으로 그린 것이며 증명이나 권한 기록이 아닙니다.
    예상 관찰

    네 부분 합성은 SEG 줄 열두 개를 출력하고 (segments 12)를 반환합니다. 깊이 3 right-split은 SEG 줄 45개를 출력하고 (segments 45)를 반환합니다. 두 SVG 모두 선택한 리스펙스 transcript에서 만들어집니다.

    실행 흐름에서 볼 점

    기본 페인터가 선분을 옮기기 전에 transform-painter가 하위 프레임을 만드는 각 호출을 찾으세요. right-split에서는 재귀 깊이를 따라 반환되는 페인터 프로시저를 본 뒤 프로시저 합성과 paint 시점의 유한 선분 생산을 구분하세요.

    직접 해보기

    힌트를 보기 전에 프로그램을 바꿔 보세요.

    원래 페인터를 아래에 두고 작은 복사본 두 개를 옆으로 놓아 그 위에 쌓는 up-split을 정의하세요. 깊이 3을 그리기 전에 선분 수를 예상하세요.

    힌트 하나 보기

    below painter (beside smaller smaller)를 사용하세요. 양의 깊이마다 현재 복사본 하나와 작은 복사본 두 개를 만들므로 선분 수 점화식은 right-split과 같습니다.