2008. 12. 30.

SDL 다중 Surface 예제

뭉기적 뭉기적 거리다가 생각난김에 코딩해서 작성해봄..

SDL 에서는 CREATE-SURFACE 로 surface를 생성한 다음 BLIT-SURFACE 로 *default-surface* 에 복사하면 출력이 가능하다.

먼저 surface 를 저장할 리스트를 선언한다.
(defparameter *test-surface* nil)

다음에 지정한 색상으로  리스트 안의 모든 surface 를 칠할 함수

<br />(defun fill_surfaces (list_sf color)<br />  (cond ((null list_sf) nil)<br />		(t <br />		 (sdl:fill-surface color :surface (car list_sf))<br />		 (fill_surfaces (cdr list_sf) color))))<br />



이제 리스트 안의 모든 surface 를 Bit-Blit 으로 디폴트 surface 에 복사하는 함수


<br />(defun blit_surfaces (list_sf)<br />  (cond ((null list_sf) nil)<br />		(t<br />		 (sdl:blit-surface (car list_sf))<br />		 (blit_surfaces (cdr list_sf)))))<br />



완성된 테스트 코드..


<br />(defun surface-test ()<br />  (sdl:with-init ()<br />	(sdl:window 320 240 :title-caption "SDL-TTF Font Example")<br />	(push (sdl:create-surface 50 60) *test-surface*)<br />	(push (sdl:create-surface 50 60 :x 100 :y 120) *test-surface*)<br />	(fill_surfaces *test-surface* sdl:*white*)<br />    (blit_surfaces *test-surface*)<br />	(sdl:with-events ()<br />		(:quit-event () <br />		    (setf *test-surface* nil)<br />		    t)<br />		(:video-expose-event () (sdl:update-display))<br />		(:idle () (sdl:update-display)))))<br />


다음번에는 Timer관련해서 애니메이션을 시켜보도록 하겠다..

2008. 12. 10.

CLISP 에서 한글 출력(UTF-8, euc-kr)

현재 리눅스(데비안 Lenny)에서 Emacs로 LISP 스크립트를 구성하고 있다.
그러다보니 한글 출력에 몇가지 애로 사항이 생긴다. FORMAT 함수등에서 일어나는 문제이다.

(format t "한글을 씁니다")


라고 입력하는 경우 이를 명령어 행에서 실행시켜보면 다음과 같은 에러가 나온다.

siabard@devel02:~/project/lisp$ clisp hantest.lisp
*** - invalid byte sequence #xC7 #xD1 in CHARSET:UTF-8 conversion

즉 UTF-8에서 가능한 캐릭터 코딩이 아니라는 말이다.

이제 Emacs에서 캐릭터 셋을 utf-8로 바꾼다. (C-x C-m f 를 순서대로 누르고 utf-8을 입력)

siabard@devel02:~/project/lisp$ clisp hantest.lisp
한글을 씁니다

정상적으로 출력되는 것을 볼 수 있다. 그렇다면 euc-kr 로 출력을 원한다면 어떻게 해야할까?

이 경우는 Emacs에서 먼저 캐릭터 셋을 korean-iso-8bit-unix 로 저장을 한다. 그리고 clisp 실행시 -E 옵션을 주어 출력을 한다.

siabard@devel02:~/project/lisp$ clisp -E euc-kr hantest.lisp
�ѱ��� ���ϴ

글자가 깨진 이유는 현재 필자가 사용하는 터미널의 문자셋이 en_US.UTF-8이기 때문이다. 유니코드면 다 되기때문에 굳이 ko_KR.EUC_KR 같은 다른 셋은 사용하지 않는다.

2008. 10. 22.

Eclipse 없이 android 플랫폼 프로젝트 컴파일/실행..

android 플랫폼 개발을 위해서 추천하는 개발툴이 Eclipse이다. 하지만 허접한 노트북(펜 1.6 셀, 램 1기가)에 저 거대한 IDE는 확실히 무리였다. 간단한 HelloAndroid를 실행시키는데 들어간 엄청난 시간을 생각하면 가히 압박..

이런 이유로 Eclipse없이 android 용 프로그램을 돌려보았다.


필요한 파일들

필요한 것은 컴파일을 일단은 해야하니 JDK 와 Android SDK, 그리고 컴파일에 쓰일 ANT 등이다.

JDK : http://java.sun.com/javase/downloads/index.jsp
Android SDK : http://code.google.com/android/download.html
Apache ANT : http://ant.apache.org/bindownload.cgi

이제 파일을 모두 받고 적당한 위치에 알아서 풀어놓는다.

설정..

JAVA_HOME, ANT_HOME 은 필수적으로 설정해주어야한다. 하지만 CLASSPATH는 가급적 적지 말것을 권고하고 있다.

set JAVA_HOME=d:\util\jdk
set ANT_HOME=d:\util\apache-ant

(윈도 플랫폼의 경우..)
그리고 PATH에 %ANT_HOME%\bin 와 android SDK의 tools 디렉토리를 추가해주면 설정 끝..

프로젝트 만들기


Eclipse를 쓴다면 자동으로 만들어주는 프로젝트를 수동으로 만들어주어야한다.
android SDK의 tools/activitycreator.bat 를 실행시켜준다. (리눅스에서는 activitycreator.py로 들어있을 수 있다. )

옵션이 몇가지 있지만 여기서는 간단히 --out 옵만을 사용한다.
activityCreator.bat --out HelloAndroid com.may2nine.hello.HelloAndroid

이렇게 하면 패키지 com.may2nine.hello 에 HelloAndroid 라는 Activity를 갖는 프로젝트를 현재 디렉토리아래 HelloAndroid 라는 디렉토리에 생성한다.
파일들이 무지무지 많이 생기지만 resource 파일과 기본 Activity인 com.may2nine.hello.HelloAndroid 클래스를 구현하는 com\may2nine\hello\HelloAndroid.java 가 가장 중요하다.

<font style="font-weight: bold;" size="4">컴파일</font>

프로젝트 디렉토리에 들어가 ans 를 실행시킨다.

실행과 프로그램 등록/삭제

Android 에뮬레이터를 실행시킨다. android SDK의 tools/emulator.exe 이다. 앞에서도 말해두었지만 이 디렉토리를 PATH에 잡아두는 것이 여러모로 편리할 것이다.
이제 Android 에뮬레이터가 떴다면 잠시 기다린다. 에뮬레이터에서 홈버튼(집모양 버튼)을 누르면 메인창으로 온다. 화면 아래에 있는 폴더버튼을 누르면 등록된 어플리케이션 목록이 나타난다.

