Difference between r1.26 and the current
@@ -1,7 +1,9 @@
== 1. 개발 환경 갖추기 ==
* SBCL로 컴파일하기* http://stackoverflow.com/questions/14171849/compiling-common-lisp-to-an-executable
* {{{(sb-ext:save-lisp-and-die "hello.exe" :toplevel #'main :executable t)}}}
* 커먼 리스프로 소켓을 만들어서 실행해보자!!!
* 커먼 리스프로 소켓을 만들어서 실행해보자!!
* 개발 환경* 서버: 우분투
* 컴파일러: SBCL
@@ -15,12 +17,35 @@
* 이제 sbcl에서 quicklisp를 쓰려면 quicklisp/setup.lisp를 로드해야 한다.
* {{{(load "~/quicklisp/setup.lisp")}}}
* usocket 설치하기: {{{(ql:quickload "usocket")}}}
----
A Gentle Introduction To Symbolic Computation: http://www.cs.cmu.edu/~dst/LispBook/
읽으면서 정리해보자
* 리스프는 인공지능 연구의 주요 언어로 유명하다
* 염두에 둔 독자: 프로그래밍 입문하는 학생 / 심리학자, 언어학자, 컴퓨터 과학자 등 인공지능에 관심 있는 사람들 / 취미로 컴퓨터 하는 사람
* {{{(load "~/quicklisp/setup.lisp")}}}
* usocket 설치하기: {{{(ql:quickload "usocket")}}}
* 아마 설치 안됐으면 설치하고 돼있으면 로드만 하는 듯..?
* 아마 설치 안됐으면 설치하고 돼있으면 로드만 하는 듯?
* 커먼 리스프에서 쓰레드 쓰기
* bordeaux-threads 라이브러리를 많이 쓰는 것 같다
* {{{(ql:quickload "bordeaux-threads")}}}
{{{
(defun test () (print "assdasdasdsadasdasd"))
(bordeaux-threads:make-thread #'test)
}}}
* multiple value return
* 뭐 이런 문법이 있어 http://psg.com/~dlamkins/sl/chapter03-09.html
----
== 2. 직접 겪으며 해결하는 고통이 가득한 트러블슈팅 ==
* 동적 바인딩으로 빅엿 먹은 사례
{{{
(defvar clients '())
(defun sendto-all-clients (message) ...)
(defun some-func ()
(let ( (clients *some-sub-list*) )
(sendto-all-clients "hi")
)
)
}}}
----
== 3. A Gentle Introduction To Symbolic Computation ==
* [http://www.cs.cmu.edu/~dst/LispBook/]에서 무료로 볼 수 있다
* 읽으면서 정리해보자
== 서문 ==* 리스프는 인공지능 연구의 주요 언어로 유명하다
* 염두에 둔 독자: 프로그래밍 입문하는 학생 / 심리학자, 언어학자, 컴퓨터 과학자 등 인공지능에 관심 있는 사람들 / 취미로 컴퓨터 하는 사람
@@ -41,8 +66,6 @@
* 잘 알려지지 않은 PUSHNEW 같은 원시형을 사용하느니 함수를 좀 더 풀어쓴 것이 몇 군데 있다
* 가장 고급 주제인 multiple value나 package system은 다루지 않는다
PDF 파일에 목차가 없어 ㅡㅡ
== 1장. 함수와 데이터 ==
=== 1장 요약 ===
* 산술 함수: +, -, *, /, ABS, SQRT
* 가장 고급 주제인 multiple value나 package system은 다루지 않는다
=== 1장 요약 ===
* 산술 함수: +, -, *, /, ABS, SQRT
@@ -293,6 +316,9 @@
* 문법: {{{(if (test) (true-part) (false-part))}}}
* 절댓값 함수: {{{(defun my-abs (x) (if (< x 0) (- x) x))}}}
* 세 번째 인자 즉 false-part는 생략할 수 있다. 이 경우 NIL로 처리된다.
* 간략한 형태
(COND (test-1 consequent-1)
* 절댓값 함수: {{{(defun my-abs (x) (if (< x 0) (- x) x))}}}
* 세 번째 인자 즉 false-part는 생략할 수 있다. 이 경우 NIL로 처리된다.
* true-part에 표현식을 여러 개 쓰고 싶을 때!!! 절대 (expr1 expr2 exprN) 이따구로 쓰지 말 것! 문법 오류
* (true-part) => (progn expr1 expr2 exprN)
* 9장에 PROGN 나오니 참조
* COND 매크로* 간략한 형태
(COND (test-1 consequent-1)
@@ -862,6 +888,9 @@
(return e))))
}}}
* DO 매크로
(DO ((var1 init1 [update1])
(var2 init2 [update2])
}}}
* DO 매크로
* 먼저 var1, var2...을 선언하고 그 값을 init1, init2...로 초기화한다
* test를 평가한다. 참이면 action-1 ... action-n을 평가하고 action-n의 값을 반환한다
* 거짓이면 body를 평가한다
{{{(DO ((var1 init1 [update1])
(var2 init2 [update2])
@@ -905,7 +934,7 @@
* PROG1은 첫 번째 표현식의 값을 반환한다
* PROG2는 두 번째 표현식의 값을 반환한다
* PROGN은 마지막 표현식의 값을 반환한다
* 함수는 키워드 인자나 임의 개수의 인자를 취할 수 있다
* 인자 목록에 '''람다-리스트 키워드'''^^lambda-list keyword^^라는 특수 심볼을 추가해야 한다
* PROG2는 두 번째 표현식의 값을 반환한다
* PROGN은 마지막 표현식의 값을 반환한다
* 근데 요즘인 이것 다 잘 안 써요
* 근데 요즘은 이것 다 잘 안 써요
* 추가 인자^^optional argument^^* 함수는 키워드 인자나 임의 개수의 인자를 취할 수 있다
* 인자 목록에 '''람다-리스트 키워드'''^^lambda-list keyword^^라는 특수 심볼을 추가해야 한다
@@ -1219,5 +1248,50 @@
* (defvar birds)
* (defvar *total-glasses* 0 "Total glasses sold so far")
* 단 해당 변수에 이미 값이 있으면 변경하지 않는다
* (defvar *total-glasses* 0 "Total glasses sold so far")
* 단 해당 변수에 이미 값이 있으면 변경하지 않는다
* DEFPARAMETER: 프로그램을 실행하는 동안 값이 바뀌지 않을 변수를 선언한다
* DEFPARAMETER: DEFVAR와 같지만 변수에 값이 있던 없던 새 값을 설정한다
* DEFCONSTANT: 상수를 정의한다끝!!! ㅋㅋ 이제 소켓 프로그래밍 해야지
----
== Common Lisp Object System ==
* 커먼 리스프에서 클래스 써보자
* A Brief Guide to CLOS: [http://www.aiai.ed.ac.uk/~jeff/clos-guide.html]
* 임의 객체의 클래스 알아내기
||표현식||||값||
||(class-of 'a)||||#<Built-In-Class SYMBOL>||
||(class-of "a")||||#<Built-In-Class STRING>||
||(class-of 12)||||#<Built-In-Class INTEGER>||
||(class-of '(a b))||||#<Built-In-Class CONS>||
||(class-of '#(a b c))||||#<Built-In-Class VECTOR>||
* '''잠깐! 표기법...'''
* "Thing*" means zero or more occurrences of thing
* "thing+" means one or more.
* Curly brackets { and } are used for grouping, as in {a b}+, which means a b, a b a b, a b a b a b, etc.
* 클래스 정의하기
{{{
(DEFCLASS class-name (superclass-name*)
(slot-description*)
class-option*)
}}}
* slot-description은 (slot-name slot-option*) 형식
* slot-option에 유용한 옵션
* :ACCESSOR function-name
* :INITFORM expression
* :INITARG symbol
* 더 자세한 건 여기에~~ http://en.wikibooks.org/wiki/Common_Lisp/Advanced_topics/CLOS
* 인스턴스 생성
* {{{(MAKE-INSTANCE class {initarg value}*)}}}
* person 클래스를 정의했으면 이런 식: (make-instance 'person :age 100)
* 부모클래스
* 클래스 정의할 때 부모클래스를 생략하면 자동으로 standard-object를 상속한다
* 다중 상속이 가능하다: (defclass a (b c) ...)
* 그럼 :initform은요? 겹치는데??
* b의 정의가 c의 정의보다 우선순위가 높다
* 메서드 정의하기
{{{
(DEFMETHOD generic-function-name specialized-lambda-list
form*)
}}}
* specialized-lambda-list라구요?
* 일반적인 lambda-list는 (var1 var2 ...)
* 이건 ((var1 class1) (var2 class2) ...) ; 변수의 타입을 제한
1. 개발 환경 갖추기 ¶
- SBCL로 컴파일하기
- http://stackoverflow.com/questions/14171849/compiling-common-lisp-to-an-executable
(sb-ext:save-lisp-and-die "hello.exe" :toplevel #'main :executable t)
- http://stackoverflow.com/questions/14171849/compiling-common-lisp-to-an-executable
- 커먼 리스프로 소켓을 만들어서 실행해보자!!
- 개발 환경
- 서버: 우분투
- 컴파일러: SBCL
- 라이브러리: usocket (quicklisp로 설치)
- 서버: 우분투
- 참고: https://github.com/ciaranbradley/land-of-lisp-chap-12-usocket/blob/master/server.lisp
- quicklisp 주의점!!!
- quicklisp.lisp는 quicklisp를 설치할 때 쓰는 거
sbcl --load quicklisp.lisp
로 실행하고
- sbcl 안에서
(quicklisp-quickstart:install)
하면 quicklisp 설치 완료
- 설치하고 나면 홈폴더에 quicklisp 폴더가 생긴다.
- 이제 sbcl에서 quicklisp를 쓰려면 quicklisp/setup.lisp를 로드해야 한다.
(load "~/quicklisp/setup.lisp")
- usocket 설치하기:
(ql:quickload "usocket")
- 아마 설치 안됐으면 설치하고 돼있으면 로드만 하는 듯?
- 아마 설치 안됐으면 설치하고 돼있으면 로드만 하는 듯?
- quicklisp.lisp는 quicklisp를 설치할 때 쓰는 거
- 개발 환경
- 커먼 리스프에서 쓰레드 쓰기
- bordeaux-threads 라이브러리를 많이 쓰는 것 같다
(ql:quickload "bordeaux-threads")
- bordeaux-threads 라이브러리를 많이 쓰는 것 같다
(defun test () (print "assdasdasdsadasdasd")) (bordeaux-threads:make-thread #'test)
- multiple value return
- 뭐 이런 문법이 있어 http://psg.com/~dlamkins/sl/chapter03-09.html
- 뭐 이런 문법이 있어 http://psg.com/~dlamkins/sl/chapter03-09.html
2. 직접 겪으며 해결하는 고통이 가득한 트러블슈팅 ¶
- 동적 바인딩으로 빅엿 먹은 사례
(defvar clients '()) (defun sendto-all-clients (message) ...) (defun some-func () (let ( (clients *some-sub-list*) ) (sendto-all-clients "hi") ) )
3. A Gentle Introduction To Symbolic Computation ¶
- http://www.cs.cmu.edu/~dst/LispBook/에서 무료로 볼 수 있다
- 읽으면서 정리해보자
서문 ¶
- 리스프는 인공지능 연구의 주요 언어로 유명하다
- 염두에 둔 독자: 프로그래밍 입문하는 학생 / 심리학자, 언어학자, 컴퓨터 과학자 등 인공지능에 관심 있는 사람들 / 취미로 컴퓨터 하는 사람
- 책의 구성
- 1, 2장: 상자, 화살표 표기로 기초적인 함수, 함수 합성 설명
- 3장: EVAL 표기
- 8장까지는 부수효과 없는 프로그래밍
- 9장: 입출력
- 10장: ordinary variables, generalized variables, destructive sequence operations.
- 11장: 반복(DO, DO*)
- 12장: 구조
- 13장: 배열, 해시테이블, property list
- 14장: 매크로, 컴파일, lexical scoping과 dynamic scoping의 차이
- 1, 2장: 상자, 화살표 표기로 기초적인 함수, 함수 합성 설명
- 간략화
- Common Lisp는 복잡한 언어라 적당히 간략화한 것들이 있다
- 1+와 1-는 이름이 혼란스러워 뺐다
- EQUAL을 주로 사용. EQ, EQL, EQUALP, =는 고급 주제에서 논의
- 잘 알려지지 않은 PUSHNEW 같은 원시형을 사용하느니 함수를 좀 더 풀어쓴 것이 몇 군데 있다
- 가장 고급 주제인 multiple value나 package system은 다루지 않는다
- Common Lisp는 복잡한 언어라 적당히 간략화한 것들이 있다
1장. 함수와 데이터 ¶
1장 요약 ¶
- 산술 함수: +, -, *, /, ABS, SQRT
- 숫자형: 정수(integer), 부동소수점수(floating number), 비율수(ratio)
- 정수로 산술 연산을 하면 결과는 정수 또는 비율수다.
- 3/6 = 1/2 <- 이게 ratio
- 3/6.0 = 0.5
- 정수로 산술 연산을 하면 결과는 정수 또는 비율수다.
- 심볼: 숫자 이외의 또다른 자료형
- 특수 심볼: T는 참 또는 긍정, NIL은 거짓 또는 부정
- T 또는 NIL을 반환하는 함수는 술어식(predicate)
- 특수 심볼: T는 참 또는 긍정, NIL은 거짓 또는 부정
- 기본적인 내장 함수들
- NUMBERP: 데이터가 숫자형인가?
- SYMBOLP: 데이터가 심볼인가?
- 숫자형 전용 술어식들: ZEROP(영), ODDP(홀수), EVENP(짝수)
- 크기 비교: <. >
- 항등 비교: EQUAL
- 내장 함수들은 원시 함수(primitive function) 또는 그냥 primitive라고 부른다
- 입력에서 1을 더하거나 빼는 1+, 1- 함수가 있지만 이 책에선 쓰지 않겠다. 헷갈림
- 부정: NOT(NIL은 T로, NIL이 아닌 모든 것은 NIL으로)
- 그럼
(NOT (NOT 5))
는 5가 아니라 T인가? => T 맞음
- 그럼
- NUMBERP: 데이터가 숫자형인가?
- 함수의 인자 개수
- ODDP의 인자는 딱 1개, EQUAL은 2개
- +, -, *, /의 인자 개수는 여러 개
- +의 인자가 2, 3, 4면 2랑 3부터 더하고 여기에 4를 더한다
- -, /의 인자가 하나인 경우
- -의 인자가 n이면 결과는 0에서 n 뺀 거
- /의 인자가 n이면 결과는 1에서 n 나눈 거
- -의 인자가 n이면 결과는 0에서 n 뺀 거
- 타 언어는 산술 연산이 이항 연산이고 2+3+4는 단순히 (2+3)+4로 처리되는데
- 리스프는 + 자체가 n항 연산이 될 수 있다는 건가???
- +의 인자가 2, 3, 4면 2랑 3부터 더하고 여기에 4를 더한다
- ODDP의 인자는 딱 1개, EQUAL은 2개
- 오류: 3이랑 FRED를 더할 수 없고, EQUAL의 인자를 한 개만 넘기면 안 되고, 0으로 나누면 안 되고.
1장 고급 주제 ¶
- 리스프의 역사
- 1956년 Dartmouth 대학에서 여름에 열린 인공지능 관련 연구 모임에서 존 매카시가 "list processing"이란 기법을 배웠다
- 1950년대에는 어셈블리어로 프로그래밍을 했지
- "list processing"을 발표한 사람들은 심볼과 리스트를 다루는 보다 추상적인 IPL이란 언어를 만들었다
- 그 문법이 어셈블리어에 가까워서 괴상했다
- 1950년대에는 어셈블리어로 프로그래밍을 했지
- 한편 수치적 계산에 특화된 FORTRAN이 개발되고 있었다
- 어셈블리어는 Y = (X + 5) * 10 하려면 LOAD Y, X -> ADD Y, 5 -> MULT Y, 10 라고 써야 하는데
- FORTRAN은 그냥 Y = (X + 5) * 10 이라고 쓰면 된다. 표현식(expression)의 작성이 가능하다는 뜻
- 그 당시에는 이 개념이 혁명이였다더라
- 존 매카시: 나도... 나도 이런 거 만들 거야!
- FORTRAN에 리스트 조작을 위한 특별한 하위루틴들을 추가하면 어떨까?
- IBM의 Herbert Gelerntner와 Carl Gerberich가 이 아이디어를 따와 FLPL을 만듦
- 매카시는 IPL, FORTRAN, FLPL을 토대로 LISP를 설계
- FORTRAN에 리스트 조작을 위한 특별한 하위루틴들을 추가하면 어떨까?
- 어셈블리어는 Y = (X + 5) * 10 하려면 LOAD Y, X -> ADD Y, 5 -> MULT Y, 10 라고 써야 하는데
- Lisp 1.5는 처음으로 널리 퍼진 리스프 방언
- 1960년대 중반부터 온갖 리스프 방언이 생기기 시작
- MacLisp, Interlisp, Stanford Lisp 1.6, UCI Lisp...
- 모두 Lisp 1.5을 확장한 것. 하지만 서로 호환이 하나도 안 된다
- MacLisp, Interlisp, Stanford Lisp 1.6, UCI Lisp...
- 1970년대: ALGOL계 언어의 특징들과 리스프의 문법을 결합한 Scheme이 나왔어요
- 그리고 또다시 Scheme의 방언들이 우후죽순 생겨나기 시작했다
- 그리고 또다시 Scheme의 방언들이 우후죽순 생겨나기 시작했다
- 1980년대: 널리 쓰이는 리스프 방언만 해도 수두룩한데... 뭘 써야 하지?
- 만국공용어를 만들자!!!
- 1984년 Common Lisp 초안 발표
- 학술계에서도 산업계에서도 빠르게 주류로 성장
- 지금은 Common Lisp 때문에 Scheme 빼고 거진 다 죽었지렁
- 만국공용어를 만들자!!!
- 1956년 Dartmouth 대학에서 여름에 열린 인공지능 관련 연구 모임에서 존 매카시가 "list processing"이란 기법을 배웠다
- 많은 프로그래밍 아이디어가 리스프에서 출발할 것
- 인터프리터 함수와 컴파일 함수의 결합
- 함수 재귀 호출
- 소스 수준 추적 & 디버깅
- 문법 지향 편집기
- 오늘날의 리스프는 함수형, 객체지향, 병렬 프로그래밍 연구의 선두 주자
- 인터프리터 함수와 컴파일 함수의 결합
2장. 리스트 ¶
2장 요약 ¶
- Lisp는 List Processor라는 뜻
- 리스트는 가장 다재다능한(versatile) 타입이다
- 집합, 테이블, 그래프, 영어 문장 등 뭐든지 표현 가능
- 함수도 리스트로 표현 가능
- 집합, 테이블, 그래프, 영어 문장 등 뭐든지 표현 가능
- 모든 리스트는 두 가지 형태를 가진다
- printed representation
- 사람이 키보드로 쓰기 편한 형식
- 사람이 키보드로 쓰기 편한 형식
- internal representation
- 실제로 메모리에 거주하는 형식
- 실제로 메모리에 거주하는 형식
- 리스트의 예:
(RED GREEN BLUE)
,(AARDVARK)
,(2 3 5 7 11 13 17)
- 메모리에서의 실제 형태: 셀(cell)마다 원소를 가리키는 포인터, 다음 셀을 가리키는 포인터를 가진다
- 마지막 셀은 NIL을 가리킨다
[PNG image (5.19 KB)]
- 포인터는 대개 4바이트이므로 셀은 8바이트
- 중첩 리스트
((BLUE SKY) (GREEN GRASS) (BROWN EARTH))
[PNG image (13.3 KB)]
- 리스트의 길이: 원시 함수 LENGTH를 이용
- 빈 리스트는 NIL로 표현
- 사실 NIL은 ()다. NIL과 빈 리스트는 동치
- NIL은 심볼이자 리스트인 유일한 존재
- 리스트의 원소 얻기
- FIRST, SECOND, THIRD 함수: 첫 번째, 두 번째, 세 번째 원소
- REST 함수: 첫 번째를 제외한 나머지 리스트
- FIRST는 CAR와 같고 REST는 CDR과 같다.
- CAR, CDR이라는 이름은 리스프가 처음에 트랜지스터도 없어서 진공관 쓰는 컴퓨터에서 작동할 때 쓴 말
- CAR: Contents of Address portion of Register
- CDR: Contents of Decrement portion of Register (카우더cou-der라고 발음)
- 요즘 컴퓨터에는 맞지 않지만 아직도 쓴단다
- CADR: CDR 다음에 CAR (kae-der라고 발음)
- CDAR: CAR 다음에 CDR (cou-dar)
- CADDR: CDR 다음에 CDR 다음에 CAR (ka-dhi-der)
- 이게 뭐야 ㅡㅡ
[PNG image (46.71 KB)]
- CAR와 CDR에 NIL을 입력하면 NIL이 출력된다
- 에러가 아니구요???
- 이게 더 좋대
- 에러가 아니구요???
- CAR, CDR이라는 이름은 리스프가 처음에 트랜지스터도 없어서 진공관 쓰는 컴퓨터에서 작동할 때 쓴 말
- FIRST, SECOND, THIRD 함수: 첫 번째, 두 번째, 세 번째 원소
- 리스트 생성
- CONS는 한 데이터와 리스트를 받아서 그 데이터를 첫 번째 원소로 끼워넣은 리스트를 반환한다
- x = CONS of (CAR of x) and (CDR of x)
- LIST는 임의 개수의 원소를 받아 그것들의 리스트를 생성한다
- CONS는 한 데이터와 리스트를 받아서 그 데이터를 첫 번째 원소로 끼워넣은 리스트를 반환한다
- 리스트 술어식
- LISTP: 입력이 리스트인가?
- CONSP: 입력이 cons cell인가?
- LISTP와 비슷하지만, NIL은 리스트인 반면 cons cell이 아니다
- LISTP와 비슷하지만, NIL은 리스트인 반면 cons cell이 아니다
- ATOM: 입력이 cons cell이 아닌가? (CONSP의 부정)
- NULL: 입력이 NIL이면 T 반환. NOT과 동치.
- 논리 연산에는 NOT을, 리스트 연산에는 NULL을 사용하는 관례가 있다
- 논리 연산에는 NOT을, 리스트 연산에는 NULL을 사용하는 관례가 있다
- LISTP: 입력이 리스트인가?
- printed representation
2장 고급 주제 ¶
- 리스트를 이용한 1진법 산술
- 0 = NIL, (X) = 1, (X X) = 2, (X X X) = 3...
- REST는 1 빼기. 단 0 빼기 1은 0
- LENGTH는 실제 숫자로 변환
- 이걸 어따 써먹죠
- 0 = NIL, (X) = 1, (X X) = 2, (X X X) = 3...
- 진리스트(proper list)는 NIL로 끝나는 리스트
- NIL로 안 끝나는 리스트는? dotted list
- A와 B의 CONS :
(A .
<- dotted pair
- LIST는 진리스트만 생성 가능. CONS는 Dotted list를 만들 수 있다
- NIL로 안 끝나는 리스트는? dotted list
- 순환 리스트(circular list)도 있다
- 원소 A, B, C가 있는데 C를 포함하는 cell이 다시 A를 포함하는 cell을 가리키면?
- sharp-equal notation:
#1=(A B C . #1#)
라고 표기한다
- 원소 A, B, C가 있는데 C를 포함하는 cell이 다시 A를 포함하는 cell을 가리키면?
- (A B C . D)의 LENGTH는 4가 아니라 3
- 순환 리스트의 LENGTH는 무한 루프
3장 EVAL 표기 ¶
3장 요약 ¶
- 함수를 그림으로 그리지 말고 리스트로 표현하자
- 리스프에서 함수는 데이터다
- EVAL 표기를 정복했으면 리스프를 통해 컴퓨터와 대화하는 데 필요한 건 대부분 알게 된 셈
- EVAL 함수는 리스프의 핵심이다
- 리스프 표현식을 평가해서 결과값을 내놓는다
- 함수 뒤에 입력값들이 따라오는 형식
- 표현식
(+ 2 3)
는 5로 평가된다
(+ 1 6) => 7
(oddp (+ 1 6)) => t
(* 3 (+ 1 6)) => 21
(/ (* 2 11) (+ 1 6)) => 22/7
- 리스프 표현식을 평가해서 결과값을 내놓는다
- EVAL의 동작을 정의하는 평가 규칙들
- 숫자형, T, NIL은 그 자신으로 평가된다
- 리스트의 첫 원소는 함수, 나머지는 그 함수에 전달되는 아직 평가되지 않은 인자다
- 인자들은 왼쪽에서 오른쪽으로 평가된다
- 인자들은 왼쪽에서 오른쪽으로 평가된다
- 심볼은 그 심볼이 가리키는 변수의 값으로 평가된다
(ODDP (+ 1 6))
의 evaltrace diagram(평가추적도표?)
[PNG image (12.47 KB)]
- 숫자형, T, NIL은 그 자신으로 평가된다
- EVAL 표기로 함수 정의하기
- 두 수의 평균을 구하는 AVERAGE 함수:
(defun average (x y) (/ (+ x y) 2.0))
- defun은 매크로 함수로서 그 인자를 평가하지 않는다
- defun은 함수를 정의하기 위한 함수다
- 첫 번째 인자는 함수 이름
- 두 번째 인자는 인자 목록
- 세 번째 인자는 함수 몸체
- 첫 번째 인자는 함수 이름
- 이제 (AVERAGE 6 8) 처럼 쓸 수 있다
- defun은 매크로 함수로서 그 인자를 평가하지 않는다
- T와 NIL을 제외한 거의 모든 심볼을 인자 이름으로 쓸 수 있다
- 두 수의 평균을 구하는 AVERAGE 함수:
- 변수: 데이터가 저장되는 공간
(defun average (x y) (/ (+ x y) 2.0))
에서 x와 y가 변수
- 변수는 심볼이 아니다
- 심볼을 가지고 변수에 이름을 붙인 것
- 리스프 프로그래머들이 "어떤 변수가 어떤 값으로 평가된다"고 말할 때
- 사실은 "심볼이 그 심볼이 가리키는 변수의 값으로 평가된다"고 말하는 것
- 리스프 프로그래머들이 "어떤 변수가 어떤 값으로 평가된다"고 말할 때
- average란 심볼을 가지고 함수에 이름을 붙인 것
- x, y 변수는 average 함수 안에서만 사용 가능
- 머 흔히 말하는 스코프 개념이겠지
- 머 흔히 말하는 스코프 개념이겠지
- 전역 변수: 어떤 함수와도 엮이지 않은 변수
- 예: PI = 3.14159
- 예: PI = 3.14159
- 값이 할당되지 않은 변수의 값을 요구하면 "미할당 변수 오류(unassigned variable error)"가 발생한다
- "unbound variable error"라고 하는 사람들도 있지만 이건 역사적인 용어라 Common Lisp에 맞지 않음
- 그럼 CAR니 CDR은 왜 쓰는 건데 ㅡㅡ
- Common Lisp에는 EGGPLANT라는 내장 변수가 없으며 EGGPLANT 심볼을 평가하면 오류가 발생한다
- 심볼이 왜 자꾸 문단에선 대문자로 나왔다 코드에선 소문자로 나왔다 하는 거야 뭐가 맞는 거야
- "unbound variable error"라고 하는 사람들도 있지만 이건 역사적인 용어라 Common Lisp에 맞지 않음
- 심볼과 리스트를 데이터로 활용하기
- 심볼 KIRK과 SPOCK이 같은 지 비교하고 싶어요
- (equal kirk spock) 라고 쓰면 되지
- 아니, KIRK이라는 심볼과 SPOCK이라는 심볼 자체를 비교하고 싶다니까요
- 아ㅋ
(equal 'kirk 'spock)
따옴표를 붙여
- T와 NIL은 그 자신으로 평가되기 때문에 따옴표를 붙일 필요가 없다
- T와 NIL은 그 자신으로 평가되기 때문에 따옴표를 붙일 필요가 없다
- Quoted Object의 평가 규칙: 따옴표를 뗀 자신으로 평가된다
- (third (my aunt mary)) => Error! MY undefined function.
(third '(my aunt mary)) => mary
- 그러니까 따옴표는 평가되는 걸 막는다는 뜻인가
- 리스트를 만드는 세 가지 방법
- '(foo bar baz) => (foo bar baz)
- (list 'foo 'bar 'baz) Þ (foo bar baz)
- (cons 'foo '(bar baz)) Þ (foo bar baz)
- '(foo bar baz) => (foo bar baz)
- 이런 건 오류
- (list foo bar baz) => Error! FOO unassigned variable.
- (foo bar baz) => Error! FOO undefined function.
- ('foo 'bar 'baz) => Error! 'FOO undefined function.
- 슬슬 멘붕온다 겁나 헷갈린다
- (list foo bar baz) => Error! FOO unassigned variable.
- 심볼 KIRK과 SPOCK이 같은 지 비교하고 싶어요
- READ-EVAL-PRINT LOOP(REPL)
- 그냥 콘솔에 뭐 치면 읽고 평가하고 출력 많이 하던 거네
- 그냥 콘솔에 뭐 치면 읽고 평가하고 출력 많이 하던 거네
- 리스프 프로그래밍 환경
- 코드 에디터: 괄호 오류 잘 찾아주는 거
- 딱 봐도 괄호 땜에 오류 겁나 쏟아지게 생길 언어네
- 딱 봐도 괄호 땜에 오류 겁나 쏟아지게 생길 언어네
- 코드 에디터: 괄호 오류 잘 찾아주는 거
3장 고급 주제 ¶
- 인자 없는 함수
- 85에 97을 곱하는 함수를 정의하고 싶은데요
(defun test () (* 85 97))
- 85에 97을 곱하는 함수를 정의하고 싶은데요
- 특수 함수 QUOTE
- QUOTE의 인자는 평가되지 않는다
- (quote foo) => foo
- 따옴표랑 같은 거 아냐
- (cons 'up '(down sideways))
- (cons (quote up) (quote (down sideways)))
- (cons 'up '(down sideways))
- QUOTE의 인자는 평가되지 않는다
- 심볼의 내부 구조
- CONS 심볼은 자신의 function cell에 함수 포인터를 가진다
[PNG image (11.92 KB)]
- (EQUAL 3 5)를 cons cell 연쇄로 표현
[PNG image (5.05 KB)]
- 더 자세히 표현하면?
[PNG image (16.98 KB)]
- 내장 함수 SYMBOL-NAME과 SYMBOL-FUNCTION
- CONS 심볼은 자신의 function cell에 함수 포인터를 가진다
- 람다 표기
- 프린스턴 대학의 수학 교수 처치가 창안
- 리스프의 원작자 존 매카시는 처치의 학생이여따
- x + 3을 람다로 표현하면
(lambda (x) (+ 3 x))
- DEFUN이랑 비슷한데?
- LAMBDA는 함수가 아니다 --뭐라구요
- EVAL이 특별 취급하는 마커marker
- LAMBDA는 함수가 아니다 --뭐라구요
- DEFUN의 역할은 이름과 함수를 엮어주는 것
- HALF라는 새로운 함수를 정의할 때
- 문자열 "HALF"는 심볼의 이름
- 심볼 HALF는 함수의 이름
[PNG image (35.53 KB)]
- 문자열 "HALF"는 심볼의 이름
- HALF라는 새로운 함수를 정의할 때
- 프린스턴 대학의 수학 교수 처치가 창안
- 원시 함수 EVAL
- (eval '(+ 2 2)) => 4
- '(list '* 9 6)) => (list '* 9 6)
(eval '(list '* 9 6)) => (* 9 6)
(eval (eval '(list '* 9 6))) => 54
- (eval '(+ 2 2)) => 4
- 원시 함수 APPLY
- 함수와 인자 목록을 인자로 취해서
- 그 인자 목록을 가지고 함수를 호출한다
- (apply #'+ '(2 3)) => 5
- 함수를 다른 함수의 인자로 넘길 때는 '가 아니라 #'를 써야 한다
- 7장에서 알려줄게
- 함수를 다른 함수의 인자로 넘길 때는 '가 아니라 #'를 써야 한다
- 함수와 인자 목록을 인자로 취해서
4장. 조건문 ¶
4장 요약 ¶
- 조건문은 모두 특수 함수 또는 매크로이기 때문에 인자들이 자동 평가되지 않는다
- 3장에서 본 DEFUN, QUOTE 함수도 그런 성질을 가진다
- +, CONS 같은 함수는 항상 인자를 평가한다
- 3장에서 본 DEFUN, QUOTE 함수도 그런 성질을 가진다
- 특수 함수 IF
- 문법:
(if (test) (true-part) (false-part))
- 절댓값 함수:
(defun my-abs (x) (if (< x 0) (- x) x))
- 세 번째 인자 즉 false-part는 생략할 수 있다. 이 경우 NIL로 처리된다.
- true-part에 표현식을 여러 개 쓰고 싶을 때!!! 절대 (expr1 expr2 exprN) 이따구로 쓰지 말 것! 문법 오류
- (true-part) => (progn expr1 expr2 exprN)
- 9장에 PROGN 나오니 참조
- (true-part) => (progn expr1 expr2 exprN)
- 문법:
- COND 매크로
- 간략한 형태
(COND (test-1 consequent-1)
(test-2 consequent-2)
(test-3 consequent-3)
....
(test-n consequent-n))
- test-1이 참이면 consequent-1로 평가됨
- 아니면 test-2를 확인해보고 참이면 consequent-2로 평가됨
- ... 그렇게 test-n까지 반복
- test-n까지 거짓이면 NIL로 평가됨
- (defun compare (x y) (cond ((equal x y) 'numbers-are-the-same) ((< x y) 'first-is-smaller) ((> x y) 'first-is-bigger)))
- test-n에 T를 넣으면 consequent-n이 무조건 실행됨을 보장
- (IF test true-part false-part) = (COND (test true-part) (T false-part))
- 간략한 형태
- AND 매크로와 OR 매크로
- (and clause-1 clause-2 ... clause-n)
- clause-1이 NIL이면 NIL로 종료. 아니면 계속
- clause-2이 NIL이면 NIL로 종료. 아니면 계속...
- clause-n이 NIL이면 NIL로 종료. 아니면 clause-n의 값 반환
- (and 1 2 3 4 5) => 5
- clause-1이 NIL이면 NIL로 종료. 아니면 계속
- (or clause-1 clause-2 ... clause-n)
- clause-1이 NIL이 아니면 clause-1의 값 반환. NIL이면 계속
- clause-2이 NIL이 아니면 clause-2의 값 반환. NIL이면 계속
- ...
- clause-n이 NIL이 아니면 clause-n의 값 반환. NIL이면 NIL 반환
- (or 'george 'fred 'harry) => george
- (or nil 'fred 'harry) => fred
- clause-1이 NIL이 아니면 clause-1의 값 반환. NIL이면 계속
- clause-x에서 평가가 끝나면 그 뒤의 clause들은 평가되지 않는다
- (defun posnump (x) (and (numberp x) (plusp x)))
- (numberp x)가 NIL이면 plusp는 실행되지 않는다
- 만약 부수효과가 있는 코드라면... 신중해야 할 것
- 리스프에서 부수효과를 어떻게 일으키는지 아직은 모르지만
- 참고로 PLUSP는 숫자가 양수인지 확인하는 술어식
- (defun posnump (x) (and (numberp x) (plusp x)))
- (and clause-1 clause-2 ... clause-n)
- 리스프 도구: STEP
- 리스프 표현식의 평가 과정을 단계별로 보여준다. 디버깅용
- 리스프 표현식의 평가 과정을 단계별로 보여준다. 디버깅용
4장 고급 주제 ¶
- 불리언 함수
- (defun logical-and (x y) (and x y t))
- (logical-and 'tweet 'woof) => t
- (and 'tweet 'woof) => woof
- (logical-and 'tweet 'woof) => t
- (defun logical-and (x y) (and x y t))
- 드 모르강의 법칙
- (and x y) = (not (or (not x) (not y)))
- (or x y) = (not (and (not x) (not y)))
- 난 법칙이라고 배웠는데 영어로는 theorem이네... theorem은 정리 아닌감
- T, NIL에 대한 논리 연산일 때만 성립
- (not (not fred))는 fred가 아니라 T다
- (not (not fred))는 fred가 아니라 T다
- (and x y) = (not (or (not x) (not y)))
5장. 변수와 부수효과 ¶
5장 요약 ¶
- 변수는 어떻게 생성되고 그 값은 어떻게 변해가는가?
- '''부수효과side effect: 함수가 값을 반환하는 것 외에도 추가로 취하는 행동action
- 변수의 값을 바꾸는 것은 부수효과의 일종
- 변수의 값을 바꾸는 것은 부수효과의 일종
- 모든 변수는 스코프scope를 가진다
- 함수 몸체 내의 변수들은 지역 변수local variable
- 함수 몸체 내의 변수들은 지역 변수local variable
- 매크로 함수 SETF: 변수에 값을 할당한다
- (setf vowels '(a e i o u))
- (setf vowels '(a e i o u and sometimes y))
- 덮어쓰기 가능. 순수성 같은 건 없다
- (setf vowels '(a e i o u))
- SETF의 부수효과: 변수의 값을 변경한다
- DEFUN의 부수효과: 새로운 함수를 정의한다
- RANDOM의 부수효과: 임의의 값을 반환한다
- (random 5) => 3
- (random 5.0) => 2.78123
- 뭐가 부수효과인데요 => 의사난수 발생기에 사용하는 숨겨진 변수의 값을 변경한다
- 그럼으로써 매번 다른 결과를 반환하는 것
- (random 5) => 3
- SETF는 지역 변수든 전역 변수든 가리지 않는다
- 하지만 전역 변수에만 쓰는 것이 좋은 실천이다 => 왜?
- 하지만 전역 변수에만 쓰는 것이 좋은 실천이다 => 왜?
- 특수 함수 LET
- 지역 변수를 만드는 또다른 방법
(defun average (x y)
(let ((sum (+ x y)))
(list x y ’average ’is (/ sum 2.0))))
- 지역 변수를 만드는 또다른 방법
- 특수 함수 LET*
- 지역 변수들을 한 번에 하나씩 만든다
- 첫 번째 지역 변수가 lexical context를 생성하고 그 context 안에서 두 번째 변수의 값이 계산되고... 머라고요?
- 긴 계산에서 중간 결과들에 이름을 부여하고 싶을 때 유용하다
- 그러니까 LET은 변수 a, b, c를 정의할 때 b, c에서 a를 이용할 수 없고 LET*은 그게 된다는 말인가?
- 지역 변수들을 한 번에 하나씩 만든다
- 그럼 LET*만 쓰면 되지???
- LET을 쓸 수 있으면 LET을 써라
- 변수 사이에 의존성이 없음을 보장
- LET을 쓸 수 있으면 LET을 써라
- 리스프 도구모음: DOCUMENTATION과 APROPOS
- 대부분의 리스프 구현은 모든 내장 함수와 변수의 온라인 문서를 포함한다
- DOCUMENTATION 함수는 문서 문자열을 반환한다
- (documentation ’cons ’function)
- (defun average (x y) "Returns the mean (average value) of its two inputs." (/ (+ x y) 2.0))
- 직접 함수를 정의할 때는 인자 목록 바로 뒤에 써주면 된다
- 아니면 주석comment을 사용하라
- ;로 시작하는 줄은 주석
- 관례: 줄 끝에 쓰면 ;, 함수 안에서 한 줄을 차지하면 ;;, 함수 밖에서는 ;;;
- ;로 시작하는 줄은 주석
- 직접 함수를 정의할 때는 인자 목록 바로 뒤에 써주면 된다
- DOCUMENTATION 함수는 문서 문자열을 반환한다
- APROPOS: 그거 이름이 정확히 뭐더라...
- 특정 문자열을 포함하는 모든 심볼을 반환한다
- (apropos "TOTAL" "USER")
ARRAY-TOTAL-SIZE (function)
ARRAY-TOTAL-SIZE-LIMIT, constant, value: 134217727
- APROPOS의 두 번째 인자는 패키지 이름. 항상 "USER"를 쓸 것
- 패키지는 Common Lisp의 이해하기 어려운 특징들 중 하나. 이 책에선 다루지 않는다
- 특정 문자열을 포함하는 모든 심볼을 반환한다
- 대부분의 리스프 구현은 모든 내장 함수와 변수의 온라인 문서를 포함한다
5장 고급 주제 ¶
- 심볼의 내부 구조
- 심볼의 내부는 다섯 개의 셀로 구성된다
- 전역 변수 TOTAL의 값이 12일 때 TOTAL 심볼의 내부 구조
[PNG image (4.59 KB)]
- 심볼의 내부는 다섯 개의 셀로 구성된다
- T와 NIL의 경우
[PNG image (9.39 KB)]
- 하나의 심볼으로 여러 변수에 이름을 부여할 수 있다
- 하지만 그 중 오직 한 변수만 전역 변수가 될 수 있다
- 값 셀은 그 전역 변수를 위해 예약된다
- 다른 모든 변수들은 local context 안에 있어야 한다
- 그리고 그 변수들의 값은 이 심볼의 값 셀이 아닌 다른 데에 저장되어야 한다
- 어디에 저장될 지는 표준에 정의되어 있지 않다. 그걸 컴파일러 작성자 너님이 해결해야 함 ㅋ
- 하지만 그 중 오직 한 변수만 전역 변수가 될 수 있다
- 근데 심볼에 셀이 5개라고???
- 하나의 심볼에 값도 있고 함수 포인터도 있고
[PNG image (13.7 KB)]
- (CAR '(A B C)) 는 CAR 함수를 호출한다
- (LIST 'A 'NEW CAR) 는 전역 변수 CAR를 참조한다
- 하나의 심볼에 값도 있고 함수 포인터도 있고
- 바인딩, scoping, 할당
- Common Lisp는 오래된 리스프 방언들로부터 진화해왔기 때문에 역사적인 이유로 용어를 혼용한다
- 바인딩: 새로운 변수를 생성하고 값을 주는 과정
- 함수의 인자 목록에 변수가 나타나면: 람다 바인딩
- LET이나 LET*의 변수 목록에 나타나면: let 바인딩
- 함수의 인자 목록에 변수가 나타나면: 람다 바인딩
- 그런데 예전부터 리스프를 써온 사람들은 좀 다르게 말한다...
- variables are lexically scoped by default 이걸 뭐라고 번역해야 매끄러우려나 ㅡㅡ
- Common Lisp는 동적 바인딩도 지원한다
- 초기 리스프 방언들은 대개 동적 바인딩이 기본이였다
- dynamically scoped variable들은 "bound"가 반드시 값을 가졌다는 뜻이 아니다
- variables are lexically scoped by default 이걸 뭐라고 번역해야 매끄러우려나 ㅡㅡ
- Common Lisp는 오래된 리스프 방언들로부터 진화해왔기 때문에 역사적인 이유로 용어를 혼용한다
6장. 리스트 자료구조 ¶
6장 요약 ¶
- 리스프는 연결 리스트, 심볼릭 자료구조, 저장공간 할당기 등을 기본적으로 제공
- 리스트는 일방통행 연쇄다
- (cons 'w '(x y z)) => (w x y z)
- (cons '(a b c) 'd) => ((a b c) . d)
- (cons 'w '(x y z)) => (w x y z)
- 리스트 연결 함수 APPEND
- (append '(friends romans) '(and countrymen))
=> (FRIENDS ROMANS AND COUNTRYMEN)
- (append '(friends romans) '(and countrymen))
- CONS, LIST, APPEND의 차이점
- CONS는 새로운 cons cell을 생성한다. 리스트의 머리에 새 원소를 추가할 때 쓰인다
- LIST는 임의 개수의 입력을 받아서 NIL로 끝나는 cons cell 연쇄를 만든다. 각 cell의 car는 대응하는 입력을 가리킨다.
- APPEND는 첫 번째 입력을 복사해서 마지막 셀의 cdr이 두 번째 입력을 가리키도록 한다. 첫 번째 입력값이 리스트가 아니면 오류.
- CONS는 새로운 cons cell을 생성한다. 리스트의 머리에 새 원소를 추가할 때 쓰인다
- 지금까지 본 리스트 함수들: CONS, LIST, APPEND, LENGTH
- 아직 많이 남았다: REVERSE, NTH, NTHCDR, LAST, REMOVE
- REVERSE: 원래 리스트는 놔두고 뒤집힌 복사본을 반환한다
- NTHCDR: n번째 CDR
- (nthcdr 0 ’(a b c)) => (a b c)
- (nthcdr 2 ’(a b c)) => (c)
- (nthcdr 3 ’(a b c)) => nil
- (nthcdr 5 ’(a b c)) => nil ; 리스트 길이 넘어가도 오류 아님
- 단 dotted list인 경우엔 오류
- 단 dotted list인 경우엔 오류
- (nthcdr 0 ’(a b c)) => (a b c)
- NTH:
(defun nth (n x) "Returns the Nth element of the list X, counting from 0." (car (nthcdr n x)))
- LAST: 마지막 cons cell을 반환한다
- (last '(all is forgiven)) => (forgiven)
- (last '(a b c . d)) => (c . d)
- (last '(all is forgiven)) => (forgiven)
- REMOVE: 일치하는 모든 항목 제거
- (remove 'a '(b a n a n a)) => (b n n)
- 원래 리스트는 놔두고 수정된 사본을 반환한다
- (remove 'a '(b a n a n a)) => (b n n)
- REVERSE: 원래 리스트는 놔두고 뒤집힌 복사본을 반환한다
- 집합(set)으로서의 리스트
- 집합에서는 어떤 원소는 최대 한 번만 출현할 수 있다
- 가능한 연산: 원소 여부, 합집합, 교집합, 차집합, 부분집합 여부
- MEMBER: 일치하는 원소로 시작하는 부분 리스트 반환. 없으면 NIL 반환.
(defun beforep (x y l) "Returns true if X appears before Y in L" (member y (member x l)))
- INTERSECTION: 교집합
- UNION: 합집합
- SET-DIFFERENCE: 차집합
- SUBSETP: 부분집합인가?
(subsetp '(a i) '(a e i o u)) => t
- 집합에서는 어떤 원소는 최대 한 번만 출현할 수 있다
- 테이블로서의 리스트
- ASSOC 함수
- (setf words '((one un) (two deux) (three trois) (four quatre) (five cinq)))
- (assoc 'three words) => (three trois)
- (setf words '((one un) (two deux) (three trois) (four quatre) (five cinq)))
- RASSOC 함수
- 테이블이 dotted pair의 리스트여야 한다
- (setf sounds '((cow . moo) (pig . oink) (cat . meow) (dog . woof) (bird . tweet)))
- (rassoc 'woof sounds) => (dog . woof)
- (assoc 'dog sounds) => (dog . woof)
- 테이블이 dotted pair의 리스트여야 한다
- ASSOC 함수
6장 고급 주제 ¶
- 트리
- SUBST 함수
- (subst x y z) => z 안에 있는 모든 y를 x로 대체한다
- 리스트를 재귀적으로 탐색, 모든 y를 x로 대체
- (subst x y z) => z 안에 있는 모든 y를 x로 대체한다
- SUBLIS 함수
- 여러 SUBST를 한꺼번에
- (sublis '((roses . violets) (red . blue)) '(roses are red))
=> (VIOLETS ARE BLUE)
- 여러 SUBST를 한꺼번에
- SUBST 함수
- 공유 구조(shared structure)
- 두 리스트가 하나의 하위 구조를 공유한다
- 콘솔에서는 항상 리스트의 복사본을 만들기 때문에 이런 거 못 만든다
- CAR, CDR, CONS로 생성 가능
- (setf x ’(a b c))
(setf y (cons ’d (cdr x)))
[PNG image (9.72 KB)]
- 두 리스트가 하나의 하위 구조를 공유한다
- 항등 검사
- EQUAL은 두 리스트의 모든 원소를 짝지어본다 (값의 비교)
- EQ는 두 리스트가 같은 주소를 참조하는지를 본다 (참조의 비교)
- EQL은 EQ와 동일, 단 숫자형의 경우 그 값을 비교
- 타입이 다르면 같지 않은 걸로 처리 (eql 3 3.0) => nil Different types.
- 타입이 다르면 같지 않은 걸로 처리 (eql 3 3.0) => nil Different types.
- =는 숫자 비교할 때만 사용
- EQUALP는 EQUAL과 동일하나 대소문자를 구분하지 않음 (equalp "foo bar" "Foo BAR") => t
- 아 이게 다 뭐야 ㅡㅡ
- EQUAL은 두 리스트의 모든 원소를 짝지어본다 (값의 비교)
- 키워드 인자
- 많은 리스프 함수가 키워드 인자keyword argument라는 추가 인자를 가진다
- REMOVE의 기본 동작은 발견된 모든 원소 삭제
- 몇 개를 삭제할 지를 지정할 수 있다
(setf text '(b a n a n a - p a n d a))
(remove 'a text :count 3)
- 뒤에서부터 지우는 것도 가능
(remove 'a text :count 2 :from-end t)
- 몇 개를 삭제할 지를 지정할 수 있다
- 키워드는 항상 :가 붙는 특별한 심볼
- COUNT와 :COUNT는 별개의 심볼
- 키워드는 항상 자기 자신으로 평가된다
- KEYWORDP: 입력이 키워드인가?
- COUNT와 :COUNT는 별개의 심볼
- MEMBER는 리스트에서 원소를 찾을 때 EQL을 사용
- (setf cards '((3 clubs) (5 diamonds) (ace spades)))
- (member ’(5 diamonds) cards) => nil 으잉 ㅜㅜ
- (member ’(5 diamonds) cards :test #’equal)
=> ((5 DIAMONDS) (ACE SPADES))
- :TEST 키워드를 허용하는 함수들: UNION, INTERSECTION, SET-DIFFERENCE, ASSOC, RASSOC, SUBST, SUBLIS
- (setf cards '((3 clubs) (5 diamonds) (ace spades)))
- 많은 리스프 함수가 키워드 인자keyword argument라는 추가 인자를 가진다
7장. Applicative Programming ¶
7장 요약 ¶
- Applicative를 뭐라고 번역해야 할까? 하스켈의 Applicative Functor도 이 applicative에서 나온 용어인데 '적용성 작용자'쯤으로 옮기면 의미가 얼추 맞지만 applicative functor나 적용성 작용자나 처음 보는 사람이 이해할 리가.. -.- 일단 '함수를 apply하다'에서 나온 말인데 이 apply는 '응용하다'가 아닌 '뭐를 뭐에 적용하다'로 쓰인 것이다.
- 프로그래밍의 세 가지 유형: applicative, recursion, iteration
- applicative programming의 기본 발상: 심볼은 데이터. 리스트는 데이터. 그러면 함수도 데이터.
- 함수를 다른 함수에 인자로 전달할 수 있다
- 함수가 어떤 함수를 반환할 수 있다
- 원시 함수 FUNCALL
(funcall #’cons 'a 'b) => (a . b)
- 일반 함수(ordinary function)를 quote하려면 #'를 붙인다
- 매크로 함수, 특수 함수는 불가함
- 매크로 함수, 특수 함수는 불가함
- MAPCAR 연산자: 리스트의 각 원소에 함수를 적용하고 그 결과들을 반환
- 어떤 언어에든 흔히 있는 배열용 map 함수
- (defun square (n) (* n n))
(mapcar #’square '(1 2 3 4 5))
(mapcar #’square '()) => nil
- 람다 버전:
(mapcar #'(lambda (n) (* n n)) '(1 2 3 4 5))
- 람다는 매크로 함수나 특수 함수가 아니라 이름 없는 일반 함수
- 람다는 매크로 함수나 특수 함수가 아니라 이름 없는 일반 함수
- 어떤 언어에든 흔히 있는 배열용 map 함수
- FIND-IF 연산자: 조건 맞는 첫 번째 원소
(find-if #'oddp '(2 4 6 7 8 9))
- REMOVE-IF: 조건 맞는 모든 원소 제거하고 남은 리스트
- (remove-if #'numberp '(2 for 1 sale)) => (2 1)
- (remove-if #'numberp '(2 for 1 sale)) => (2 1)
- REMOVE-IF-NOT: 조건 맞는 원소들만 모아서 반환
- REDUCE 연산자: 하스켈의 fold
- (reduce #’+ '(1 2 3)) => 6
- (reduce #’+ '(1 2 3)) => 6
- EVERY: 모든 원소가 조건에 맞는가? (every #'numberp '(1 2 3 4 5))
- 리스프 도구모음: TRACE와 DTRACE
- 함수를 다른 함수에 인자로 전달할 수 있다
7장 고급 주제 ¶
- MAPCAR는 여러 리스트를 한꺼번에 처리할 수 있다
- 리스트 길이가 다르면? 제일 짧은 리스트가 끝날 때 짤라먹음
- 리스트 길이가 다르면? 제일 짧은 리스트가 끝날 때 짤라먹음
- 특수 함수 FUNCTION: '가 특수 함수 QUOTE의 단축 표현이듯 사실 #'는 FUNCTION의 단축 표현
- QUOTE는 인자를 평가하지 않고 반환한다
- FUNCTION은 인자의 함수 측면에서의 해석 결과를 반환한다
- 인자가 심볼이면 심볼의 함수 셀의 내용물(컴파일된 코드 객체)
- 인자가 람다면 lexical closure
- 인자가 심볼이면 심볼의 함수 셀의 내용물(컴파일된 코드 객체)
- QUOTE는 인자를 평가하지 않고 반환한다
- applicative 연산자의 키워드 인자
- (find-if #'oddp '(2 3 4 5 6) :from-end t)
- (reduce #'cons '(a b c d e)) => ((((A . . C) . D) . E)
- (reduce #'cons '(a b c d e) :from-end t) => (A B C D . E)
- (find-if #'oddp '(2 3 4 5 6) :from-end t)
- 함수를 반환하는 함수
- (defun make-greater-than-predicate (n) #'(lambda (x) (> x n)))
- (defun make-greater-than-predicate (n) #'(lambda (x) (> x n)))
8장. 재귀 ¶
8장 요약 ¶
- 주요 주제: 재귀적인 제어 구조
- 하나라도 홀수인가?
(defun anyoddp (x) (cond ((null x) nil) ((oddp (first x)) t) (t (anyoddp (rest x)))))
- 너무 코딩 안 해보고 책만 읽는 것 같다. 코드 안 보고 팩토리얼 짜보자
- 성공은 했는데... 괄호 개많어 ㅡㅡ
- 성공은 했는데... 괄호 개많어 ㅡㅡ
(defun factorial (n) (if (zerop n) 1 (* n (factorial (- n 1)))))
- 재귀 템플릿: 너의 재귀 패턴은 강약강약약중강약이다
- Double-Test Tail Recursion
- Double-Test Tail Recursion
(DEFUN func (X) (COND (end-test-1 end-value-1) (end-test-2 end-value-2) (T (func reduced-x))))
- Single-Test Tail Recursion
(DEFUN func (X) (COND (end-test end-value) (T (func reduced-x))))
- Augmenting Recursion
- f(n)에서 f(n-1)에 모종의 연산을 적용
- f(n)에서 f(n-1)에 모종의 연산을 적용
(DEFUN func (X) (COND (end-test end-value) (T (aug-fun aug-val (func reduced-x)))))
- 트리와 CAR/CDR 재귀
- 중첩 리스트의 모든 원소 처리하기
[PNG image (11.44 KB)]
- 중첩 리스트의 모든 원소 처리하기
(defun find-number (x) (cond ((numberp x) x) ((atom x) nil) (t (or (find-number (car x)) (find-number (cdr x))))))
8장 고급 주제 ¶
- 꼬리 재귀의 이점
- 다른 재귀보다 효율적으로 실행
- 다른 재귀보다 효율적으로 실행
- 특수 함수 LABELS
- LET은 지역 변수
- LABELS는 지역 함수
- LET은 지역 변수
(LABELS ((fn-1 args-1 body-1) ... (fn-n args-2 body-2)) body)
- 재귀 자료 구조
- S-표현식(symbolic expression): 아톰, 또는 CAR와 CDR이 S-표현식인 cons cell
- 트리는 단일 말단 노드이거나 가지들이 트리인 비단말 노드다.
- S-표현식(symbolic expression): 아톰, 또는 CAR와 CDR이 S-표현식인 cons cell
9장. 입출력 ¶
9장 요약 ¶
- 리스프에선 입출력 인터페이스가 아직도 표준화가 안 됐다
- 그나마 아주 기본적인 입출력 루틴들은 표준화가 되었음
- 그나마 아주 기본적인 입출력 루틴들은 표준화가 되었음
- 문자열(character string): vector의 subtype
- 문자열은 그 자신으로 평가된다
- STRINGP: 입력이 문자열인가?
- 문자열은 그 자신으로 평가된다
- FORMAT 함수: NIL을 반환. 하지만 부수효과로 디스플레이나 파일에 뭔가를 출력
- 화면에 출력하려면 첫 번째 인자는 T, 다른 데 출력하려면 여러 가지가 있음
- 두 번째 인자는 format control string (C의 printf의 인자와 비슷해보인다)
~%
는 printf의\n
~&
는 새로운 줄이 아닐 때만 새로운 줄을 시작
~S
는 리스프 객체의 문자열 표현 (FORMAT 함수의 나머지 인자들을 채워줘야 한다)
~A
는 ~S에서 이스케이프 문자를 떼어내고 출력
- 화면에 출력하려면 첫 번째 인자는 T, 다른 데 출력하려면 여러 가지가 있음
- READ 함수: 키보드 입력 -> 리스프 객체로 변환
(defun my-square () (format t "Please type in a number: ") (let ((x (read))) (format t "The number ~S squared is ~S.~%" x (* x x))))
- YES-OR-NO-P 함수: format control string을 입력으로 취해 화면에 출력한다. 사용자는 반드시 yes 또는 no로 답해야 한다.
(defun riddle () (if (yes-or-no-p "Do you seek Zen enlightenment? ") (format t "Then do not ask for it!") (format t "You have found it.")))
- Y-OR-N-P 함수도 있음: y 또는 n으로 답해야 한다
- 파일 읽기
- WITH-OPEN-FILE 매크로
- (WITH-OPEN-FILE (var pathname) body)
- 지역 변수를 하나 만들고 파일에 대한 연결을 나타내는 stream object로 설정한다
- stream object는 파일 연결을 나타내기 위한 리스프의 특수 데이터타입
- 전역 변수
*TERMINAL-IO*
를 한 번 볼 것. 이게 바로 stream object의 일종
- READ의 추가 인자로 stream object를 넘겨 파일에서 데이터를 읽을 수 있다
- WITH-OPEN-FILE을 벗어나면 파일 연결이 자동으로 닫힌다
- (WITH-OPEN-FILE (var pathname) body)
- WITH-OPEN-FILE 매크로
- 파일 쓰기
- WITH-OPEN-FILE에 특수 키워드 인자 :DIRECTION :OUTPUT을 추가
- WITH-OPEN-FILE에 특수 키워드 인자 :DIRECTION :OUTPUT을 추가
- 리스프 도구모음: DRIBBLE 함수
- 리스프 세션의 일부를 파일에 기록한다
- 리스프 세션의 일부를 파일에 기록한다
9장 고급 주제 ¶
- FORMAT 지시문의 매개변수
- ~S 지시문은 width 매개변수를 가진다
- ~D는 정수를 십진 표기로 출력한다
- ~F는 부동소수점 수를 출력
- ~7,5F는 정수 부분 7자리, 소수 부분 5자리
- ~7,5F는 정수 부분 7자리, 소수 부분 5자리
- ~S 지시문은 width 매개변수를 가진다
- 리스프 1.5의 원시 입출력 함수들: TERPRI, PRIN1, PRINC, PRINT
- 현대의 커먼 리스프까지 이어져왔다
- 하지만 FORMAT으로 같은 작업을 할 수 있다
- TERPRI: 커서를 새 줄로 옮긴다
- PRIN1: 리스프 객체를 터미널에 출력한다. 이스케이프 문자 보존
- PRINC: 리스프 객체를 터미널에 출력한다. 이스케이프 문자 버림
- PRINT는 이것과 같이 작동한다
- 현대의 커먼 리스프까지 이어져왔다
(defun my-print (x) (terpri) (prin1 x) (princ " ") x)
- EOF 처리
- 입력의 끝에 다다랐는데 읽기 하면 End of File 오류
- READ가 오류 대신 특별한 값을 반환하도록 지정할 수 있다
- 입력의 끝에 다다랐는데 읽기 하면 End of File 오류
(defun read-my-file () (with-open-file (stream "/usr/dst/sample-file") (let ((contents (read-all-objects stream (list ’$eof$)))) (format t "~&Read ~S objects from the file." (length contents)) contents))) (defun read-all-objects (stream eof-indicator) (let ((result (read stream nil eof-indicator))) (if (eq result eof-indicator) nil (cons result (read-all-objects stream)))))
10장. 할당 ¶
10장 요약 ¶
- 5장에서 이르기를.. 변수의 값을 변경하려면 SETF 매크로를 쓰라 하였더라
- 이를 할당(assignment)라고 부른다
- 지금까지는 전역 스코프에서만 썼는데 함수 안에서 SETF를 쓰려면?
- 할당은 자주 오용되어 함수를 이해하고 디버깅하기 어렵게 만든다
- 그냥 함수형 프로그래밍을 강조하는 거겠지.. 난 할당 난무해도 상관 없음 ㅋ
- 그냥 함수형 프로그래밍을 강조하는 거겠지.. 난 할당 난무해도 상관 없음 ㅋ
- 전역 변수의 갱신
- 레모네이드를 팔고 있는데 몇 잔 팔았는지 알고 싶어
- 판매량을 전역 변수로 표현
(setf *total-glasses* 0)
- 전역 변수 이름 양끝에 *를 붙인 건 그냥 관례. 특별한 의미는 없다
- 판매량을 전역 변수로 표현
- 레모네이드를 팔 때마다
*total-glasses*
를 갱신
- 레모네이드를 팔고 있는데 몇 잔 팔았는지 알고 싶어
(defun sell (n) "Ye Olde Lemonade Stand: Sales by the Glass." (setf *total-glasses* (+ *total-glasses* n)) (format t "~&That makes ~S glasses so far today." *total-glasses*))
- 전형적인 갱신 방법들
- SETF는 임의의 변수에 어떤 값이라도 할당 가능하다
- 할당의 흔한 용례가 변수를 갱신하는 것(원래 값을 이용해서 새로운 값을 계산)
- INCF 매크로와 DECF 매크로: 숫자형 변수를 편하게 갱신
(SETF A (+ A 5))
=>(INCF A 5)
- DECF는 빼기. INCF와 DECF는 두 번째 인자를 생략하면 1을 덧셈/뺄셈
- PUSH 매크로와 POP 매크로: 리스트를 스택으로 취급해서 맨 처음 위치에 삽입/제거
(SETF X (CONS 'FOO X))
=>(PUSH 'FOO X)
- PUSH와 POP은 다른 할당 형태들과 일관성을 지키려면 PUSHF와 POPF가 됐어야 하지만 SETF보다 먼저 발명됨
- SETF는 "set field"를 뜻한다
- SETF는 임의의 변수에 어떤 값이라도 할당 가능하다
- 지역 변수의 갱신
- 할당을 무분별하게 쓰면 안돼요
- 지역 변수의 값을 변경하느니 LET으로 새 지역 변수를 바인딩하라
- 함수의 인자 목록에 있는 변수를 수정하는 것도 좀..
- 그래도 잘 쓰면 괜찮다
- 할당을 무분별하게 쓰면 안돼요
(defun get-name () (let ((last-name nil) (first-name nil) (middle-name nil) (title nil)) (format t "~&Last name? ") (setf last-name (read)) (format t "~&First name? ") (setf first-name (read)) (format t "~&Middle name or initial? ") (setf middle-name (read)) (format t "~&Preferred title? ") (setf title (read)) (list title first-name middle-name last-name))) > (get-name) Last name? higginbotham First name? waldo ; <- 왈도? Middle name or initial? j Preferred title? admiral (ADMIRAL WALDO J HIGGINBOTHAM)
- WHEN과 UNLESS
- (WHEN test body)
test
가 NIL이면 NIL 반환. NIL이 아니면body
를 평가하고 마지막 값을 반환
- UNLESS는
test
판정이 WHEN과 반대
- body에서 마지막 것을 제외한 원소들은 부수효과 용도(입출력이나 할당)
- COND와 비슷.. 그냥 뭐가 언제 코드를 더 깔끔하게 만드냐의 문제
- (WHEN test body)
- 일반화 변수Generalized Variable
- 일반화 변수? 포인터가 저장될 수 있는 임의의 장소
- N이나 X 같은 통상의 변수들은 객체에 대한 포인터를 저장한다
- 즉 'N의 값이 3이다'는 'N이라는 이름의 변수가 숫자 3에 대한 포인터를 가지고 있다'는 뜻
(INCF N)
은 그 포인터를 4를 가리키는 포인터로 치환한다
- 즉 'N의 값이 3이다'는 'N이라는 이름의 변수가 숫자 3에 대한 포인터를 가지고 있다'는 뜻
- 할당 매크로들(SETF, INCF, DECF, PUSH, POP)은 다양한 장소에 포인터를 저장할 수 있다
- 일반화 변수? 포인터가 저장될 수 있는 임의의 장소
(setf x ’(jack benny was 39 for many years)) (setf (sixth x) ’several) > x (JACK BENNY WAS 39 FOR SEVERAL YEARS) > (decf (fourth x) 2) 37 > x (JACK BENNY WAS 37 FOR SEVERAL YEARS)
- 리스프 도구모음: BREAK 함수와 ERROR 함수
- 첫 번째 인자는 format control string
- 나머지 인자는 format directive들의 인자
- 첫 번째 인자는 format control string
10장 고급 주제 ¶
- 손수 해보는 리스트 수술
(defun snip (x) (setf (cdr x) (cdr (cdr x)))) > (setf a ’(no down payment)) (NO DOWN PAYMENT) > (setf b (cdr a)) (DOWN PAYMENT) > (snip a) (PAYMENT) > a (NO PAYMENT) > b (DOWN PAYMENT)
[PNG image (19.74 KB)]
- 리스트의 cons cell의 포인터를 직접 조작하는 걸 리스트 수술list surgery라고 한다
- 파괴적인 리스트 연산: cons cell의 내용을 조작하는 연산
- 관례에 따라 함수 이름이 N으로 시작
- NCONC: APPEND의 파괴적 버전
- APPEND는 새로운 리스트를 만들지만 NCONC는 입력된 리스트 자체를 수정한다
- 첫 번째 인자가 NIL이면 그냥 두 번째 인자를 반환한다... 주의
- APPEND는 새로운 리스트를 만들지만 NCONC는 입력된 리스트 자체를 수정한다
> (setf x nil) NIL > (setf y '(no luck today)) (NO LUCK TODAY) > (nconc x y) (NO LUCK TODAY) > x ; 주의!!! NIL > (setf x (nconc x y)) ; 이렇게 써야 안전 ㅋ (NO LUCK TODAY)
- 사실 NCONC는 임의 개수의 입력을 취한다
- NSUBST: SUBST의 파괴적 버전
- destructive update를 파괴적 갱신라고 하니까 좀 어색한데.. 좋은 단어 없나
- destructive update를 파괴적 갱신라고 하니까 좀 어색한데.. 좋은 단어 없나
- 그 외에 NREVERSE, NUNION, NINTERSECTION, NSET-DIFFERENCE...
- APPEND, REMOVE의 대응은 NCONC, DELETE
- N-관례가 생기기 전에 지은 이름이라.. 이 둘은 작명 규칙을 따르지 않는다
- N은 "noncopying" 혹은 "nonconsing"을 뜻한다
- N-관례가 생기기 전에 지은 이름이라.. 이 둘은 작명 규칙을 따르지 않는다
- SETQ
- SETF와 일반화 변수가 생기기 전에는 SETQ라는 할당 함수를 썼다
- 근데 아직도 있다. 제일 싫음 구버전 신버전 API가 공존하는 거 ㅡㅡ 겁나 헷갈려 deprecated로 처리하던가 아예 삭제해줘!!
- SETQ의 사용법은 SETF와 같다. 단 일반화 변수는 사용 불가
- 오늘날의 리스프 프로그래머들은 모든 할당에 SETF를 사용한다
- 리스프 구현체들은 가능한 경우 대개 내부적으로 SETF를 SETQ로 바꾼다
- SETF와 일반화 변수가 생기기 전에는 SETQ라는 할당 함수를 썼다
- SET
- 해당 심볼의 value cell에 값을 저장한다. 즉 전역 변수에 값을 할당한다
- 해당 심볼의 value cell에 값을 저장한다. 즉 전역 변수에 값을 할당한다
11장. 반복과 블록 구문 ¶
11장 요약 ¶
- 반복에 쓰이는 것들: DO, DO*, DOTIMES, DOLIST
- DOTIMES와 DOLIST는 매크로 함수여서 인자들을 평가하지 않는다
(DOTIMES (index-var n [result-form]) body)
- index-var가 0에서 n-1까지
- index-var가 0에서 n-1까지
(DOLIST (index-var list [result-form]) body)
- result-form은 반환할 값. 생략하면 NIL 반환
(dolist (x '(red blue green) 'flowers) (format t "~&Roses are ~S." x))
- result-form은 반환할 값. 생략하면 NIL 반환
- 루프를 RETURN으로 빠져나올 수 있다
- return으로 빠져나올 경우 result-form은 무시된다
- return으로 빠져나올 경우 result-form은 무시된다
(defun find-first-odd (list-of-numbers) (dolist (e list-of-numbers) (format t "~&Testing ~S..." e) (when (oddp e) (format t "found an odd number.") (return e))))
- DO 매크로
- 먼저 var1, var2...을 선언하고 그 값을 init1, init2...로 초기화한다
- test를 평가한다. 참이면 action-1 ... action-n을 평가하고 action-n의 값을 반환한다
- 거짓이면 body를 평가한다
- 먼저 var1, var2...을 선언하고 그 값을 init1, init2...로 초기화한다
(DO ((var1 init1 [update1]) (var2 init2 [update2]) ...) (test action-1 ... action-n) body)
(defun launch (n) (do ((cnt n (- cnt 1))) ((zerop cnt) (format t "Blast off!")) (format t "~S..." cnt)))
- update_i 부분에서 변수 갱신을 할 수 있어서 SETF를 쓸 필요가 없다
- DO* 매크로
- LET*처럼 변수들을 순차적으로 갱신한다
- LET*처럼 변수들을 순차적으로 갱신한다
- DO를 이용한 무한루프
- test에 NIL을 넣고 body에서 필요할 때 RETURN
- test에 NIL을 넣고 body에서 필요할 때 RETURN
- 암묵적 블록implicit block
- 함수 몸체는 암묵적인 블록에 포함되고 함수 이름이 그 블록의 이름이 된다
- 블록은 일련의 표현식으로서 임의의 시점에 특수 함수 RETURN-FROM으로 종료할 수 있다
- RETURN-FROM block-name result-expression
- block-name은 평가되지 않으므로 '를 붙이지 말 것
- block-name은 평가되지 않으므로 '를 붙이지 말 것
- DOTIMES, DOLIST, DO, DO*의 블록 이름은 NIL이다
- 사실
RETURN x
는RETURN-FROM NIL x
이다
- 사실
- 함수 몸체는 암묵적인 블록에 포함되고 함수 이름이 그 블록의 이름이 된다
(defun find-first-odd (x) (format t "~&Searching for an odd number...") (dolist (element x) (when (oddp element) (format t "~&Found ~S." element) (return-from find-first-odd element))) (format t "~&None found.") 'none)
- 리스프 도구모음: TIME
- 함수 성능 측정하는 프로파일링 함수
- 함수 성능 측정하는 프로파일링 함수
11장 고급 주제 ¶
- PROG1, PROG2, PROGN
- 추가 인자optional argument
- 함수는 키워드 인자나 임의 개수의 인자를 취할 수 있다
- 인자 목록에 람다-리스트 키워드lambda-list keyword라는 특수 심볼을 추가해야 한다
- 함수는 키워드 인자나 임의 개수의 인자를 취할 수 있다
(defun foo (x &optional y) (format t "~&X is ~S" x) (format t "~&Y is ~S" y) (list x y)) > (foo 3 5) X is 3 Y is 5 (3 5) > (foo 4) X is 4 Y is NIL ; 기본값은 NIL (4 NIL)
- 기본값을 지정할 수 있다
(defun divide-check (dividend &optional (divisor 2)) ; divisor라고 쓰는 대신 (divisor 2)로 묶어줌 (format t "~&~S ~A divide evenly by ~S" dividend (if (zerop (rem dividend divisor)) "does" "does not") divisor))
- 나머지 인자rest argument
- 람다-리스트 키워드인 &REST 다음에 오는 변수는 나머지 인자들의 목록에 바인딩된다
- 재귀 함수에서 쓸 때 조심!! 리스트의 리스트가 생김.. APPLY를 쓸 것
- 람다-리스트 키워드인 &REST 다음에 오는 변수는 나머지 인자들의 목록에 바인딩된다
(defun average (&rest args) (/ (reduce #’+ args) (length args) 1.0))
- 키워드 인자
- 람다-리스트 키워드인
&key
를 사용한다
- 키워드는 그 자신으로 평가되므로 호출할 때 '를 붙이지 않는다
- 하지만 인자 목록과 함수 몸체 안에서는 일반 심볼을 쓴다
- 아래 함수에서 MAKE-SUNDAE의 내부에서 CHERRIES는 하나의 변수일 뿐이다
- 람다-리스트 키워드인
(defun make-sundae (name &key (size 'regular) (ice-cream 'vanilla) (syrup 'hot-fudge) nuts cherries whipped-cream) (...body...))
- 보조 변수auxiliary variable
- 람다-리스트 키워드
&aux
- 특수 함수 *LET과 동일 기능. 코드 깔끔한 걸로...
- 람다-리스트 키워드
(defun average (&rest args &aux (len (length args))) (/ (reduce #’+ args) len 1.0))
12장. 구조체와 타입 시스템 ¶
12장 요약 ¶
- 커먼 리스프에는 많은 자료형이 내장되어 있고 이것들이 타입 시스템을 형성한다
- 지금까지 본 것들? 숫자형, 심볼, cons, 문자열, 함수 객체, 스트림 객체
- 타입 시스템의 중요한 성질 두 가지
- 1. 가시적이다. 타입이.. 타입이 보여요!
- 2. 확장가능하다. 구조체는 프로그래머가 정의한 자료형의 한 예시
- 1. 가시적이다. 타입이.. 타입이 보여요!
- Common Lisp Object System(CLOS)는 객체지향 프로그래밍을 지원한다. 이 책에서는 안 다루니까 다른 거 찾아보세요
- TYPEP: 객체가 해당 타입의 것인가?
(typep 3 'number) => t
(typep 3 'integer) => t
(typep 3 'float) => nil
(typep 'foo 'symbol) => t
- TYPE-OF: 객체의 타입 반환
- (type-of 'aardvark) => symbol
- (type-of 3.5) => short-float
- (type-of '(bat breath)) => cons
- (type-of "Phooey") => (simple-string 6)
- 정확한 반환값은 구현에 따라 다를 수 있어용
- (type-of 'aardvark) => symbol
[PNG image (27.28 KB)]
- 구조체 정의하기: DEFSTRUCT 매크로
(defstruct starship (name nil) (speed 0) (condition 'green) (shields 'down))
- 생성자 함수 MAKE-STARSHIP이 자동으로 생성된다
- #S 표기는 구조체를 표시하는 표준적인 방법
- 구조체는 리스트가 아니여
- #S 표기는 구조체를 표시하는 표준적인 방법
> (setf s1 (make-starship)) #S(STARSHIP NAME NIL SPEED 0 CONDITION GREEN SHIELDS DOWN)
- STARSHIP 객체를 바로 작성할 수도 있다
> (setf s2 '#s(starship speed (warp 3) condition red shields up))
- 타입 술어식도 자동으로 생긴다
- (starship-p s2) => t
- (starship-p 'foo) => nil
- (starship-p s2) => t
- 접근자 함수도 생긴다
- (starship-speed s2) => (warp 3)
- (starship-shields s2) => up
- (starship-speed s2) => (warp 3)
- 구조체 생성할 때 구성성분 값 설정하기
- (setf s3 (make-starship :name "Reliant" :shields 'damaged))
- (setf s3 (make-starship :name "Reliant" :shields 'damaged))
- 리스프 도구모음: DESCRIBE와 INSPECT
- DESCRIBE: 임의의 리스프 객체를 취해 설명해준다
- INSPECT: 내부 구조를 보여준다
- DESCRIBE: 임의의 리스프 객체를 취해 설명해준다
12장 고급 주제 ¶
- 구조체를 입맛대로 출력하기
- 출력할 객체
- 어느 스트림에 출력?
- 중첩 깊이 제한
- 출력할 객체
(defun print-starship (x stream depth) (format stream "#<STARSHIP ~A>" (starship-name x))) (defstruct (starship (:print-function print-starship)) ; 여기가 중요!! (captain nil) (name nil) (shields ’down) (condition ’green) (speed 0))
- 구조체의 항등 비교
- 별개의 두 구조체의 구성요소 값이 모두 동일할 때
- EQUAL은 NIL
- EQUALP는 T
- EQUAL은 NIL
- 별개의 두 구조체의 구성요소 값이 모두 동일할 때
- 구조체의 상속
- STARSHIP은 name, captain, crew-size, weapons, shields를 구성요소로 가진다
- STARSHIP은 name, captain, crew-size, weapons, shields를 구성요소로 가진다
(defstruct ship (name nil) (captain nil) (crew-size nil)) (defstruct (starship (:include ship)) (weapons nil) (shields nil))
13장. 배열, 해시 테이블, property list ¶
13장 요약 ¶
- property list는 리스프 1.5 시절에 쓰던 거.. 지금은 거진 해시 테이블로 대체되었다
- 배열
- 1차원 배열은 벡터vector라고 부른다
- (setf my-vec '#(tuning violin 440 a))
[PNG image (5.82 KB)]
- 음영 있는 부분은 배열 헤더. 배열의 차원, 길이 등의 정보를 보관한다
- 배열 출력하기
- 전역 변수 *PRINT-ARRAY*를 T로 설정해야 내용물이 보인다
- 전역 변수 *PRINT-ARRAY*를 T로 설정해야 내용물이 보인다
- 1차원 배열은 벡터vector라고 부른다
> (setf *print-array* nil) NIL > my-vec #<Vector {204844}> > (setf *print-array* t) T > my-vec #(TUNING VIOLIN 440 A)
- 배열 원소에 접근하기: AREF
- (aref my-vec 1) => VIOLIN
- (aref my-vec 1) => VIOLIN
- 배열 원소 수정하기: SETF + AREF
- (setf a '#(nil nil nil nil nil))
- (setf (aref a 0) 'foo)
- (setf (aref a 1) 37)
- (setf a '#(nil nil nil nil nil))
- 우리가 배운 리스트 함수들은 대개 순열sequence를 위해 설계된 것
- 순열 = 리스트 + 벡터 통칭
- 순열 함수: LENGTH, REVERSE, FIND-IF 등
- CAR, CDR, MEMBER, SUBST, SUBLIS, NCONC 등은 리스트 전용
- NREVERSE는 벡터에도 사용 가능
- 순열 = 리스트 + 벡터 통칭
- MAKE-ARRAY 함수: 배열을 만들어서 반환한다
- 초기값을 지정하지 않으면 구현에 따라 NIL이나 0 등으로 채워진다. 구현마다 다르므로 이 값에 의존하지 말 것!
- (make-array 5 :initial-element 1)
- 초기값을 지정하지 않으면 구현에 따라 NIL이나 0 등으로 채워진다. 구현마다 다르므로 이 값에 의존하지 말 것!
- 문자열은 벡터의 특수 유형
- 벡터에 작용하는 함수들은 문자열에도 작용
- 문자열의 원소들은 문자 객체character object다. 심볼, 숫자와는 별개의 타입
- 문자는 그 자신으로 평가된다
#\k => #\k
- 벡터에 작용하는 함수들은 문자열에도 작용
- 해시 테이블: 기능은 연관 리스트association list와 동일
- 벡터와 달리 리터럴이 없다. MAKE-HASH-TABLE 함수로 생성해야 한다.
- 키값의 비교에는 기본적으로 EQL을 사용
- 벡터와 달리 리터럴이 없다. MAKE-HASH-TABLE 함수로 생성해야 한다.
> (setf h (make-hash-table)) #<EQL Hash table 5173142> > (type-of h) HASH-TABLE
- GETHASH 함수: 해시 테이블에서 키를 검색한다. 키는 임의 종류의 객체
- (setf (gethash 'john h) '(attorney (16 maple drive)))
=> (ATTORNEY (16 MAPLE DRIVE))
- (gethash 'john h)
=> (ATTORNEY (16 MAPLE DRIVE))
T
- 반환값이 두 개?
- 첫 번째 반환값: 해당 키와 연관된 값. 해당 키가 없으면 NIL.
- 두 번째 반환값: 해시 테이블에서 키를 찾았으면 T, 못 찾았음면 NIL
- 그러니까 함수가 단순히 리스트 하나를 반환하는 게 아니라 값을 여러 개 반환할 수 있다는 건가..
- 첫 번째 반환값: 해당 키와 연관된 값. 해당 키가 없으면 NIL.
- (setf (gethash 'john h) '(attorney (16 maple drive)))
- Property List
- 모든 심볼은 property list를 가진다
- property list에 키와 함께 값을 저장할 수 있다 (이 키를 표지indicator라고 부른다)
- 이렇게 구축됨...
(ind-1 value-1 ind-2 value-2 ...)
- 겁나 낡은 개념. 그냥 완벽히 다루려고 넣은 내용...
- 여러분은 연관 리스트랑 해시 테이블을 쓰세요
- (setf (get 'fred 'gender) 'male)
- (setf (get 'fred 'age) 23)
- (setf (get 'fred 'siblings) '(george wanda))
- => (siblings (george wanda) age 23 gender male)
- (get 'fred 'age) => 23
- (get 'fred 'favorite-ice-cream-flavor) => nil ; GET에 세 번째 인자를 추가하면 nil 대신 그 인자를 반환
- GET은 항등 검사에 EQ를 사용
- SYMBOL-PLIST 함수: 심볼의 property list를 반환한다
- REMPROP: 심볼에서 property 제거하기. 반환값은 구현에 따라 다르다
- 모든 심볼은 property list를 가진다
- 리스프 도구모음 : ROOM
- 리스프 시스템은 대개 메모리를 많이 쓴다
- 대부분의 리스프 구현체는 가비지 컬렉터를 제공하지만 커먼 리스프 표준에 명시된 건 아니다
- 대개는 가비지 컬렉터를 강제로 작동시키는 GC라는 함수를 제공한다
- 대개는 가비지 컬렉터를 강제로 작동시키는 GC라는 함수를 제공한다
- ROOM 함수는 리스프의 현재 메모리 사용량을 보여준다
- 리스프 시스템은 대개 메모리를 많이 쓴다
13장 고급 주제 ¶
- Property List Cell
- 심볼은 포인터 다섯 개로 이루어져있더랬다
- 심볼 이름, value cell, function cell은 봤고
- 네 번째로 볼 것은 property list cell
- 모든 심볼은 property list cell을 가진다. 심지어 NIL도 가지고 있다
- property list cell은 일종의 전역 자료구조다... 어디서든 수정 가능
- 이거 쓰면 안되겠따 ㅡㅡ
- 심볼은 포인터 다섯 개로 이루어져있더랬다
- 순열 보충설명
- COERCE 함수: 리스트 <-> 벡터 변환해준다
- (coerce "Cockatoo" 'list)
(#\C #\o #\c #\k #\a #\t #\o #\o)
- (coerce '(#\b #\i #\r #\d) 'string)
"bird"
- (coerce '(foo bar baz) 'vector)
#(FOO BAR BAZ)
- (coerce "Cockatoo" 'list)
- 문자열을 만드는 또다른 방법
- MAKE-ARRAY로 벡터를 만든다
- ELEMENT-TYPE 키워드에 이 벡터가 STRING-CHAR 타입의 객체만 저장한다고 명시한다
- STRING-CHAR는 CHARACTER의 하위 타입
- STRING-CHAR는 CHARACTER의 하위 타입
- (make-array 3 :element-type 'string-char :initial-contents '(#\M #\o #\m))
"Mom"
- MAKE-ARRAY로 벡터를 만든다
- MAPCAR는 리스트에만 작동하지만 MAP은 리스트에도 벡터에도 작동한다
- (map 'list #'+ '(1 2 3 4) '#(10 20 30 40))
=> (11 22 33 44)
- 첫 번째 인자는 반환되는 순열의 타입. NIL 주면 반환값도 NIL. (부수효과만 필요할 때 사용)
- (map 'list #'+ '(1 2 3 4) '#(10 20 30 40))
- COERCE 함수: 리스트 <-> 벡터 변환해준다
14장. 매크로와 컴파일 ¶
아 14장 저장 눌렀는데 날아갔어 ㅡㅡ 급귀찮
14장 요약 ¶
- 매크로는 리스프의 문법을 확장하기 위한 수단이다
- 매크로는 대충 C의 #define과 비슷함
- 매크로 정의하기: (defmacro simple-incf (var) (list 'setq var (list '+ var 1)))
- 매크로는 인자를 평가하지 않는다. 평가될 표현식을 반환한다
- 함수는 인자를 평가한다. 함수가 반환하는 값은 평가되지 않는다
- 특수 함수special function는 인자를 평가하지도 않고 평가할 표현식을 반환하지도 않는다
- 너는 특수 함수를 정의할 수 없다 컴파일러 작성자만 가능
- 너는 특수 함수를 정의할 수 없다 컴파일러 작성자만 가능
- 역따옴표 문자
- (setf name 'fred)
`(this is ,name from pittsburgh) => (THIS IS FRED FROM PITTSBURGH)
- ,@는 리스트를 결과 리스트에 끼워넣는다
- (setf name 'fred)
- 매크로는 대충 C의 #define과 비슷함
- 컴파일
- 단일 함수를 컴파일: COMPILE
- 파일 전체를 컴파일: COMPILE-FILE
- 주의사항
- 부수효과를 일으키는 매크로를 정의하지 말것. 부수효과를 일으키는 표현식을 반환하는 매크로는 괜찮다
- 왜 하스켈이 떠오르는가... -.-??
- 왜 하스켈이 떠오르는가... -.-??
- 전역 변수를 쓰면 컴파일러가 경고한다: ""assumed to be SPECIAL.""
- 변수를 선언할 때 DEFVAR, DEFPARAMETER, DEFCONSTANT를 써야 한다
- 변수 선언은 그 변수를 쓰는 코드보다 앞에
- 변수를 선언할 때 DEFVAR, DEFPARAMETER, DEFCONSTANT를 써야 한다
- 매크로 정의는 그 매크로를 호출하는 코드보다 앞에
- 내장 함수를 재정의하지 말 것
- 부수효과를 일으키는 매크로를 정의하지 말것. 부수효과를 일으키는 표현식을 반환하는 매크로는 괜찮다
- 단일 함수를 컴파일: COMPILE
14장 고급 주제 ¶
- 람다-리스트 키워드 &BODY
- &REST 대신 쓸 수 있다
- &REST 대신 쓸 수 있다
(defmacro while (test &body body) '(do () ((not ,test)) ,@body))
- Destructurinng Lambda List: 인자에 대한 패턴 매칭
- 매크로 함수에서만 가능. 일반 함수에선 불가능
- 매크로 함수에서만 가능. 일반 함수에선 불가능
(defmacro mix-and-match (p q) (let ((x1 (first p)) (y1 (second p)) (x2 (first q)) (y2 (second q))) `(list '(,x1 ,y1) '(,x1 ,y2) '(,x2 ,y1) '(,x2 ,y2)))) ;; 위에 걸 이렇게 쓸 수 있다 (defmacro mix-and-match ((x1 y1) (x2 y2)) `(list '(,x1 ,y1) '(,x1 ,y2) '(,x2 ,y1) '(,x2 ,y2)))
- dynamic scoping
- 지금까지는 항상 lexical scoping을 사용했다
- 즉 함수 FOO가 변수 X에 접근하려면 X가 정의된 문맥 안에 FOO의 정의가 있어야 한다
- dynamically scoped variable은 special variable이라고도 부른다
- 변수 이름을 special하게 되도록 정하면 이 변수는 특정 함수에 국한되지 않는다
- 즉 어디서든 이 변수에 접근할 수 있다
- DEFVAR: 특수 변수를 선언한다
- (defvar birds)
- (defvar *total-glasses* 0 "Total glasses sold so far")
- 단 해당 변수에 이미 값이 있으면 변경하지 않는다
- 단 해당 변수에 이미 값이 있으면 변경하지 않는다
- (defvar birds)
- DEFPARAMETER: DEFVAR와 같지만 변수에 값이 있던 없던 새 값을 설정한다
- DEFCONSTANT: 상수를 정의한다
- 지금까지는 항상 lexical scoping을 사용했다
Common Lisp Object System ¶
- 커먼 리스프에서 클래스 써보자
- A Brief Guide to CLOS: http://www.aiai.ed.ac.uk/~jeff/clos-guide.html
- 임의 객체의 클래스 알아내기
표현식 값 (class-of 'a) #<Built-In-Class SYMBOL> (class-of "a") #<Built-In-Class STRING> (class-of 12) #<Built-In-Class INTEGER> (class-of '(a b)) #<Built-In-Class CONS> (class-of '#(a b c)) #<Built-In-Class VECTOR> - 잠깐! 표기법...
- "Thing*" means zero or more occurrences of thing
- "thing+" means one or more.
- Curly brackets { and } are used for grouping, as in {a b}+, which means a b, a b a b, a b a b a b, etc.
- "Thing*" means zero or more occurrences of thing
- 클래스 정의하기
(DEFCLASS class-name (superclass-name*) (slot-description*) class-option*)
- slot-description은 (slot-name slot-option*) 형식
- slot-option에 유용한 옵션
- :ACCESSOR function-name
- :INITFORM expression
- :INITARG symbol
- 더 자세한 건 여기에~~ http://en.wikibooks.org/wiki/Common_Lisp/Advanced_topics/CLOS
- :ACCESSOR function-name
- slot-option에 유용한 옵션
- 인스턴스 생성
(MAKE-INSTANCE class {initarg value}*)
- person 클래스를 정의했으면 이런 식: (make-instance 'person :age 100)
- 부모클래스
- 클래스 정의할 때 부모클래스를 생략하면 자동으로 standard-object를 상속한다
- 클래스 정의할 때 부모클래스를 생략하면 자동으로 standard-object를 상속한다
- 다중 상속이 가능하다: (defclass a (b c) ...)
- 그럼 :initform은요? 겹치는데??
- b의 정의가 c의 정의보다 우선순위가 높다
- 그럼 :initform은요? 겹치는데??
- 메서드 정의하기
(DEFMETHOD generic-function-name specialized-lambda-list form*)
- specialized-lambda-list라구요?
- 일반적인 lambda-list는 (var1 var2 ...)
- 이건 ((var1 class1) (var2 class2) ...) ; 변수의 타입을 제한
- 일반적인 lambda-list는 (var1 var2 ...)