ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 내 맘대로 프로그램 설계 4. - 고정 크기 데이터.
    프로그래밍/설계 2018. 5. 29. 18:31

    이번 섹션에서는 섹션2,3 정리와 고정 크기 데이터에 대하여 배우도록 하겠습니다.

    다시 연재 시작했어요.


    내 맘대로 하는 프로그램 설계 시리즈.

    Chapter1 - 간단한 데이터 처리(4섹션)

    2017/12/27 - [프로그래밍/설계] - 내 맘대로 프로그램 설계 1. - 이유와 준비.

    2018/01/11 - [프로그래밍/설계] - 내 맘대로 프로그램 설계 2. - 데이터 타입.

    2018/01/16 - [프로그래밍/설계] - 내 맘대로 프로그램 설계 3. - 함수와 변수.

    2018/05/29 - [프로그래밍/설계] - 내 맘대로 프로그램 설계 4. - 고정 크기 데이터.(현재)

    2017/06/30 - [프로그래밍/설계] - 프로그래밍과 추상화에 대하여.[부록]


    Chapter2 - 임의의 데이터 처리

    2018/06/10 - [프로그래밍/설계] - 내 맘대로 프로그램 설계 5. - 리스트와 재귀.

    1. 시작하기.

    본격적으로 Section4에 들어가기 전에 지난 시간에 배웠던 것들을 정리하고 넘어가도록 합시다.


    저번 글이 예상외로 너무 길어져서 정리하는 부분을 넣지 못했습니다.


    여러분도 처음하는 것이라 소화하기 힘들었을테니 이번기회에 핵심부분들을 다시한번 보고 넘어가도록 합시다.

    일부 내용은 보충하기도 하고요.


    망각곡선[from Wikipedia]

    에빙하우스의 망각곡선에 의하면 여러분은 지금쯤 꽤나 까먹었을테니까.

    (아마도? 그냥 합리화좀 하자.)


    Section2, 3이란 긴 분량에 비해 의외로 심플.


    원자 데이터(Atomic Data).

    가장 처음 배웠던 것 입니다.

    숫자, 문자열, 불린 값등은 조각으로 나뉠 수 없지만 각 데이터의 속성을 이용한 계산을 할 수 있었습니다.

    각 데이터 자체에 의미가 있어 리터럴 상수(Literal Constant)라고들 부르기도 한다.


    상수 정의.

    반복적으로 사용하는 데이터에 이름을 부여해줍니다.

    (define 상수이름 상수값)

    Racket은 함수형 언어의 특성 때문에 변수가 없으며, 선언 후에 값의 변경이 안된다고 하였습니다.

    그래서 앞 강좌에서는 변수 정의라고 했지만, 이제 상수를 선언한다고 부릅니다.

    사람이 특별히 이름을 붙여주어 의미를 가지게 되는 상수라 하여 심볼릭 상수(Symbolic Constant)라고도 한다.


    함수 정의와 사용.

    함수는 특정 동작을 수행하기 위한 일정한 코드의 부분이다.

    ;정의
    (define (함수이름 매개변수 ..)
      함수표현식)
    
    ;사용
    (함수이름 인자 ..)

    함수는 1~2가지의 기능을 가지는 것이 적당하며, 간결하고 직관적으로 작성되어야 한다.

    여러 기능을 만들어야 한다면 함수를 기능에 따라 쪼개도록 하자.


    작명법.

    누구나 보면 내용을 알아볼 수 있도록 핵심을 찌르는 명확함을 가지고 있어야 한다.


    주로 요구사항의

    동사, 명사(주어, 목적어)

    로부터 뽑아내면 된다.


    다만 이름이 너무 길 경우, 축약하는 방법을 고려해보아야 한다.

    이름이 길 경우 사람이 읽고 해석하는데 걸리는 시간, 타이핑하는데 낭비되는 시간이 많다.


    디자인 레시피.

    1편의 디자인 레시피를 따르되, 규모에 따라 디자인 레시피를 축소시켜 사용해도 된다.


    테스트 케이스를 만드는 작업은 꼭 해보자.

    코드 정리는 한번에 하지말고, 틈틈히 해버리자!!

    +.

    프로그램을 설계할 때 필요한 지식으로는 크게 2가지로 나뉠 수 있다.

    도메인 지식과 기술 지식이다.


    도메인 지식은 환경 또는 프로젝트에 종속되는 특정한 지식이다.

    CAD 프로그램을 만들려면 건축, DNA 분석 프로그램을 만들 때는 생명과학 지식이 도메인 지식이다.

    간단히 말하자면, 프로그래밍 자체가 아닌 외부영역의 지식이라 할 수 있다.


    기술 지식은 프로그래밍 언어(문법, 라이브러리 종류 등), 컴퓨터 구조와 같은 것.


    ++.

    check-expect라는 함수를 사용하면 쉽게 테스트를 할 수 있다.


    첫 번째는 테스트 값과 예측 값이 같은 경우, 두 번째는 테스트 값과 예측 값이 다를 때를 가정하여 예제를 구성해보았다.

    Code 4.1

    (check-expect (+ 10 5) 15)
    (check-expect (- 10 5) 10)

    결과:

    테스트 값과 예상 값의 차이를 알려주고, 파일의 몇 번째 줄에서 에러가 났는지 체크해주는 것을 볼 수 있다.

    모든 테스트가 통과하면 실행창에 '모든 n개 테스트 통과함!'이라고만 뜬다.

    2개중 1개 테스트 실패함이라고 표시되어야 하지만 반대로 표시되는 소소한 버그가 존재한다.


    2. 고정 크기 데이터.

    2.1 이미지 추가설명.

    이번에 다룰 고정 크기 데이터는 각종 원자데이터를 필두로 하여 저번 챕터때 지겹도록 써본 것들입니다.


    네. 이미 알고 있던 것들이죠.


    저번에 조금 덜 다루었던 이미지(Image)를 좀더 다루어보도록 하겠습니다.

    좀 더 재미있게 프로그래밍을 배워보기 위해서 인데,

    이번에 소개시켜 드릴것 까지 Section2에서 다루었다면 여러분들이 힘들었을 거에요.


    당연히 이미지를 다룰려면 '2htdp/image'라는 배움꾸러미(라이브러리)를 사용해야 합니다.


    2.1.1 이미지 생성

    저번에 이미지를 다루었던 방법은 이미지의 속성값(width, height)을 이용하는 것이었습니다.

    이번엔 이미지를 복사, 붙여넣기하는 것이 아니라 생성시켜봅시다.


    사용법이 더 궁금하다면 여기링크를 참고해주세요.

    이 강좌는 사용법을 배우는 것이 목적이 아닙니다.

    [이미지 생성법은 그냥 이런 것도 있다는 것이므로 중요도가 낮습니다.]


    • line : 두점을 이용해 선을 그림.
    • circle : 반지름의 크기대로 원을 그림.
    • ellipse : 장축과 단축으로 타원을 그림.
    • rectangle : 밑변과 높이로 직사각형을 그림.
    • triangle : 정삼각형을 그림.
    • star : 별을 그림.
    • text : 문자열 그림을 그림.

    그럼 예제를 통해 사용하는 방법을 알아보도록 합시다.


    Code 4.2

    ;----------library
    (require 2htdp/image)
    
    ;----------function
    (line 30 -20 "purple")
    (line 30 20  "purple")
    (line 30 20  "silver")
    
    (circle 30 "outline" "purple")
    (circle 20 "outline" "purple")
    (circle 30 "solid"   "purple")
    (circle 30 255       "purple")
    
    (ellipse 30 20 "outline" "purple")
    (ellipse 20 30 "outline" "purple")
    
    (rectangle 30 20 "outline" "purple")
    (rectangle 20 30 "outline" "purple")
    
    (triangle 30 "outline" "purple")
    (triangle 20 "outline" "purple")
    
    (star 30 "outline" "purple")
    (star 20 "outline" "purple")
    
    (text "Hello" 30 "purple")
    (text "Hello" 20 "purple")

    결과:

    2.1.2 이미지 합성.

    이미지들을 합성 시켜 봅시다.

    • overlay: 중심을 기준으로 하여 이미지들을 위에 배치합니다.
    • overlay/xy : 두번째 이미지를 xy축을 사용하여 배치합니다.(왼쪽 모서리 기준)
    • overlay/align : 이미지들을 왼쪽 정렬, 가운데 정렬처럼 정렬시킵니다..
    • underlay : overlay와 반대로 이미지들을 배치합니다.
    • empty-scene : 너비와 높이에 맞추어 하얀 사각형을 그림.
    • place-image: 지정된 이미지에 다른 이미지를 배치한다. 단, 테두리를 벗어나면 잘린다.
    • scene+line : 주어진 이미지에 선을 그린다.

    그럼 예제를 통해 사용하는 방법을 알아보도록 합시다.

    (예제 사용 중 궁금한 점이 있다면 위에 걸어놓았던 링크 참조.)


    Code 4.3

    (overlay (triangle 30 "solid" "black" )
             (square   40 "solid" "purple")
             (circle   30 "solid" "silver"))
    
    (overlay/xy (triangle 30 "solid" "black")
                0 0
                (overlay/xy (square 40 "solid" "purple")
                            20 10
                            (circle 30 "solid" "silver")))
    
    (overlay/align "left" "bottom"
                  (triangle 30 "solid" "black" )
                  (square   40 "solid" "purple")
                  (circle   30 "solid" "silver"))
    
    (underlay (circle   30 "solid" "silver")
              (square   40 "solid" "purple")
              (triangle 30 "solid" "black" ))
    
    (empty-scene 30 20)
    
    (place-image (circle 30 "solid" "black")
                 20 0
                 (empty-scene 50 50))
    
    (scene+line (ellipse   30 20 "outline" "purple")
                 0  20 30 0   "purple")
    (scene+line (rectangle 30 20 "solid"   "silver")
                 10 20 20 -10 "purple")

    결과:


    2.1.3 이미지 활용.

    재미로 이미지를 활용한 프로그램을 만들어 보도록 할까요?

    '2htdp/universe'라는 라이브러리를 추가해봅시다.(아마 universe.rkt라고 되어 있을 겁니다.)


    운영체제[from Wikipedia]

    우리가 사용하는 BSL은 프로그래밍 언어이고, 프로그래밍 언어를 실행하는 Dr.Racket은 소형 운영체제라 생각할 수 있습니다.
    [정확히 말하면 운영체제는 아니고 비스무레한 역할을 한다는 것 입니다.]


    운영체제에 대해 쉽게 설명하자면 하드웨어와 응용프로그램(어플리케이션)을 중재 해주는 프로그램입니다.

    윈도우, 맥, 리눅스나 안드로이드, IOS라 불리는 것들이 대표적인 예라 할 수 있죠.


    Dr.Racket이 실제로 직접적인 하드웨어 제어를 하는 것은 아니지만 저희가 BSL로 만든 프로그램이 컴퓨터의 자원(램, CPU, 입출력 등)을 활용하는데 중재 해주는 역할을 하기 때문에 소형 운영체제처럼 여길 수 있습니다.


    big-bang 함수 설명[from racket-lang]


    universe라는 라이브러리에서 제공하는 big-bang이란 매커니즘은 프로그램의 상태를 추적하며,

    어떤 함수가 어떤 이벤트를 처리하는지 운영체제에게 알려주는 역할을 합니다.


    이를 위해 하나의 필수 하위 표현식(state-expr)이 있고, 그 값은 프로그램의 초기값이 됩니다.
    위 그림에서는 World_0 이겠죠?

    추후 World-State라고 표시하겠습니다.


    이외에 big-bang은 필수 요소(clause)과 많은 옵션의 요소(clause)들로 구성됩니다.
    필수: to-draw
    선택: on-tick, on-mouse 등..


    to-draw라는 기능은 Dr.Racket에 초기상태를 포함하여 프로그램의 상태를 렌더링하는 방법을 알려줍니다.
    다른 옵션의 요소들은 운영체제에게 특정 기능이 특정 이벤트를 처리한다는 것을 알려줍니다.

    clause라고 나온 것들이 아래 설명의 이벤트 핸들러(Event Handler) 역할을 했다는 뜻.


     용어정리

    - 상태 관련 용어

    해(Solution):

     일련의 행동 또는 하나의 상태

     각 행동이 순차적으로 변화시켜 목표하는 상태에 도달


    세계(World):

     문제에 포함된 대상들과 이들의 상황을 포괄적으로 지칭


    상태(State):

     특정 시점에 세계가 처해 있는 모습


    - UI 관련 용어

    렌더링(Rendering):

     컴퓨터 프로그램을 사용하여 모델(또는 이들을 모아놓은 장면인 씬(scene) 파일)로부터 영상을 만들어내는 과정.
     간단한 UCC 같이 영상을 만들어보신 분들은 렌더링이란 단어가 익숙할 겁니다.


     이벤트(Event):

     프로그램에 의해 감지되고 처리될 수 있는 동작이나 사건.

     마우스나 키보드의 입력이 대표적인 예입니다.


    이벤트 핸들러(Event Handler):
    이벤트가 일어났을 때 받아서 처리하는 함수입니다.
    정확히 말하면 콜백(Callback)이지만, 여기서는 위에 나온 개념 정도로만 알아도 상관 없습니다.


    BSL에서 이벤트 관리는 해당 기능이 프로그램의 상태와 이벤트에 대한 설명에 시간을 사용하고 다음 상태의 프로그램을 생성하는 것을 의미합니다.
    즉, 우리는 프로그램의 현 상태에 대해 그때 그때 만들면 됩니다.


    간단하게 만들어보죠.


    일단 주요 big-bang 요소들의 핸들러를 위한 계약과 목적을 알아보도록 합시다.

    소괄호'()'안의 항목은 써도 되고 안 써도 되는 항목입니다.


    예를 들어

    to-draw의 핸들러 함수는 WorldState 또는 WorldState width height 가 매개변수가 될 수 있고.

    on-tick에 쓸 핸들러 함수는 WorldState, WorldState rate 또는 WorldState rate limit 이 매개변수가 될 수 있는 형식입니다.


    • to-draw: WorldState (width height) -> image; 이미지 렌더링, 필수
    • on-tick: WorldState (rate (limit)) -> WorldState ; 시간의 지남
    • on-key: WorldState, KeyEvent -> WorldState ; 키보드 이벤트
    • on-mouse: WorldState integer integer MouseEvent -> WorldState ; 마우스 이벤트
    • stop-when: WorldState (WorldState) -> boolean (image) ; 멈출 때
    • name: NULL -> symbol or string ; 창 제목

    * 공식 문서에서 HandlerResult란 표현이 있었는데 WorldState와 사실상 같으므로, WorldState로 표시했습니다.

    같은 이유로 scene도 image라 표시.


    2htdp/universe의 데이터 타입.

    WorldState는 아무 데이터 타입이나 사용할 수 있습니다.


    width, height, limit은 자연수(integer), rate는 실수(real).


    KeyEvent는 문자열(string)이지만(예 a키는 "a", 스페이스바는 " ") 특수키는 확장된 방식으로 표현합니다.

    [엔터: "\r", 탭: "\t", 백스페이스: "\b", 시프트: "shift", 화살표: "left" "right" "up" "down" 같은 방식]


    MouseEvent도 제약된 문자열(string) 형식으로 다음 동작 중 하나입니다 .

    [누름: "button-down", 땜: "button-up", 드래그: "drag", 움직임: "move", 창안으로: "enter", 창밖으로: "leave"]


    기타 파라미터에 대한 사항은 2htdp/universe 라이브러리 문서를 참고해주세요.

    처음엔 필수 요소인 to-draw를 이용해 만들어보겠습니다.

    숫자(WorldState)를 통해 image를 만드는 핸들러 함수(number->circle)를 만들고 테스트 해봅시다.


    이제부터 함수 테스트는 check-expect를 이용하도록 하겠습니다.

    계약과 목적 또한 충분히 숙지했으니, Constract나 Purpose라는 표시 없이 넣도록 하겠습니다.

    코드를 좀 더 간결하게 보이기 위한 노력입니다.

    Code 4.4 - 1

    ;----------library
    (require 2htdp/universe)
    
    ;----------fuction
    ;;number->circle: WorldState -> image
    ;;Make circle from number.
    (define (number->circle r)
      (circle r "solid" "purple"))
    
    ;----------fuction test
    (check-expect (number->circle 10)                    )
    (check-expect (number->circle 100) )
    
    ;----------big-bang test
    (big-bang  100 [to-draw number->circle])

    결과:

    * 이번까지는 이미지를 직접 삽입하여 보여주었지만, 다음부터는 아래 코드처럼 그냥 코드로 검증할 예정입니다.

    실제로 프로그래밍을 할 때 대부분의 언어는 텍스트 파일로 사용하고, 이미지는 외부에서 불러서 사용합니다.

    Code 4.4 -2

    (check-expect (number->circle 10)
                  (circle 10  "solid" "purple"))
    (check-expect (number->circle 100)
                  (circle 100 "solid" "purple"))


    숫자(반지름 r)에 따라 원을 그리는 함수를 만들고, 테스트 했습니다.

    그리고 big-bang 함수를 써보았는데요, 초기값은 100, 그리는 요소로 위에서 만든 number->circle을 사용했습니다.
    이때 World 창이 실행되어 반지름이 100인 원 이미지가 출력 되었고, X버튼을 누를 때까지 실행 영역은 그대로 멈춰있습니다.
    마치 계속 실행되고 있는 것처럼요.


    여러분이 생각한 것처럼 실제로 실행이 되고 있는 중이 맞고,
    창을 닫게 되면 그 상태의 초기 값을 반환하게 됩니다.(여기서는 100)



    이번에는 이미지를 제어하면서 조금 더 알아봅시다.

    화살표 키를 이용하여 원의 크기를 제어하고(1씩), 10 미만으로 원의 크기가 작아지면 종료되도록 합니다.

    창 제목도 커스텀 해보겠습니다.


    이때 big-bang의 World 상태는 이벤트가 일어날 때(키보드가 눌릴 때)

    마치 이미지를 연속으로 보여주는 필름 영화처럼 변화하게 됩니다.

    위에서 나왔던 World_0->World_1->World_2 ... 같은 식으로요.

    Film Strip[from Pixabay]


    즉, 저희는 입력 받은 World의 상태가 이벤트가 일어날 때마다

    이벤트 핸들러가 기존 World의 상태를 처리(크기 조절)하여 반환하고,

    새로운 상태로 이미지가 렌더링 되도록 할 것입니다.


    이번에도 디자인 레시피를 따라 만들어 보겠습니다.

    저번 강좌에서는 디자인 레시피를 단계별로 따라가며 적용해보았지만,

    이번에는 한번에 작성을 해보죠.


    우선 하나의 커다란 문제를 여러가지 보조 문제로 나누어봅시다.

    보조 문제와 관련된 big-bang의 요소는 [Clause] 와 같은 방식으로 표시하였습니다.


    * 원 크기 제어.

      - 화살표 키로 크기 제어.[on-key]

      - 원 그리기.[to-draw]


    * 프로그램 종료 제어.

      - 10 미만 가려내기.[stop-when]


    * 창 제목 제어.

      - 창 제목[name]


    이제 보조 문제들을 풀어볼 차례입니다.


    * 원 크기 제어.

      - 화살표 키로 크기 제어.[on-key]

    왼쪽, 위 화살표는 원의 크기를 1씩 줄이고, 오른쪽과 아래 화살표로는 1씩 커지도록 할 겁니다.

    (나머지 키로는 아무런 영향을 미치지 않게 하고요.)


    여기서 계약, 목적, 헤더가 완성됩니다.

    ;;key-control: WorldState KeyEvent -> WorldState
    ;;Size control with arrow-key.
    (define (key-control world a-key) ...)

    key로 조절을 하기 위한 것이니 함수 이름은 key-control.

    입출력값은 on-key에서 요구하는 대로 WorldState string -> WorldState.

    매개 변수 이름은 WorldState값 -> world, 키보드 키(string) -> a-key라고 표현합니다.

    마지막으로 목적은 화살표 키를 이용한 크기 조절이 되겠죠.


    함수에서 판독할 조건

    $$key-control = \begin{cases}
    왼쪽 & 화살표, & -1 \\
    오른쪽 & 화살표, & +1 \\
    위쪽 & 화살표, & +1 \\
    아래쪽 & 화살표, & -1 \\
    나머지 & , & 반응없음.
    \end{cases}$$


    이때 키보드 신호를 판독하기 위해 key=? 라는 함수(universe 라이브러리에 포함)로 키보드 이벤트를 판독하도록 했습니다.

    key=? 함수는 입력 받은 키보드 신호가 같으면 #true를 다르면 #false를 반환 합니다.


    테스트로는 10을 기준으로 좌우상하, a키, f1키를 사용해보았습니다.

    Code 4.5 - 1

    ;----------fuction
    ;;key-control: WorldState KeyEvent -> WorldState
    ;;Size control with  arrow-key.
    (define (key-control world a-key)
      (cond
        [(key=? a-key "left" )  (- world 1)]
        [(key=? a-key "right")  (+ world 1)]
        [(key=? a-key "up"   )  (- world 1)]
        [(key=? a-key "down" )  (+ world 1)]
        [(= (string-length a-key) 1)  world] ; order-free checking
        [ else                        world]))
    
    ;----------fuction test
    (check-expect (key-control 10 "left" ) 9 )
    (check-expect (key-control 10 "right") 11)
    (check-expect (key-control 10 "up"   ) 9 )
    (check-expect (key-control 10 "down" ) 11)
    (check-expect (key-control 10 "a"    ) 10)
    (check-expect (key-control 10 "f1"   ) 10)

    결과: 모든 6개 테스트 통과함!


    key-control은 키보드 이벤트에 대한 핸들러가 될 것입니다.


      - 원 그리기.[to-draw]

    위에서 만든 number->circle 함수를 쓰도록 하겠습니다.


    * 프로그램 종료 제어.

      - 10 미만 가려내기.[stop-when]

    WorldState값이 10 미만이 되면 #true를 반환하고, 나머지 경우는 #false를 반환하면 된다.


    역시 계약, 목적, 헤더를 완성합니다.

    ;;under-ten?: WorldState -> boolean
    ;;Check under than 10.
    (define (under-ten? world) ...)

    10보다 작은지 판독하는 것이므로 함수 이름은 under-ten?.

    입출력은 stop-when이 요구하는 것처럼 WorldState -> boolean.

    매개변수는 key-control과 마찬가지로 world.

    목적은 10보다 작은지 확인하는 것.


    이건 경우의 수가 작으므로, if를 사용하여 처리하였습니다.

    10 미만일 때 #true가 되야 되므로 경계 값인 11, 10, 9를 테스트 케이스로 사용했습니다.

    Code 4.5 - 2

    ;----------fuction
    ;;under-ten?: WorldState -> boolean
    ;;Check under than 10.
    (define (under-ten? world)
      (if (> 10 world)
         #true #false))
    
    ;----------fuction test
    (check-expect (under-ten? 11) #false)
    (check-expect (under-ten? 10) #false)
    (check-expect (under-ten? 9)  #true )

    결과: 모든 3개 테스트 통과함!



    * 창 제목 제어.

      - 창 이름.[name]

    창 이름은 함수가 아닌 상수.

    창 이름을 Big-Bang TEST로 사용합니다.

    ;----------constant
    (define window-name "Big-Bang TEST")


    * Big-Bang 테스트 하기.

    마지막으로 테스트를 한번 해봅시다!

    기존에 만들었던 함수들을 이벤트 핸들러에 연결하였습니다.

    Code 4.5 - 4

    ;----------big-bang test
    (big-bang 100
      [to-draw   number->circle]
      [on-key    key-control   ]
      [stop-when under-ten?    ]
      [name      window-name   ])

    답:

    화살표 키로 이미지의 크기를 줄여 10 미만이 된다면 사진처럼 마지막 상태값(9)을 반환하게 됩니다.

    * 블로그의 폰트가 고정폭이 아니라 가로 정렬이 이상해 보이지만, 고정폭 폰트로 보면 깔끔합니다.


    의도대로 잘 돌아가는 것을 볼 수 있습니다.


    on-tick이나 on-mouse 같은 요소들을 이용해서 이미지를 제어해보고,

    이미지를 합성 기능도 사용해보세요.


    혹시 중간에 막힌다면 HtDP를 참고해보도록 하고요.

    2htdp/univers 라이브러리의 사용법이 궁금하면 역시 공식 문서를 참고하고요.

    옛날과 달리 요즘은 프로그램을 만들 때 각종 라이브러리들을 적극적으로 사용하니, 문서들을 읽는 버릇을 들이는 것이 좋습니다.


    방금 썼던 big-bang 표현으로 프로그램의 이벤트, 상태 변환, 속성 결정법 등을 체험하였습니다.

    이번엔 지금까지 다루어왔던 데이터 타입과는 다른 구조체라는 것을 배워봅시다.


    2.2 구조체(Struct).

    2.2.1 구조체의 필요성.

    혹시 이미지 사용법을 보는 도중 image-color?라는 것이 궁금해서 들어간적이 있나요?

    image-color?의 설명을 보면  색 구조체(Struct)가 있던 것인지  확인하고 불린값(Boolean)으로 반환해준다고 되어 있습니다.




    image-color?

    (image-color? x) → boolean?
      x : any/c

    Determines if x represents a color. Strings, symbols, and color structs are allowed as colors.




    또한 그 밑의 Color란 구조체(Struct)의 설명을 보면 0~255의 RGB값(Red, Green, Blue)과 Alpha(투명도)를 이용하여 make-color란 생성자를 통해 색을 정의한다고 되어 있습니다.




    Color

    (struct	 	color (red green blue alpha)
        #:extra-constructor-name make-color)
      red : (and/c natural-number/c (<=/c 255))
      green : (and/c natural-number/c (<=/c 255))
      blue : (and/c natural-number/c (<=/c 255))
      alpha : (and/c natural-number/c (<=/c 255))

    The color struct defines a color with red, green, blue, and alpha components that range from 0 to 255.




    혹시 구조체(Struct)의 필요성에 대해 눈치를 챈 분이 있나요?


    함수에서 입력 값이 딱 1개의 수나 기호를 가지는 경우는 많지 않습니다.

    여러 정보를 함수에 입력해야 되는 경우가 잦다는 것인데, 여러 정보를 묶어서(Compound) 하나의 자료로 나타내는 것이 효율적입니다.

    이때 구조체는 다음처럼 고정된 갯수의 값들을 묶어서 표현해주는 역할을 합니다.

    사실 '4. 고정 크기 데이터'라는 섹션 제목은 구조체를 의미합니다.


    구조체 구조.


    위에 나온 Color란 구조체에서는 빨강, 초록, 파랑과 투명도란 값들을 모아서 하나의 색을 이루는 의미가 있는 단위가 된 예입니다.


    예를 들어 바로 위 '구조체 구조'라는 표에 사용된 노랑색은


    구조체 이름: color

    red: 238

    green: 169

    blue: 16

    alpha: 255

    (alpha 값은 투명도로, 제 표의 노랑색은 투명도를 딱히 설정해놓지 않아 255가 들어갔습니다.)


    처럼 표현할 수 있습니다.

    2.2.2 구조체 생성 및 기초 사용법.

    이제 구조체를 만들고 사용하는 방법에 대하여 알아보도록 합시다.


    제가 사용할 구조체의 예는 음악입니다.

    음원에 대한 메타데이터로는 아티스트 이름, 트랙 번호, 장르, 발매 년도 같은 정보들이 들어가 있죠.

    * 메타데이터: 데이터를 위한 데이터라는 뜻으로, 데이터에 대한 설명을 포함하고 있습니다. 제 예제에서는 아티스트 이름, 트랙 번호 등이 음원이란 데이터를 설명하는 데이터로 사용되고 있습니다.


    - 구조체 정의

    구조체를 정의하는 방법도 기존 함수나 상수 정의를 할 때와 비슷합니다.


    정의: 구조체의 이름과 들어갈 데이터 이름으로 구조체의 틀을 만들어줍니다.

    (define-struct 구조체이름 (매개변수1 매개변수2 ..))

    예) 구조체 이름: 노래, 구조체 매개변수: 아티스트, 트랙, 장르, 년도

    Code 4.6 - 1

    (define-struct Song (artist track genre year))

    함수를 정의할 때와 마찬가지로 틀만 만들어져 있습니다.

    구조체를 사용하려면 데이터를 모아서 구조체를 생성시켜 주어야죠.


    - 구조체 사용법

    생성: 실제 데이터를 넣는 과정입니다.

    (make-구조체이름 인자1 인자2 ...)

    추출: 구조체에서 인자값을 알아내는 방법입니다.

    (구조체이름-매개변수이름 생성된구조체데이터)


    예) 버스커 버스커의 벚꽃엔딩과 볼빨간사춘기의 프리지아의 메타데이터에서 각각 아티스트 이름과 장르 내용을 추출해봅시다.

    Code 4.6 - 2

    (Song-artist
     (make-Song "Busker Busker" 4 'Rock 2012))
    (Song-genre
     (make-Song "Bolbbalgan4"   7 'Folk 2016))

    답: "Busker Busker", 'Folk


    +.

    구조체도 데이터 타입입니다.

    새로 정의된 구조체 또한 데이터 타입이라 할 수 있습니다.


    그래서 struct?는 구조체 , Song?은 Song 구조체인지 판독 가능합니다.


    2.2.3 구조체 활용법.

    기존에 배웠던 것들과 합쳐 활용을 해봅시다.


    위 코드를 보다 보면 헷갈릴 만한 것이

    (make-Song
      "Cherry Blossom Ending" "BuskerBusker" 4 'Rock 2012)
    
     (make-Song
      "Freesia"               "Bolbbalgan4"  7 'Folk 2016)

    처럼 구조체를 만들어야 하는 것이 아니냐는 것입니다.


    그럼 이렇게 한번 생각해보자.

    빨간색을 (red, 255, 0, 0)라고 표현하지 않고, red=(255, 0, 0)처럼 표현하는 것이 더 나은 방식이 아닐까?

    구조체도 숫자, 문자, 문자열 같은 하나의 데이터 타입이기 때문이다.


    더 쉬운 예로 상수 원주율 Pi는 그냥 3.141592..의 데이터에 이름을 붙인 것이지 3.14592..란 데이터에 Pi란 단어가 들어가지 않는 것과 같다.


    이쯤 되면 눈치 빠른 분들은 무슨 의도였는지 눈치채셨을 것입니다.

    기존에 배웠던 상수와 함수와 함께 써보는 겁니다.


    위에서는 알아서 on-tick이나 on-mouse 같은 요소들을 자율적으로 사용해보라 했지만,

    안 해봤을 것만 같은 불안감이 스물스물 올라와 이번에 구조체와 함께 써보도록 하죠.


    이번에 우리가 만들어볼 것은 4가지의 상태를 전환하는 프로그램입니다.

    1초마다 상태가 전환되는데 파란색 -> 노란색 ->  초록색 원(반지름 20)으로 전환됩니다.

    키보드 스페이스를 누르면 노란색으로 표시되고, 마우스 커서를 창안으로 넣으면 빨간색으로 전환되며 종료됩니다.

    원은 200x200인 empty-scene 안에 언급된 색 순서대로 80x0 160x50 80x100 0x50위치에 존재합니다.

    위치는 2htdp/image에 미리 정의되어 있는 posn 이란 구조체를 사용하며, x와 y라는 매개변수를 가지고 있습니다.

    현재 자신의 색이 표시되면 "solid"로, 아니면 "outline" 원이 그려집니다.

    * 실제로 만들어보면 종료시 빨간색으로 전환이 안되는데 stop-when 체크를  to-draw보다 먼저 실행하기 때문입니다.

      빨간색으로 바꾸고 끄려면 코드가 복잡해지기 때문에 따로 작성하지 않겠습니다.


    이번 문제는 살짝 복잡해서 글 만으로 이해하기가 어렵기 때문에, 미리 결과를 보여주도록 하겠습니다.


    결과



    * 문제 정리.

    상태를 전환하는 프로그램은 표를 그려 정리하면 편합니다.

    상태표.

    Q(t)

    입력

    Q(t+1)

    출력

    blue

    blue

    yellow

    yellow

    yellow

    yellow

    green

    green

    green

    green

    blue

    blue

    blue

    blue

    yellow

    yellow

    yellow

    yellow

    green

    green

    ...

    ...

    ...

    ...

    ?

    ?, “ ”

    yellow

    yellow

    ...

    ...

    ...

    ...

    ?

    ?, “enter”

    red

    red


    현재 상태인 Q(t), big-bang에 입력되는 입력값, 다음 상태 예정인 Q(t+1), 최종 반환 값으로 이루어져있습니다.


    ?는 어떤 상태인지 모를 때, 즉 미지수를 뜻 합니다.




    * 내용이 거의 비슷한데 왜 Q(t), 입력, Q(t+1), 출력을 넣었는가.

    지금의 상태표는 마지막 함수를 기준으로 기술하고 있기 때문에 Q(t)와 입력이, Q(t+1)과 출력 값이 거의 비슷해보입니다.

    만약 프로그램의 중간에 있는 함수를 기준으로 작성한다면 입력값(파라미터)이 여러개일 수도 있고, 최종 출력 값도 전혀 다른 것일 수도 있습니다.


    중간 중간 함수 작성 시 막힌다면 Q(t), 입력에 값을 넣고, 다음 상태와 최종 출력값을 계산해보세요.




    비슷하게 다이어그램으로도 표현할 수 있습니다.

    상태 다이어그램.

    원 안에 있는 것은 상태값,  화살표는 전이방향, 전이시 텍스트는 '입력1, 입력2/출력'으로 이루어집니다.



    * 상수

    우선 문제에 나와있는 대로 상수를 만들어봅시다.

    Code 4.7 - 1

    ;----------constant
    ;-----background
    (define WIDTH  200)
    (define HEIGHT 200)
    (define BACKGROUND (empty-scene WIDTH HEIGHT))
    
    ;-----circle
    (define R 20)
    
    ;;ncolor
    (define BLUE   0)
    (define YELLOW 1)
    (define GREEN  2)
    (define RED    3)
    
    ;;posn
    (define START
      (make-posn 80  0  )) ;;100 50
    (define MIDDLE
      (make-posn 160 50 )) ;;150 100
    (define END
      (make-posn 80  100)) ;;100 150
    (define ERROR
      (make-posn 0   50 )) ;;50  100

    배경으로 쓸 BACKGROUND, 원의 반지름, 색, 좌표를 설정해주었습니다.

    색은 "blue", "yellow"같은 식이 아니라, 숫자로 설정하였습니다.

    [숫자로 색을 설정했으므로 기존 color 구조체와 차이를 두기위해 number color라는 뜻으로 ncolor라 표시했습니다.]

    좌표는 현재 상태의 전환에 중점을 두어 시작, 중간, 끝, 에러로 분류하였습니다.

    왜 숫자를 썼는지는 차차 알아보도록 합시다.


    * 함수

    함수들을 만들어 줄 때입니다.


    그런데 조건이 상당히 복잡하죠?

    차근차근 밑바닥부터 만드는 것이 아니라 마지막부터 거꾸로 만드는 것도 하나의 방법입니다.

    미로를 거꾸로 풀 때 더 쉬운 것처럼요.


    마지막 순서인 함수를 만들어 보겠습니다.

    계약-목적-헤더

    ;----------big-bang
    ;;4state-trans ; WorldState -> image
    ;;4 color's circle transition.
    (define (4state-trans ncolor))

    4가지 상태의 변환을 나타내는 문제이므로, 4state-trans라는 이름을 붙였습니다.

    현재 색을 입력하여 전환되도록 색을 big-bang의 world state값을 나타내도록 합니다.


    그러고 보니 마지막 순서는 big-bang을 실행하는 함수죠?

    문제에서 big-bang과 관련된 부분을 찾아서 헤더를 확장해봅시다.

    함수 템플릿

    (define (4state-trans ncolor)
      (big-bang ncolor
        [to-draw   ...]
        [on-tick   ...]
        [on-mouse  ...]
        [on-key    ...]
        [stop-when ...]))

    구체적인 값 빼고, 조건들만 채워져 있죠?

    이 상태를 함수 템플릿이라 부릅니다.(섹션1의 디자인 레시피 참고)


    필수 요소이자 화면에 이미지 표시 기능인 to-draw, 1초마다 상태 전환을 위한 on-tick, 마우스와 키보드 이벤트 인식을 위한 on-mouse, on-key, 프로그램의 종료 조건을 위한 stop-when이 조건입니다.

    4state의 조건들은 다 작성했으니 '...'으로 되어 있는 값들을 채워줘야 할 때입니다.

    Code 4.7 - 2

    ;----------big-bang
    ;;4state-trans: WorldState -> image
    ;;4 color's circle transition.
    (define (4state-trans ncolor)
      (big-bang ncolor
        [to-draw   render      ]
        [on-tick   next-color 1]
        [on-mouse  enter?      ]
        [on-key    space?      ]
        [stop-when RED?        ]))
    
    ;----------big-bang test
    (4state-trans BLUE)
    이미지를 생성해주기 때문에 render, 1초마다 전환(next-color)을 하기 위해 on-tick에 1을 붙여주었습니다
    마우스가 창에 들어왔는지와 스페이스키가 눌리는지를 확인하는 enter?과 space?

    빨간색인지 판독하는 RED? 핸들러를 만듭니다.


    draw, tick, key, stop의 조건들을 하나 씩 뜯어볼 차례입니다.

    역시 간단한 tick, key, stop부터 해봅시다.


     - tick

    시간이 지날 때(1초)마다 다음 상태로 전환이 됩니다.

    Code 4.7 - 3

    ;----------function
    ;-----handler
    ;;next-color: WorldState -> WorldState
    ;;Purpose: Transition next state.
    (define (next-color world)
      (if (equal? world RED)
      RED (remainder (+ world 1) 3)))
    
    ;----------fuction test
    ;-----tick
    (check-expect (next-color BLUE  ) YELLOW)
    (check-expect (next-color YELLOW) GREEN )
    (check-expect (next-color GREEN ) BLUE  )
    (check-expect (next-color RED   ) RED   )

    world의 값이 빨간색일 경우 전환할 색이 없으므로, RED(3)를 반환하고, 다음 상태로 전환하기 위해 remainder(나머지)를 사용하였습니다.

    3의 나머지를 사용하면 0->-1->2->0->1...가 반복 되겠죠?


    색을 숫자로 사용하면 방금 전처럼 수학적인 트릭을 사용할 수 있다는 장점이 있습니다.

    숫자는 텍스트보다 사용할 수 있는 연산이 다양하여 좋죠.


    1을 YELLOW, 3을 RED로 사용하지 않은 이유는 색을 나타내는 것이 아니라 숫자의 의미이기 때문입니다.

    설사 값이 같다고 하더라도 의미를 곡해 시키는 일이 있으면 안됩니다.


     - mouse

    마우스를 가져다 대면 빨간색으로 전환이 되게 합니다.

    그러면 stop의 조건에 맞아 떨어져서 종료가 되겠죠.

    Code 4.7 - 4

    ;----------function
    ;-----handler
    ;;enter?: WorldState integer integer MouseEvent -> WorldState
    ;;Check MouseEvent is "enter".
    (define (enter? world x y a-mouse)
      (if (mouse=? a-mouse "enter")
         RED world))
    
    ;----------fuction test
    ;-----mouse
    (check-expect (enter? BLUE 10 10 "enter") RED )
    (check-expect (enter? BLUE 10 10 "leave") BLUE)
    (check-expect (enter? BLUE 10 10 "drag" ) BLUE)


     - key

    스페이스바가 눌리면 노란색을 반환 해줍니다.

    만약 다른 키가 눌리면 현재 상태를 반환해주면 되겠죠.

    Code 4.7 - 5

    ;----------function
    ;-----handler
    ;;space?: WorldState KeyEvent -> WorldState
    ;;Check KeyEvent is "space".
    (define (space? world a-key)
      (cond
        [(key=? a-key " ")           YELLOW]
        [(= (string-length a-key) 1) world ]
        [ else                       world ]))
    
    ;----------fuction test
    ;-----key
    (check-expect (space? BLUE " " ) YELLOW)
    (check-expect (space? BLUE "a" ) BLUE  )
    (check-expect (space? BLUE "f1") BLUE  )

     - stop

    종료 조건을 확인하는 것으로 현재 world(색) 상태가 빨간색인지 확인하면 됩니다.

    Code 4.7 - 6

    ;----------function
    ;-----handler
    ;;RED? WorldSate -> boolean
    ;;Check WorldState is RED.
    (define (RED? world)
      (equal? RED world))
    
    ;----------fuction test
    ;-----stop
    (check-expect (RED? RED ) #true )
    (check-expect (RED? BLUE) #false)


     - draw

    마지막으로 화면에 원을 표시해주는 작업만 남았네요.


    남은 조건들을 정리해봅시다.

    $$draw = \begin{cases}
    원의색, & blue, yellow, green, red \\ \\
    world=원의색, & solid \\
    world \ne 원의색, & outline \\ \\
    원위치, & posn(x,y) \; of \; empty-scene
    \end{cases}$$


    조건을 토대로 on-draw의 핸들러 함수인 render를 만들어보겠습니다.

    Code 4.7 - 7

    ;----------function
    ;-----handler
    ;;render: WorldState -> image
    ;;Render scene.
    (define (render world)
      (overlay/align "left" "top"
                     (render-circle world BLUE  )
                     (render-circle world YELLOW)
                     (render-circle world GREEN )
                     (render-circle world RED   )
                      BACKGROUND))
    
    ;----------fuction test
    ;-----draw
    (check-expect (render BLUE)
                 (overlay/align "left" "top"
                                (render-circle BLUE BLUE  )
                                (render-circle BLUE YELLOW)
                                (render-circle BLUE GREEN )
                                (render-circle BLUE RED   )
                                 BACKGROUND))
    (check-expect (render YELLOW)
                 (overlay/align "left" "top"
                                (render-circle YELLOW BLUE  )
                                (render-circle YELLOW YELLOW)
                                (render-circle YELLOW GREEN )
                                (render-circle YELLOW RED   )
                                 BACKGROUND))

    최종 이미지를 그려주는 함수 입니다.

    BACKGROUND의 좌측 상단을 기준으로 하여 4개의 원들을 표시합니다.


    render 함수에서 미지의 값은 현재 상태와 원의 색에 따라 그려주는 render-circle입니다.


    render-circle은 posn에 따라 원의 위치를 결정하는 역할을 합니다.

    유의할 점은 원이 있을 좌표를 지정하여 배치할 overlay/xy 함수는 기준점이 필요하다는 것입니다.

    그래서 반지름의 크기가 0이어서 보이지 않는 원을 만들고 기준점으로 삼을 것입니다.

    Code 4.7 - 8

    ;----------constant
    ;-----circle
    (define zero-circle
      (circle 0 "solid" "white"))
    
    ;----------function
    ;-----draw
    ;;render-circle: WorldState ncolor -> image
    ;;Rendering circle with position and shape.
    (define (render-circle world ncolor)
      (overlay/xy zero-circle
                  (posn-x       (number->posn ncolor))
                  (posn-y       (number->posn ncolor))
                  (circle-shape world         ncolor)))
    
    ;----------fuction test
    ;-----draw
    (check-expect (render-circle BLUE BLUE)  (overlay/xy zero-circle
                                                         (posn-x (number->posn BLUE))
                                                         (posn-y (number->posn BLUE))
                                                         (circle-shape BLUE    BLUE)))
    (check-expect (render-circle RED  BLUE)  (overlay/xy zero-circle
                                                         (posn-x (number->posn BLUE))
                                                         (posn-y (number->posn BLUE))
                                                         (circle-shape RED     BLUE)))

    원이 그려질 x, y 좌표는 앞서 만들어놓은 START, INPUT, END, ERROR 구조체로부터 가져와야 합니다.

    그러려면 숫자로 표현되는 색(현재 world 상태)을 구조체로 변환해주는 number->posn 함수가 필요합니다.


    Code 4.7 - 9

    ;----------function
    ;-----draw
    ;;number->posn: ncolor -> posn
    ;;Transition color to posn
    (define (number->posn ncolor)
      (cond
        [(equal? ncolor BLUE  ) START ]
        [(equal? ncolor YELLOW) MIDDLE]
        [(equal? ncolor GREEN ) END   ]
        [(equal? ncolor RED   ) ERROR ]
        [ else                  ERROR ]))
    
    ;----------fuction test
    ;-----draw
    (check-expect (number->posn BLUE)   START )
    (check-expect (number->posn YELLOW) MIDDLE)
    (check-expect (number->posn GREEN)  END   )
    (check-expect (number->posn RED)    ERROR )


    number->posn이 만들어졌으니 원의 모양을 만들어주는 circle-shape 함수를 만들어볼 차례입니다.

    Code 4.7 - 10

    ;----------function
    ;-----draw
    ;;circle-shape: WorldState ncolor -> image
    ;;Render circle with mode, ncolor.
    (define (circle-shape world ncolor)
      (if (equal? world ncolor)
         (circle R "solid"   (number->ncolor ncolor))
         (circle R "outline" (number->ncolor ncolor))))
    
    ;----------fuction test
    ;-----draw
    (check-expect (circle-shape BLUE   BLUE  ) (circle R "solid"   "blue"  ))
    (check-expect (circle-shape YELLOW YELLOW) (circle R "solid"   "yellow"))
    (check-expect (circle-shape GREEN  GREEN ) (circle R "solid"   "green" ))
    (check-expect (circle-shape RED    RED   ) (circle R "solid"   "red"   ))
    (check-expect (circle-shape YELLOW BLUE  ) (circle R "outline" "blue"  ))
    (check-expect (circle-shape GREEN  BLUE  ) (circle R "outline" "blue"  ))
    (check-expect (circle-shape RED    BLUE  ) (circle R "outline" "blue"  ))

    circle-shape 함수에서는 현재 색(world)과 지정색 원의 색(color)가 같은지 확인한다.

    같다면 채워진(solid), 아니면 비워진(outline)원을 반환한다.


    render-circle때와 마찬가지로 색은 BLUE, YELLOW처럼 숫자로 이루어져 있기 때문에 숫자를 색으로 바꾸어주는 number->color가 필요하다.

    Code 4.7 - 11

    ;----------function
    ;-----draw
    ;;number->color: ncolor -> color
    ;;Transition ncolor to color
    (define (number->color ncolor)
      (cond
        [(equal? ncolor BLUE  ) "blue"  ]
        [(equal? ncolor YELLOW) "yellow"]
        [(equal? ncolor GREEN ) "green" ]
        [(equal? ncolor RED   ) "red"   ]
        [ else                  "red"   ]))
    
    ;----------fuction test
    ;-----draw
    (check-expect (number->color BLUE  ) "blue"  )
    (check-expect (number->color YELLOW) "yellow")
    (check-expect (number->color GREEN ) "green" )
    (check-expect (number->color RED   ) "red"   )

    이제 모두 완성 되었습니다.


    한번 실행을 시켜볼까요?

    Code 4.7 - 12

    ;----------big-bang test
    (4state-trans BLUE)


    잘 작동이 되죠?


    구조체 활용법 파트에서는 조건이 복잡해지면 거꾸로(일반화가 되어 있는 부분부터) 문제를 풀어보거나 함수 템플릿을 이용하여 함수 디자인하는 방법에 대하여 배웠습니다.


    코드를 전체적으로 보면서 정리를 해봅시다.

    Code 4.7 - 전체

    ;-------------------------Code 4.7-------------------------
    ;----------constant
    ;-----background
    (define WIDTH  200)
    (define HEIGHT 200)
    (define BACKGROUND (empty-scene WIDTH HEIGHT))
    
    ;-----circle
    (define R 20)
    (define zero-circle
      (circle 0 "solid" "white"))
    
    ;;ncolor
    (define BLUE   0)
    (define YELLOW 1)
    (define GREEN  2)
    (define RED    3)
    
    ;;posn
    (define START
      (make-posn 80  0  )) ;;100 50
    (define MIDDLE
      (make-posn 160 50 )) ;;150 100
    (define END
      (make-posn 80  100)) ;;100 150
    (define ERROR
      (make-posn 0   50 )) ;;50  100
    
    ;----------function
    ;-----draw
    ;;number->posn: ncolor -> posn
    ;;Transition ncolor to posn
    (define (number->posn ncolor)
      (cond
        [(equal? ncolor BLUE  ) START ]
        [(equal? ncolor YELLOW) MIDDLE]
        [(equal? ncolor GREEN ) END   ]
        [(equal? ncolor RED   ) ERROR ]
        [ else                  ERROR ]))
    
    ;;number->color: ncolor -> color
    ;;Transition ncolor to color
    (define (number->color ncolor)
      (cond
        [(equal? ncolor BLUE  ) "blue"  ]
        [(equal? ncolor YELLOW) "yellow"]
        [(equal? ncolor GREEN ) "green" ]
        [(equal? ncolor RED   ) "red"   ]
        [ else                  "red"   ]))
    
    ;;circle-shape: WorldState ncolor -> image
    ;;Render circle with mode, ncolor.
    (define (circle-shape world ncolor)
      (if (equal? world ncolor)
         (circle R "solid"   (number->ncolor ncolor))
         (circle R "outline" (number->ncolor ncolor))))
    
    ;;render-circle: WorldState ncolor -> image
    ;;Rendering circle with position and shape.
    (define (render-circle world ncolor)
      (overlay/xy zero-circle
                  (posn-x       (number->posn ncolor))
                  (posn-y       (number->posn ncolor))
                  (circle-shape world         ncolor)))
    
    ;-----handler
    ;;render: WorldState -> image
    ;;Render scene.
    (define (render world)
      (overlay/align "left" "top"
                     (render-circle world BLUE  )
                     (render-circle world YELLOW)
                     (render-circle world GREEN )
                     (render-circle world RED   )
                      BACKGROUND))
    
    ;;next-color: WorldState -> WorldState
    ;;Transition next state.
    (define (next-color world)
      (if (equal? world RED)
      RED (remainder (+ world 1) 3)))
    
    ;;enter?: WorldState integer integer MolibraryEvent -> WorldState
    ;;Check MouseEvent is "enter".
    (define (enter? world x y a-mouse)
      (if (mouse=? a-mouse "enter")
         RED world))
    
    ;;space?: WorldState KeyEvent -> WorldState
    ;;Check KeyEvent is "space".
    (define (space? world a-key)
      (cond
        [(key=? a-key " ")           YELLOW]
        [(= (string-length a-key) 1) world ]
        [ else                       world ]))
    
    ;;RED? WorldSate -> boolean
    ;;Check WorldState is RED.
    (define (RED? world)
      (equal? RED world))
    
    ;----------big-bang
    ;;4state-trans: WorldState -> image
    ;;4 color's circle transition.
    (define (4state-trans ncolor)
      (big-bang ncolor
        [to-draw   render      ]
        [on-tick   next-color 1]
        [on-mouse  enter?      ]
        [on-key    space?      ]
        [stop-when RED?        ]))
    
    ;-------------------------Test 4.7-------------------------
    ;----------fuction test
    ;-----draw
    (check-expect (number->posn BLUE)   START )
    (check-expect (number->posn YELLOW) MIDDLE)
    (check-expect (number->posn GREEN)  END   )
    (check-expect (number->posn RED)    ERROR )
    
    (check-expect (number->color BLUE  ) "blue"  )
    (check-expect (number->color YELLOW) "yellow")
    (check-expect (number->color GREEN ) "green" )
    (check-expect (number->color RED   ) "red"   )
    
    (check-expect (circle-shape BLUE   BLUE  ) (circle R "solid"   "blue"  ))
    (check-expect (circle-shape YELLOW YELLOW) (circle R "solid"   "yellow"))
    (check-expect (circle-shape GREEN  GREEN ) (circle R "solid"   "green" ))
    (check-expect (circle-shape RED    RED   ) (circle R "solid"   "red"   ))
    (check-expect (circle-shape YELLOW BLUE  ) (circle R "outline" "blue"  ))
    (check-expect (circle-shape GREEN  BLUE  ) (circle R "outline" "blue"  ))
    (check-expect (circle-shape RED    BLUE  ) (circle R "outline" "blue"  ))
    
    (check-expect (render-circle BLUE BLUE)  (overlay/xy zero-circle
                                                         (posn-x (number->posn BLUE))
                                                         (posn-y (number->posn BLUE))
                                                         (circle-shape BLUE    BLUE)))
    (check-expect (render-circle RED  BLUE)  (overlay/xy zero-circle
                                                         (posn-x (number->posn BLUE))
                                                         (posn-y (number->posn BLUE))
                                                         (circle-shape RED     BLUE)))
    
    (check-expect (render BLUE)
                 (overlay/align "left" "top"
                                (render-circle BLUE BLUE  )
                                (render-circle BLUE YELLOW)
                                (render-circle BLUE GREEN )
                                (render-circle BLUE RED   )
                                 BACKGROUND))
    (check-expect (render YELLOW)
                 (overlay/align "left" "top"
                                (render-circle YELLOW BLUE  )
                                (render-circle YELLOW YELLOW)
                                (render-circle YELLOW GREEN )
                                (render-circle YELLOW RED   )
                                 BACKGROUND))
    
    ;-----tick
    (check-expect (next-color BLUE  ) YELLOW)
    (check-expect (next-color YELLOW) GREEN )
    (check-expect (next-color GREEN ) BLUE  )
    (check-expect (next-color RED   ) RED   )
    
    ;-----mouse
    (check-expect (enter? BLUE 10 10 "enter") RED )
    (check-expect (enter? BLUE 10 10 "leave") BLUE)
    (check-expect (enter? BLUE 10 10 "drag" ) BLUE)
    
    ;-----key
    (check-expect (space? BLUE " " ) YELLOW)
    (check-expect (space? BLUE "a" ) BLUE  )
    (check-expect (space? BLUE "f1") BLUE  )
    
    ;-----stop
    (check-expect (RED? RED ) #true )
    (check-expect (RED? BLUE) #false)
    
    ;----------big-bang test
    (4state-trans BLUE)

    저번 강좌에서 코드 정리를 하라는 대로, 기능마다 구분하였습니다.


    특히, draw쪽 코드를 살펴보면 몇가지 배울점을 발견할 수 있습니다.


    1. number->posn과 number->color

    둘 다 ncolor를 color로 변환하는 함수며, 비슷한 형태로 구현하였습니다.

    대등한 아이디어는 비슷하게 구현하는 것이 깔끔하며 일관성이 생깁니다.


    2. 모으기[행 단위]

    draw의 최종 목적인 render-circle을 보면 number->color, circle-shape 순으로 등장합니다.

    그래서 render-circle의 바로위에 number->color, circle-shape 순으로 배열하였습니다.

    circle-shape에서 새로 등장하는 number->posn은 render-circle과 거리가 언급한 2함수에 비하면 멀기 때문에 더 위에 배치하였습니다.

    이렇게 코드를 배치하다 보니 위에 있는 posn, ncolor, circle과 대칭이 되는 구조가 우연히 만들어져서 한층 읽기 편해졌습니다.


    비슷한 의미를 가지는 코드를 모으라는 조언에는 마치 글처럼 맥락도 고려하여 배치하라는 의미도 숨어있었습니다.


    3. 유한 상태 기계(finite-state machine, FSM)

    마지막으로 유한 상태 기계라는 것을 배워봅시다.


    방금 전의 프로그램처럼 유한한 상태를 가질수 있는 모델을 유한 상태 기계라고 부릅니다.

    이러한 기계는 한 번에 오로지 하나의 상태만을 가지게 되며, 현재 상태(Current State)란 임의의 주어진 시간의 상태를 칭한다.[1초였죠?]


    유한 상태 기계는 크게 무어 모델(Moore model)과 밀리 모델(Mealy model)로 나뉠 수 있습니다.

    • 무어 모델은 현재 상태들로만 결정이 된다.
    • 밀리 모델은 현재 상태 + 입력으로 결정이 된다.

    우리가 만든 프로그램은 어떤 모델일까요?


    만약 1초마다 B->Y->G->B->...같이 현재 상태로만 결정이 되었다면 무어 모델지만, 키보드 이벤트 " "나 마우스 이벤트 "enter"과 같은 입력에도 영향을 받았으니 밀리 모델입니다.

    유한 상태 모델은 Chapter2 부록에서 정규 표현식과 함께 조금 더 다루어보도록 하겠습니다.

    +.

    posn은 position의 약어(abbreviation)이다.

    그냥 쓰기에는 긴 단어일 때 약어(abbreviation)나 두문자어(acronym)로 작명을 하면 된다.


    약어는 철자를 생략하거나, 머릿 글자를 따서 만든다.

    방금 전의 position -> posn. 이나 corporated -> Corp. building -> bldg. 같은 예가 있다.


    두문자어는 약어 중 머릿 글자를 따서 만드는 것이다.

    Fédération Internationale de Football Association -> FIFA, Acquired Immune Deficiency Syndrome -> AIDS 같은 예가 있다.

    FIFA는 프랑스어라고 한다.


    간단하게 줄일 수 있어서 좋지만,

    많은 사람들에게 통용되지 않는 약어를 코드에 작성하게 되면 나중에 보는 사람이 헷갈릴 수 있으므로 유의하는 것이 좋다.

    언어적 특성.


    다시 쓰지만 코드 또한 글이고, 언어입니다.

    +++.

    ;----------library 후에 라이브러리.

    ;----------constant 후에 상수.

    ;----------function 후에 함수.

    ;----------function test 후에 함수 테스트 등으로

    코드 내 영역을 나누어 놓은 아이디어는 파스칼이라는 프로그래밍 언어에서 따온 것입니다.


    program 프로그램이름;     // 이 소스코드가 프로그램이라는것을 의미한다.
    label                     // Goto문의 지표가 된다.
      지표1, 지표2;
    
    uses                      // 외부유닛의 참조목록이다.
      유닛1, 유닛2;
    
    type                      // 자료형 선언부이다.
      형명칭: 자료형;
    
    var                       // 변수 선언부이다.
      변수명: 자료형;
    
    const                     // 상수 선언부이다.
      상수명=상수값;
    
      procedure 프로시저이름; // 프로시저를 만들었다.
      begin                   // 시작
        명령;
      end;                    // 끝 - 프로시저
    
    begin                     // 시작
      프로시저이름;
      명령;                   // end의 앞에는 원칙적으로는 ;이 오면 안된다. 이 문제는 나중에 다룬다.
    end.                      // 끝 - 프로그램

    파스칼 소스코드 구조 [from wikibooks]

    파스칼이란 프로그래밍 언어는 구조적이라 교육용으로도 쓰이곤 합니다.

    대신 유연성이 떨어진다는 단점이 존재합니다.(C언어와 비교.)

    3. 섹션4 후기.

    챕터 1(기초)의 내용이 끝났습니다.


    점점 난이도가 오르고 있는 것이 느껴지시죠?


    혹시 어려워서 못 따라오겠다면 다시 섹션 2, 3의 내용을 참고해가면서


    섹션 4까지의 내용을 이해할 수 있는 독자라면,

    2017/06/30 - [프로그래밍/설계] - 프로그래밍과 추상화에 대하여.

    를 이해할 수 있을 것입니다.


    프로그래밍을 '추상화'라는 관점으로 쭉 해석해놓은 것입니다.


    갑자기 클래스(class) 라는 뜬금없는 개념이 등장하지만,

    구조체에서는 상수들을 담았던 것이라면 클래스는 상수와 함수 모두 담을 수 있고,

    확장 시켜나갈 수 있는 것이라 생각하면 됩니다.



    마치 집합처럼 말이죠.(엄밀히 말하자면 좀 다르기는 하지만, 지금 클래스를 다루지 않으니 넘어가도록 하겠습니다.)


    또 후반에 C++의 코드가 있는 것이 조금 문제이기는 하지만, 그냥 제 의도만 파악을 하면 됩니다.

    프로그래밍은 추상화하는 과정의 연속.


    참조.  image&universe(racket-lang), 유한 상태 기계(Wikipedia)





    댓글 0

Designed by black7375.