어플리케이션 목록은 adb를 통해 실행되고 있는 에뮬레이터(만약 기기가 있다면 USB로 통신할 수도 있다)에 접근해서 얻을 수 있다.

이때 설치되는 프로그램은 .pak 확장자의 파일들이다. 해당하는 파일은 프로젝트 디렉토리/bin 에 들어있다.

일단 adb로 프로그램을 설치한다.

adb install bin\HelloAndroid-debug.pak


이 명령은 프로젝트 디렉토리에서 실행해주었다. install 뒤에 나오는 pak 파일의 경로를 정확하게 써주도록 한다.
이제 에뮬레이터에서 프로그램 목록을 살펴보면 HelloAndroid 라는 아이콘을 볼 수 있다. 이 프로젝트 이름은 build.xml에 기재되어 있는 이름이므로, 바꾸고 싶다면 바꿔도 된다.

프로그램을 설치한 후에 해당 프로그램이 업데이트되거나 지워야한다면 adb의 shell 모드로 들어가면 된다. adb shell 모드는 Linux등에서 볼 수 있는 쉘과 거의 동일하다. (cd , ls, rm 등이 모두 먹는다.)

adb shell 로 들어가서 data/app 디렉토리로 들어간다.
아까 설치한 pak파일을 볼 수 있다. 해당 파일을 지우는 명령은 rm 이다.
이 명령을 실행하면 에뮬레이터에서 해당하는 파일이 지워진다.

이렇게 해서 Eclipse없이 프로젝트를 구성할 수 있지만, ADT에 포함된 많은 도구 - 특히 XML을 이용해 UI를 구성하는 - 없이 Android 플랫폼 프로그램을 구성할 수 있을지는 자신이 없다.
다만, 현재 개발중인 컴퓨터가 나처럼 구린 경우에는 그나마 큰 도움이 되지 않을까 싶다. ㅠ.ㅠ

컴터 업그레이하고 싶다...

HTDP... 그리고 근황..

SICP가 무지막지한 난이도로 악명이 높아 대안적으로 나온.. 조금 쉬울거라던 HTDP..

이것도 만만치않네요..

HTDP보고 SICP 하면 좋을거라는 얘기는 뭐랍니까.. 좌절 100배중..

어쨌거나 볼만합니다. Scheme 좀 공부한 덕인지 초반 진도는 쾌적하고 무엇보다도 떡하니 실려있는 해답에 마음이 포근해지는군요..

요즘 SICP 근황은...

SICP 4장 진행하다가 다시 3장으로 백~ 했습니다. 아마 3장하다가 2장 중반으로 빽할 수도 있을듯합니다. 그동안 공부하던 것을 다시 살펴보니 많이 모자른 부분도 있고.. 그때 그때 보충도 가능하군요.. 복습하면서 배우는 것이 더 많습니다.

한동안 Practical Common LISP에 빠져 살았습니다. 최종으로는 lisp으로 이런 저런 테스트 해보면서 간단한 웹 개발을 해보려고 합니다. 하지만 lisp에서 문자열 지원은 기대할 것이 거의 없기 때문에(문자열 = 글자리스트.. -ㅇ- 누가 LISP아니랄까바..) 만드는 과정이 꽤 막막합니다.
쓸만한 webframe 쓰면 한큐에 해결되지만, 원래 From the Scratch 식으로 일을 해야 배우는 게 많은거 아니겠습니까? ^^ (Lisp/Scheme 으로 만드는 게임은 꾸준히 기획 준비중입니다. 내년에는 삽을 뜰 수 있을까요? 허허..)

현업얘기로..

기존에 사용하던 웹플랫폼이 ASP였습니다. ASP.net도 아닌.. ASP.. -ㅇ-
이걸 Python이나 Ruby로 바꿀 생각인데(플랫폼 호환을 고려해서 DB도 PostgreSQL등으로 고려중..) 주변에서 누가 Erlang 어떻겠냐고 해서 조금 고민중입니다.

그 렇게 엄청날 정도로 일을 하는 것은 아니지만 함수형 언어로 프로젝트 진행하는 것도 괜찮겠다 싶고, 그전에 사둔 Erlang책도 있어서 생각중에 있습니다. JSP는 서버 옮긴다음에 천천히 시작해야겠습니다. 지금은 웹호스팅이라... 윈도 기반에서 JSP하려니 갑갑하네요..

JAVA로 POI, XML, jTDS를 이용해서 엑셀 데이터를 MS-SQL에 꾸준히 집어넣는 프로젝트를 얼마전에 마쳤습니다. 자동으로 돌아가는 서비스를 만들었으면 좋았겠지만 엑셀 데이터는 무조건 수동으로밖에 못얻도록 해놓은 야박한 솔루션 개발사덕에, 백만년만에 JAVA 프로그램을 제작했습니다. 시간은 오래 걸렸지만... 한거보니 한숨..

2008. 9. 23.

Programming Bottom-Up

원문 : http://www.paulgraham.com/progbot.html

Programming Bottom-Up

1993

원문 : http://www.paulgraham.com/progbot.html

It's a long-standing principle of programming style that the functional elements of a program should not be too large. If some component of a program grows beyond the stage where it's readily comprehensible, it becomes a mass of complexity which conceals errors as easily as a big city conceals fugitives. Such software will be hard to read, hard to test, and hard to debug.

프로그램에서 기능 요소가 지나치게 커지지 않도록 하는 방식은 오랜동안 프로그램의 원칙이었다. 프로그램의 특정 요소가 쉽게 읽혀지는 범위를 넘어 커진다면, 마치 대도시에 도망자가 쉽게 숨는 것처럼 오류를 포함하게되는 복잡한 덩어리가 되고 만다. 이런 소프트웨어는 읽기 어렵고, 테스트가 곤란하며, 디버깅이 힘들어진다.

In accordance with this principle, a large program must be divided into pieces, and the larger the program, the more it must be divided. How do you divide a program? The traditional approach is called top-down design: you say "the purpose of the program is to do these seven things, so I divide it into seven major subroutines. The first subroutine has to do these four things, so it in turn will have four of its own subroutines," and so on. This process continues until the whole program has the right level of granularity-- each part large enough to do something substantial, but small enough to be understood as a single unit.

이 원칙을 적용하면, 거대한 프로그램을 작은 조각으로 나누어져야하며, 프로그램이 커질 수록, 더 많이 나누어야한다. 어떻게 프로그램을 나누는가? 전통적인 접근은 탑다운 디자인이라고 한다. "프로그램의 목적은 이들 7가지 일을 하는 것으로, 나는 이를 7가지 주요 부분으로 나누었다. 첫번째 부분은 이들 4가지 일을 함으로, 다시 이를 4개의 작은 부분으로 나눈다"라는 식이다. 이런 절차는 전체 프로그램이 적당한 수준의 조각이 될때까지 행하며, 각각의 부분이 주요한 일을 할정도의 크기이기는 하지만, 단일한 유닛으로 이해가능할 정도까지 한다.

Experienced Lisp programmers divide up their programs differently. As well as top-down design, they follow a principle which could be called bottom-up design-- changing the language to suit the problem. In Lisp, you don't just write your program down toward the language, you also build the language up toward your program. As you're writing a program you may think "I wish Lisp had such-and-such an operator." So you go and write it. Afterward you realize that using the new operator would simplify the design of another part of the program, and so on. Language and program evolve together. Like the border between two warring states, the boundary between language and program is drawn and redrawn, until eventually it comes to rest along the mountains and rivers, the natural frontiers of your problem. In the end your program will look as if the language had been designed for it. And when language and program fit one another well, you end up with code which is clear, small, and efficient.

경 험많은 리스프 프로그래머는 자신의 프로그램을 다른 식으로 분해한다. 탑다운 디자인과 함께, 버텀업(bottom-up) 디자인이라는 원칙을 따른다. 프로그램을 문제에 맞게끔 변형하는 것이다. 리스프에서, 언어를 통해서 프로그램을 짜는 것 뿐 아니라, 언어자체를 프로그램에 맞추어 만들어나간다. 프로그램을 만들어나가면서, "리스프로 어런 이런 연산자가 필요해"라고 생각할 수 있다. 그러면, 이것을 작성해 나간다. 이후 새로운 연산자를 사용하는 것이, 프로그램의 다른 부분을 간결하게 하는 것임을 알게 된다. 마치 두 전선의 경계처럼, 언어와 프로그램의 경계는 서로 오고 가면서, 이따금 강과 산, 또다른 자연적인 경계에 막혀 쉬기도 한다. 종국에는 프로그램은 해당 문제를 위해 디자인된 언어처럼 보인다. 언어와 프로그램이 서로 잘 맞아떨어지므로, 분명하고, 작고, 효율적인 코드의 작성으로 작업은 끝나게 된다.

It's worth emphasizing that bottom-up design doesn't mean just writing the same program in a different order. When you work bottom-up, you usually end up with a different program. Instead of a single, monolithic program, you will get a larger language with more abstract operators, and a smaller program written in it. Instead of a lintel, you'll get an arch.

바 텀업 디자인은 같은 문제를 다른 방식으로 푸는 것에 국한되지 않는 다는 점에서 효과적이다. 바텀업을 하면서, 다른 프로그램으로 끝맺게 된다. 단일하고, 통합된 프로그램 대신에, 더 추상적인 연산자와, 그것으로 작성된 작은 프로그램을 가지는 좀 더 큰 언어를 가지게 된다. 교각대신에 아치를 가지게 된다.

In typical code, once you abstract out the parts which are merely bookkeeping, what's left is much shorter; the higher you build up the language, the less distance you will have to travel from the top down to it. This brings several advantages:

특정한 코드에서는, 도서관리와 같은 것을 추상화한다면, 남은 것은 더 작아진다. 언어를 좀 더 고차원적으로 구성해갈 수록, 위와 아래를 오고 가는 일은 더 적어진다. 이는 상당한 이점을 가진다.

   1. By making the language do more of the work, bottom-up design yields programs which are smaller and more agile. A shorter program doesn't have to be divided into so many components, and fewer components means programs which are easier to read or modify. Fewer components also means fewer connections between components, and thus less chance for errors there. As industrial designers strive to reduce the number of moving parts in a machine, experienced Lisp programmers use bottom-up design to reduce the size and complexity of their programs.

   1. 언어가 더 많은 일을 할 수 있도록 함으로써, 바텀업 디자인은 프로그램이 좀 더 작고, 민첩해질 수 있도록 한다. 더 짧은 프로그램은 많은 부분으로는 나뉘어지지않기 때문에, 더 적은 부분은 쉽게 읽히거나 수정하기 쉬워짐을 의미한다. 더 적은 부분은 또한, 부품간에 더 적은 연결을 의미함으로, 에러의 발생 가능성도 줄여준다. 산업 디자이너들이 기계에서 움직이는 부품의 수를 줄이는 것처럼, 숙련된 리스프 프로그래머는 바텀업 디자인을 사용해서, 프로그램의 크기와 복잡성을 줄인다.

   2. Bottom-up design promotes code re-use. When you write two or more programs, many of the utilities you wrote for the first program will also be useful in the succeeding ones. Once you've acquired a large substrate of utilities, writing a new program can take only a fraction of the effort it would require if you had to start with raw Lisp.

   2. 바텀업 디자인은 코드 재사용성을 늘린다. 두개 혹인 그 이상의 프로그램을 작성한다면, 처음 프로그램에서 작성한 많은 유용한 유틸리티가 이어지는 프로그램에서도 유용하게 상요될 수 있다. 많인 수의 도구를 얻게된다면, 새로운 프로그램을 작성할 때, 아주 작은 노력만 필요하다.

   3. Bottom-up design makes programs easier to read. An instance of this type of abstraction asks the reader to understand a general-purpose operator; an instance of functional abstraction asks the reader to understand a special-purpose subroutine. [1]
   
   3. 바텀업 디자인을 통해서 프로그램은 더 읽기 편해진다. 추상화된 형의 인스탄스를 읽는 사람은 일반적인 연산자로 이해한다. 추상화된 함수의 인스탄스는 전용 목적의 서브루틴으로 이해한다.

   4. Because it causes you always to be on the lookout for patterns in your code, working bottom-up helps to clarify your ideas about the design of your program. If two distant components of a program are similar in form, you'll be led to notice the similarity and perhaps to redesign the program in a simpler way.

   4. 코드의 패턴을 늘 지켜보도록 하기때문에, 바텀업으로 일하는 것은 프로그램을 디자인하는 생각을 더 명확하게 한다. 프로그램중 두개의 요소가 유사한 형태라면, 이 유사성을 발견하고, 프로그램을 더 간단한 방식으로 재디자인하도록 할 수 있다.

Bottom-up design is possible to a certain degree in languages other than Lisp. Whenever you see library functions, bottom-up design is happening. However, Lisp gives you much broader powers in this department, and augmenting the language plays a proportionately larger role in Lisp style-- so much so that Lisp is not just a different language, but a whole different way of programming.

바텀업 디자인은 리스프 이외에도 많은 언어에서 어느 정도 가능하다. 라이브러리 함수등에서도 바텀업 디자인은 발견할 수 있다. 그렇지만 리스프는 이른 분야에서는 더 많은 강력함을 보이며, 리스프 스타일로 더 다양한 역할을 가능케 한다. 리스프는 다른 언어일 분 아니라, 프로그램을 함는 완전히 다른 방식이다.

It's true that this style of development is better suited to programs which can be written by small groups. However, at the same time, it extends the limits of what can be done by a small group. In The Mythical Man-Month, Frederick Brooks proposed that the productivity of a group of programmers does not grow linearly with its size. As the size of the group increases, the productivity of individual programmers goes down. The experience of Lisp programming suggests a more cheerful way to phrase this law: as the size of the group decreases, the productivity of individual programmers goes up. A small group wins, relatively speaking, simply because it's smaller. When a small group also takes advantage of the techniques that Lisp makes possible, it can win outright.

이런 스타일의 개발은 작은 그룹에서 행해질 때 더 효과적임은 분명하다. 그러나, 동시에, 작은 그룹이 할 수 있는 한계를 확장시킨다. 고전적인 맨-먼스에 따른 프레데릭의 책에서는 한 그룹의 프로그래머의 효율성은 크기에 비례하지 않는다. 그룹의 크기가 증가할 수록, 개개인의 효율성은 낮아진다. 리스프 프로그램의 경험상 이 법칙을 좀 더 즐겁게 바꿀 수 있다. 그룹의 크기가 감소함에 따라, 개개인의 효율성은 급증한다. 작은 그룹이 승리하는 이유는 더 작기 때문이다. 작은 그룹이 리스프가 가능한 기법의 이득을 받는다면, 승리는 더욱 자명해진다.

2008. 9. 16.

PCL Ch3. save-db load-db 변경하기

환경은 CLISP + emacs + slime

CLISP은 유니코드를 지원한다. 유니코드를 지원하게 하기위해서는

<blockquote>(setq slime-net-coding-system 'utf-8-unix)</blockquote>

라인을 .emacs 에 넣어두어야한다.

우선 여기까지 되었으면 with-open-file 에서 각 stream에 encoding을 정의해주어야한다.
몇가지 예제 파일을 찾다가 Common Lisp Cookbook 에서 힌트를 찾았다.
:external-format을 설정해주면 모든 문제가 일단 OK
아래는 변경한 save-db, load-db이다.


(defun save-db (filename)
  (with-open-file (out filename
                       :external-format (ext:make-encoding :charset 'charset:utf-8 :line-terminator :unix)
                      
                       :direction :output
                       :if-exists :supersede)
    (with-standard-io-syntax (print *db* out))))

(defun load-db (filename)
  (with-open-file (in filename
                      :external-format (ext:make-encoding :charset 'charset:utf-8 :line-terminator :unix))
    (with-standard-io-syntax
      (setf *db* (read in)))))

이상으로 문제는 완료..
이제 유니코드를 사용해도 정상적으로 출력이 된다. 핵심은 역시 (ext: ... ) 부분..

2008. 9. 9.

PCL ch4.함수의 정의

* 함수 정의하는 형태
<blockquote>(defun name (parameters)
"Optional document string"
bodyform)
</blockquote>


* 파라미터 리스트 : 함수에 전달되는 인자를 받을 변수를 정의한다.
- 필수 파라미터 : 변수 이름의 리스트
- 옵션 파라미터
<blockquote>(defun foo (a b &optional c) ... )<br /></blockquote>

  • a, b 두 인자는 필수 파라미터이다. 따라서 먼저 두 변수에 값이 할당된다.
  • 이후에도 남은 값이 있다면 그때 c에 할당된다.
- 디폴트 값 넘기기
옵션 파라미터에 디폴트 값을 넣고 싶다면 다음과 같이 사용한다.


<blockquote>(defun foo (a b &optional (c 10)) ... )</blockquote>

만약 주어진 옵션 파라미터에 값이 등록되었는지를 알고 싶다면 "-supplied-p" 변수를 이용할 수 있다. 해당하는 변수가 등록되었다면 T, 그렇지 않다면 NIL 이다.

<blockquote>(defun foo (a b &optional (c 0 c-supplied-p)) <br />     (list a b c c-supplied-p))</blockquote>
- Rest 파라미터 : 여러 개의 인수를 받고자할 때 사용한다.
<blockquote>(defun foo (a b &rest c) ... )</blockquote>

- Keyword 파라미터 : 어떤 인수가 어떤 변수로 매핑될지 일일이 정하고 싶다면 사용한다. 또한 &optional 에서 처럼 기본값과 등록되었는지 확인하는 변수 모두 사용이 가능하다.

<blockquote>(defun foo (&keyword a b (c 0) (d 0 d-supplied-p)) (list a b c d d-supplied-p)) </blockquote>
해당하는 값을 등록하고 싶다면 :a :b 식으로 :을 변수명 앞에 붙여 값을 가져올 수 있다.

* Return 값 강제하기

- RETURN-FROM, BLOCK
BLOCK으로 해당 블록의 이름을 정한 후 RETURN-FROM을 이용해 해당 블록에서 탈출한다. 기본적으로 함수정의안에서는 함수 이름자체가 하나의 블록으로 취급된다.

<blockquote>(defun foo (n)<br />  (dotimes (i 10)<br />    (dotimes (j 10)<br />      (when (> (* i j) n)<br />        (return-from foo (list i j))))))</blockquote>
* 고차 함수 (Higher-order function)

- LAMBDA, FUNCALL, APPLY

일반적으로 함수를 사용한다는 것은 다음과 같다.

<blockquote>(foo 1 2 3) == (funcall #'foo 1 2 3) </blockquote>
이때 #'는 해당하는 함수를 찾아오는 함수이다. 일종의 함수 포인터 연산이라고 생각해도 되겠다. (Scheme과는 달리 LISP은 변수공간과 함수 공간이 구분되어있다.)

apply는 funcall과 유사하지만 개별 인자들을 일일이 가져오는 형식이 아니고, 리스트 자체를 받아오는 경우에 사용한다.

lambda는 이름없는 함수를 만들 수 있다.

<blockquote>(funcall #'(lambda (x y) (+ x y)) 3 5) ==> 8</blockquote>


2008. 9. 2.

연습문제 4.6


(define (let? exp)
(tagged-list? exp 'let))

(define (let-body exp)
(caddr exp))

(define (let-vars exp)
(let ((var-exp-list (cadr exp)))
(map car var-exp-list)))

(define (let-exps exp)
(let ((var-exp-list (cadr exp)))
(map cadr var-exp-list)))

(define (let->combination exp)
(cons
(make-lambda (let-vars exp)
(let-body exp))
(let-exps exp)))


연습문제 4.4

<blockquote>(define (eval-and exp env)
(if (null? exp)
#t
(let ((test (car exp)))
(if (eval test)
(eval-and (cdr exp) env)
#f))))

(define (eval-or exp env)
(if (null? exp)
#f
(let ((test (car exp)))
(if (eval test)
#t
(eval-or (cdr exp) env)))))

(define (and? exp)
(tagged-list? exp 'and))

(define (or? exp)
(tagged-list? exp 'or))
</blockquote>


좀 아리까리함..

연습문제 4.3

(define eval-table (make-table))
(define get (eval-table 'lookup-proc))
(define put (eval-table 'insert-proc!))

(define (eval-quoted exp env)
(text-of-quotation exp))

(define (eval-set exp env)
(eval-assignment exp env))
(define (eval-define exp env)
(eval-definition exp env))
(define (eval-if. exp env)
(eval-if exp env))
(define (eval-lambda exp env)
(make-procedure (lambda-parameters exp)
(lambda-body exp)
env))
(define (eval-begin exp env)
(eval-sequence (begin-actions exp) env))
(define (eval-cond exp env)
(eval (cond->if exp) env))
(put 'quote eval-quoted)
(put 'set eval-set)
(put 'define eval-definition)
(put 'if eval-if.)
(put 'lambda eval-lambda)
(put 'begin eval-begin)
(put 'cond eval-cond)


(define (eval exp env)
(cond ((self-evaluating? exp) exp)
((variable? exp) (lookup-variable-value exp env))
((get (car exp))
((get (car exp)) exp env))
((application? exp)
(apply (eval (operator exp) env)
(list-of-values (operands exp) evn)))
(else
(error "Unknown expression type -- EVAL" exp))))


연습문제 4.2

a. 프로시저 적용이 우선되게될 때 (define x 3)를 예로 들어 보자.
이 경우 eval은 (define x 3)를 define 정의 식이 아니라, 일반 프로시저로 인식하게 된다.
따라서 define이라는 프로시저에 (x 3) 을 넣어 해당하는 결과를 수행하도록 식을 평가하게된다. 따라서, application이 무리하게 먼저 실행되면 심각한 문제가 발생하게된다.

b. 굳이 application을 call을 사용해서 수행하고 싶다면 다음과 같이 변경한다.
<blockquote>
(define (application? exp) (tagged-list? exp 'call))
(define (operator exp) (cadr exp))
(define (operands exp) (cddr exp))</blockquote>


연습문제 4.1


(define (list-of-values exps env)
(if (no-operands? exps)
'()
(let ((left-values (eval (first-operands exps))))
(let ((right-values (list-of-values (rest-operands exps))))
(cons left-values right-values)))))


오른쪽부터 셈하도록 하려면 left-values와 right-values의 위치를 바꿔주면 된다.

2008. 8. 27.

연습문제 3.79


<blockquote>(define (solve-2nd f y0 dt)
(define y (integral (delay dy) y0 dt))
(define dy (integral (delay ddy) y0 dt))
(define ddy (stream-map f dy y))
y)</blockquote>


연습문제 3.78


(define (solve-2nd a b y0 dt0 dt)
(define y (integral (delay dy) y0 dt))
(define dy (integral (delay ddy) y0 dt))
(define ddy (add-stream (scale-stream dy a)
(scale-stream y b)))
y)


연습문제 3.77

<blockquote>
(define (integral delayed-integrand initial-value dt)
(cons-stream initial-value
(let ((integrand (force delayed-integrand)))
(if (stream-null? integrand)
the-empty-stream
(integral (delay (stream-cdr integrand))
(+ (* dt (stream-car integrand))
initial-value)
dt)))))</blockquote>


2008. 8. 26.

연습문제 3.34

(set-value! B 100 'user)

Probe: B = 100
done

과 같은 결과가 나온다. 즉 A의 값이 구성되지 않는다.
이는 종단점이 3개가 있지만 그 중 하나만 구성되는 것을 볼 수 있다.
multiplier에서 B값은 product에 대응한다. m1, m2에 해당하는 값은 세팅되어있지않으므로 해당하는 값을 구할 수가 없다.

연습문제 3.66

pair의 선두에는 S, T의 선두값이 오고 interleave값이 온 후 다시 재귀적으로 돈다.

몇차례의 step을 밟아보면..
(1 1)
(1 2)
(2 2)
(1 3)
(2 3)
(1 4)
(3 3)
(1 5)
(2 4)
(1 6)
매 짝수열마다 (1, n)항이 오는 것을 알 수 있다.
따라서 (1, n) 이전의 모든 pair의 수는 2(n-1)개가 된다.
 이후는 생략..

연습문제 3.65

PI와 비슷하게 sum 만을 뽑고, 그리고 scale을 계산하도록 하자.

(define (ln2-summands n)
  (cons-stream (/ 1.0 n)
                     (stream-map - (ln2-summands (+ n 1))))

(define ln2-stream
  (scale-stream (partial-sum (ln2-summands 1)) 4)


연습문제 3.64

해당 스트림에서 다음번 얻어지는 값은 cdr중 car한 값 즉, cadr 값이다.
따라서 우선 스트림에서 cadr을 수행하는 stream-cadr을 만든다.

(define (stream-cadr s)
  (stream-car (stream-cdr s)))


이제 stream-limit을 정의한다.

(define (stream-limit s tol)
  (if ( < (abs (- (stream-car s) (stream-cadr s)) tol)
    (stream-card s)
    (stream-limit (stream-cdr tol)))


연습문제 3.63

memo-func 내에서 (proc)를 호출하는 일이 있는데, sqrt-stream을 바로 쓰면, 이 때 해당 proc가 바로 호출되므로 불필요한 계산이 발생한다.

2008. 8. 25.

연습문제 3.58

해당하는 연산은 주어진 수num을 den으로 나눈 값으로, radix 진수로 나타낸다.

상세한 계산 결과는 생략한다.

연습문제 3.57

이전에 memo-func을 이용한다면 각 fibs가 수행될때마다 해당하는 값이 기입되어 나가므로 덧셈은 n-1번만 수행된다.

memo-func을 사용하지않는다면 fibs는 이전에 tree를 만든 형식처럼 지수함수적으로 증가하게 된다.

연습문제 3.56

(define S
  (cons-stream 1
    (merge (scale-stream integers 2)
                (merge (scale-stream integers 3)
                           (scale-stream integers 5)))))

연습문제 3.55

(define (partial-sums s)
  (cons-stream (stream-car s)
                        (add-streams (stream-cdr s) (partial-sums s))))



연습문제 3.54

(define (mul-streams m1 m2)
  (stream-map * m1 m2))

(define factorials (cons-stream 1 (mul-streams factorials integers)))


연습문제 3.53

1, 2, 4, 8, 16 식으로 2^n으로 늘어나는 스트림이다.

2008. 8. 23.

연습문제 3.50

(define (stream-map proc . argstreams)
  (if (stream-null? (car argstreams))
      the-empty-stream
      (cons-stream
       (apply proc (map stream-car argstreams))
       (apply stream-map
              (cons proc (map stream-cdr argstreams))))))


2008. 8. 21.

연습문제 3.48

<blockquote>(define (make-account number balance)
(define (withdraw amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds"))
(define (deposit amount)
(set! balance (+ balance amount))
balance)
(let ((balance-serializer (make-serializer)))
(define (dispatch m)
(cond ((eq? m 'withdraw) withdraw)
((eq? m 'deposit) deposit)
((eq? m 'number) number)
((eq? m 'balance) balance)
((eq? m 'serializer) balance-serializer)
(else (error "Unknown request -- MAKE-ACCOUNT"
m))))
dispatch))

(define (serialized-exchange account1 account2)
(let ((serializer1 (account1 'serializer))
(serializer2 (account2 'serializer)))
(if (< (account1 'number) (account2 'number))
((serializer2 (serializer1 exchange))
account1 account2)
((serializer1 (serializer2 exchange))</blockquote>
account1 account2))))


연습문제 3.47

a.
<blockquote>(define (make-semaphore-mtx maximal)
(let ((count maximal)
(mutex (make-mutex)))
(define (the-sema m)
(cond ((eq? m 'release)
(mutex 'acquire)
(unless (= count maximal)
(set! count (+ 1 count)))
(mutex 'release))
((eq? m 'acquire)
(mutex 'acquire)
(cond
((> count 0)
(set! count (- count 1))
(mutex 'release))
(else
(mutex 'release)
(the-sema 'acquire))))
(else
(error "Unknown request -- " m))))
the-sema))</blockquote>
b.
<blockquote>(define (loop-test-and-set! cell)
(if (test-and-set! cell)
(loop-test-and-set! cell)
'()))

(define (make-semaphore-ts maximal)
(let ((count maximal)
(guard (cons #f '())))
(define (the-sema m)
(cond ((eq? m 'release)
(loop-test-and-set! guard)
(unless (= count maximal)
(set! count (+ 1 count)))
(clear! guard))
((eq? m 'acquire)
(cond
(loop-test-and-set! guard)
((> count 0)
(set! count (- count 1))
(clear! guard))
(else
(clear! guard)
(the-sema 'acquire))))
(else
(error "Unknown request -- " m))))
the-sema))
</blockquote>


연습문제 3.46

어떤 프로시저 p1이 test-and-cset!에서 (car cell)을 통과했다고 가정하고 이때 p2가 mutex에 접근해서 수행한다면 아직 cell 값이 변경되지 않은 상황에서 p2가 실행될 가능성이 있다
따라서 이 경우 p1, p2가 모두 실행될 수 있다.

연습문제 3.44

금액의 전송에서 인출과 송금은 각기 차례열로 지어져있으므로 오류에 대해 안전하다.
따라서 Ben의 생각이 옳다고 본다.
다 만 이전 문제에서는 두 계좌의 차이를 구하는 작업이 중요했다면, 이번 문제에서는 그런 것 없이 실제로 인출이 가능한지 여부가 더 중요하다. 다만 from-account에 남은 돈이 항상 amount만큼 된다고 가정했기 때문에, 문제가 없다고 볼 수 있다.

연습문제 3.43

세 계정을 각각 a, b, c라고 하고 계정의 잔액을 맞바꾸는 연산을 e-ab, e-bc, e-ca라고 하자.
이 경우 프로세스가 차례대로만 돌아준다면 다음과 같은 순서가 유지된다.

phase 1 e-ab: 20 10 30
phase 2 e-bc: 20 30 10
phase 3 e-ca: 10 30 20
phase 4 e-ab: 30 10 20
phase 5 e-bc: 30 20 10
phase 6 e-ca: 10 20 30

즉 6번의 수행후에는 다시 원래대로 반복된다.

그렇지만 줄세워지지않은 exchange로 변동되는 경우
phase 1의 계산중이라면 아직 10 20 30 순으로 저장되어 있으나 a, b의 차액 -10은 계산이 되어있다고 하고, 바로 phase-3가 끝났다고 하면 금액은 30 20 10 이 된다.
이제 phase 1이 마무리되면 a에서 -10을 빼고 b에 -10을 더해야하므로 40 10 10 이라는 결과가 나온다.
따라서 계정 금액이 무너지게 된다.

다만 계정에서 더하고 뺀 값의 총합은 0이기 때문에 전체 금액의 변화는 없게된다.

연습문제 3.42

병행처리에서 줄세워지는 것은 생성시의 문제가 아니라 실제로 해당하는 프로시저가 parrerel-excute를 통해 수행될 때 결정된다. 따라서 이런 변경이 프로그램 수행에 큰 변경이 일어나지는 않을 것으로 본다.

연습문제 3.41

실제 balance의 값을 변경하는 메서드 withdraw와 deposit이 모두 줄세워졌기때문에 실제 어플리케이션에서 balance를 출력하는 구문이 줄세워지는 것은 별 문제가 되지 않는다.

연습문제 3.40

줄세우지 않은 경우는 여러가지가 가능하다.

P1을 모두 계산하고 P2를 계산하는 경우 : 1000000
P2를 계산하고 P1을 계산하는 경우 : 1000000
P1의 계산이 끝난후 P2를 계산하고 P2의 덮어쓰기 이후 P1의 덮어쓰기가 일어나는 경우 100
P1의 계산이 끝난후 P2를 계산하고 P1의 덮어쓰기 이후 P2의 덮어쓰기가 일어나는 경우 1000
P2의 계산이 끝난후 P1을 계산하고 P2의 덮어쓰기 이후 P1의 덮어쓰기가 일어나는 경우 100
P2의 계산이 끝난후 P1을 계산하고 P1의 덮어쓰기 이후 P2의 덮어쓰기가 일어나는 경우 1000

줄세운 겨우에는 P1의 끝나고 P2가 수행되므로 1000000 이 나온다.

연습문제 3.39

순차대로 실행되는 해당 프로시저중 serializer가 된 함수는 x를 하나 늘리는 것과 x를 거듭제곱하는 두개의 프로시저가 있다.
문제는 이렇게 차례지은 프로시저중 하나만이 제대로 등록된다는 점이다.
따라서 해당하는 값은 크게

거듭제곱 -> x 저장 -> 하나 늘리기 : 101
거듭제곱 -> 하나 늘리기 -> 거듭제곱 결과 저장 : 100
하나늘리기 -> 거듭제곱 -> 결과 저장 : 121

이 세가지가 모두 가능하게 된다.

연습문제 3.38

a.
Peter, Paul, Mary -> 45
Peter, Mary, Paul -> 35
Mary, Peter, Paul -> 40
Mary, Paul, Peter -> 40
Paul, Peter, Mary -> 45
Paul, Mary, Peter -> 50

b. Peter가 실행해서 일단 balance를 110으로 계산한다. 다음에 Pault은 이전 balance 100을 가지고 해당 값을 80으로 맞춘다.  paul의 프로세스가 balance를 110으로 맞춘다. 마지막으로 mary가 값을 55로 맞춘다. 이런 시나리오가 가능하다.

2008. 8. 20.

연습문제 3.37

(define (c+ x y)
  (let ((z (make-connector)))
    (adder x y z)
    z))

(define (c* x y)
  (let ((z (make-connector)))
    (multiplier x y z))
  z)

(define (c/ x y)
  (let ((z (make-connector)))
    (multiplier x z y))
  z)

(define (cv n)
  (let ((a (make-connector)))
    (constant n a)
    a))



연습문제 3.35

(define (squarer a b)
(define (process-new-value)
(if (has-value? b)
(if (< (get-value b ) 0)
(error "square less than 0 -- SQUARER" (get-value b))
(set-value! a (sqrt (get-value b)) me))
(if (has-value? a)
(set-value! b (square (get-value a)) me) )))
(define (process-forget-value)
(forget-value! a me)
(forget-value! b me))
(define (me request)
(cond ((eq? request 'I-have-a-value)
(process-new-value))
((eq? request 'I-lost-my-value)
(process-forget-value))
(else
(error "Unknown request -- SQUARER" request))))
(connect a me)
(connect b me)
me)

연습문제 3.34

(set-value! B 100 'user)

Probe: B = 100
done

과 같은 결과가 나온다. 즉 A의 값이 구성되지 않는다.
이는 종단점이 3개가 있지만 그 중 하나만 구성되는 것을 볼 수 있다.
multiplier에서 B값은 product에 대응한다. m1, m2에 해당하는 값은 세팅되어있지않으므로 해당하는 값을 구할 수가 없다.

연습문제 3.33

(define (averager a b c)
(let ((u (make-connector))
(v (make-connector)))
(adder a b u)
(multiplier c v u)
(constant 2 v)
'ok))

2008. 8. 14.

연습문제 3.32

회로에서 개별 회로는 이전에 일어난 결과를 토대로 동작하게된다. 조각 시간에 들어있는 프로시저는 회로의 구성에 따라 수행순서가 들어가기때문에 이런 차례는 반드시 따라야 한다.

연습문제 3.31

프로시저를 곧바로 호출하면서 wire에 연산이 추가될 때 즉시적인 결과가 발생한다. 즉 after-delay가 호출되면서 자연스럽게 agenda에 회로데이터가 적재된다.
만약 프로시저를 곧바로 호출하지않는다면, 실제 agenda에 적용시켜주기위해서는 논리적으로 회로의 순서에 맞게끔 해당 회로를 다시 실행시켜주어야하는 작업이 있어야한다.

연습문제 3.30

(define (ripple-carry-adder lista listb lists c)
  (let ((lc '()))
    (define (inner-ripple la lb ls ck)
      (if (null? la)
          'ok
          (begin
            (let ((cout (make-wire)))
              (full-adder (car la) (car lb) ck (car ls) cout)
              (append cout lc)                   
              (inner-ripple (cdr la) (cdr lb) (cdr ls)) cout))))
    (inner-ripple lista listb lists c)))

뒤쳐지는 시간을 논리곱, 논리합, 인버터 시간으로 표현하면..
inner-ripple은 모두 n번 호출되므로 full-adder 역시 n번 호출된다.
full-adder는 2개의 half-adder와 하나의 논리합으로 구성되어있다.
half-adder는 두개의 논리곱, 하나의 논리합, 하나의 인버터로 구성되어 있다.

따라서 full-adder는 네개의 논리곱, 세개의 논리합, 하나의 인버터 만큼 시간지연이 발생한다.
그러므로 ripple-carry-adder는 총 4n 논리곱 + 3n 논리합 + n인버터 지연시간이 필요하게된다.

연습문제 3.29

드 모르강의 법칙에 따라 P v Q = (~ (~ P ^ ~Q))와 같으므로..
(define (or-gate a1 a2 output)
  (let ((na1 (make-wire))
        (na2 (make-wire))
        (nand (make-wire)))
    (inverter a1 na1)
    (inverter a2 na2)
    (and-gate na1 na2 nand)
    (inverter nand output)))

와 같이 정의할 수 있다.
이때 발생하는 delay는 and-gete-delay + (3 * inverter-delay) 와 같다.

연습문제 3.28

(define (or-gate a1 a2 output)
  (define (or-action-procedure)
    (let ((new-value (logical-or (get-signal a1) (get-signal a2))))
      (after-delay or-gate-delay
                   (lambda ()
                     set-signal! output new-value))))
  (add-action! a2 or-action-procedure)
  (add-action! a1 or-action-procedure)
  'ok)


2008. 8. 13.

연습문제 3.27

memorize가 호출되면 새로운 환경이 생성된다.
이 환경에서 table이 구성되며, 테이블에 넘겨진 인자 n과 다른 값이 들어오면 기존의 factorial lambda연산을 수행하고 그렇지 않은 경우에는 테이블을 lookup 하게 된다.

memo- fib를 (memorize fib)로 변경한 경우 fib자체가 memo-fib에 의해 정의되지 않기때문에 이전에 테이블에 저장되지 않은 값의 경우 이전처럼 지수비례로 자라난다. 물론 이전에 계산한 데이터의 경우에는 테이블에서 lookup하여 찾을 수 있다.

2008. 8. 12.

연습문제 3.24

(define false #f)
(define (make-table same-proc)
  (let ((local-table (list '*table*)))
    (define (assoc key records)
      (cond ((null? records) false)
            ((same-proc key (caar records)) (car records))
            (else (assoc key (cdr records)))))
    (define (lookup key-1 key-2)
      (let ((subtable (assoc key-1 (cdr local-table))))
        (if subtable
            (let ((record (assoc key-2 (cdr subtable))))
              (if record
                  (cdr record)
                  false)))))
    (define (insert! key-1 key-2 value)
      (let ((subtable (assoc key-1 (cdr local-table))))
        (if subtable
            (let ((record (assoc key-2 (cdr subtable))))
              (if record
                  (set-cdr! record value)
                  (set-cdr! subtable
                            (cons (cons key-2 value)
                                  (cdr subtable)))))
            (set-cdr! local-table
                      (cons (list key-1
                                  (cons key-2 value))
                            (cdr local-table)))))
      'ok)
    (define (dispatch m)
      (cond ((eq? m 'lookup-proc) lookup)
            ((eq? m 'insert-proc!) insert!)
            (else (error "Unknown operator -- TABLE" m))))
    dispatch))


연습문제 3.23

Deque 문제는 양방향 링크 리스트로 푼다.

(define (make-deque) (cons '() '()))

(define (front-ptr deque) (car deque))
(define (rear-ptr deque) (cdr deque))
(define (set-front-ptr! deque item) (set-car! deque item))
(define (set-rear-ptr! deque item) (set-cdr! deque item))

(define (empty-deque? deque) (null? (front-ptr deque)))
(define (front-deque deque)
  (if (empty-deque? deque)
      (error "Empty deque")
      (cadr (front-ptr deque))))
(define (rear-deque deque)
  (if (empty-deque? deque)
      (error "Empty deque")
      (cadr (rear-ptr deque))))

(define (front-insert-deque! deque item)
  (let ((ele (list '() item '())))
    (cond ((empty-deque? deque)
           (begin
             (set-front-ptr! deque ele)
             (set-rear-ptr! deque ele)))
          (else
           (begin
             (set-car! (cddr ele) (front-ptr deque))
             (set-car! (front-ptr deque) ele)
             (set-front-ptr! deque ele))))))
(define (rear-insert-deque! deque item)
  (let ((ele (list '() item '())))
    (cond ((empty-deque? deque)
           (begin
             (set-front-ptr! deque ele)
             (set-rear-ptr! deque ele)))
          (else
           (begin
             (set-car! ele (rear-ptr deque))
             (set-car! (cddr (rear-ptr deque)) ele)
             (set-rear-ptr! deque ele))))))

(define (front-delete-deque! deque)
    (cond ((empty-deque? deque)
           (error "Empty queue " deque))
          (else
           (begin
             (set-front-ptr! deque (caddr (front-ptr deque)))
             (set-car! (car (front-ptr deque)) '())))))

(define (rear-delete-deque! deque)
    (cond ((empty-deque? deque)
           (error "Empty queue " deque))
          (else
           (begin
             (set-rear-ptr! deque (car (rear-ptr deque)))
             (set-car! (cddr (rear-ptr deque)) '())))))


(define (print-deque deque)
  (define (print-dl double-list)
    (if (null? double-list)
        (display "")
        (begin
          (display (cadr double-list))
          (print-dl (caddr double-list)))))
  (cond ((empty-deque? deque) (display ""))
        (else
         (print-dl (front-ptr deque)))))


연습문제 3.22

(define (make-queue)
  (let ((front-ptr '())
        (rear-ptr '()))
    (define (set-front-ptr! item)
      (set! front-ptr item))
    (define (set-rear-ptr! item)
      (set! rear-ptr item))
    (define (empty-queue?)
      (null? front-ptr))
    (define (insert-queue! item)
      (let ((new-pair (cons item '())))
        (cond ((empty-queue?)
               (set-front-ptr! new-pair)
               (set-rear-ptr! new-pair))
              (else
               (set-cdr! rear-ptr new-pair)
               (set-rear-ptr! new-pair)))))
    (define (delete-queue!)
      (cond ((empty-queue?)
             (error "DELETE! called with an empty queue"))
            (else
             (set-front-ptr! (cdr front-ptr)))))
    (define (print-queue)
      (display front-ptr))
    (define (dispatch m)
      (cond ((eq? m 'insert-queue!) insert-queue!)
            ((eq? m 'empty-queue?) empty-queue?)
            ((eq? m 'print-queue) print-queue)
            ((eq? m 'delete-queue!) delete-queue!)
            (else
             (error "Unknown request -- MAKE-QUEUE" m))))
    dispatch))


연습문제 3.21

Ben은 queue가 리스트를 가르키는 pair임을 잊고 이다. queue는 car에는 리스트의 가장 앞을, cdr에는 리스트의 가장 뒤를 가르키고 있기때문에 실제 출력을 한다면 해당하는 리스트 전체와, 가장 뒤 요소를 동시에 출력하게 된다.

front-ptr 로 얻어진 리스트는 해당하는 queue 전체의 내용과 동일하므로 다음과 같이 print-queue를 만들 수 있다.

(define (print-queue queue)
  (cond ((empty-queue? queue) (display ""))
        (else
         (display (front-ptr queue)))))


2008. 8. 11.

연습문제 3.20

앞의 cons를 이용했다고 하면

z는 x가 가르키는 pair로 묶은 또다른 pair이다.

그러므로 z의 cdr은 x가 되고 그 x의 car을 변경했으므로 (car x)는 17이 된다.

연습문제 3.16

3이 나오는 경우

(define z (list 'a 'b 'c)

4가 나오는 경우

이 경우는 하나의 pair 가 추가되면 되므로..

(set-car! (cdr z) (cddr z))

라고 하면 된다.

7이 나오는 경우

이 경우는 z의 car이 그 cdr을 가르키게 하면된다.

(set-car! z (cdr z))

연습문제 3.14

mystery는 list의 값을 역으로 만드는 함수이다.

v -> (a)
w -> (d c b a)

가 된다.