1. 프로시저를 써서 요약하는 방법
1. LISP를 사용하는 이유
- 프로그램을 짜는데 필요한 여러 원소와 갖가지 데이터 구조를 공부하고, 이를 뒷받침하는 데 어떤 언어 기능이 있어야 하는지를 관계지어 설명하기에 알맞은 점이 많다
1.1 프로그램을 짤때 바탕이 되는 것
- 프로그래밍 언어는 프로세스에 대한 사람의 생각을 짜임새 있게 담아내는 그릇
- 프로그래밍의 구성 요소
- 프로시저 : 데이터를 처리하는 규칙을 적은 것
- 데이터 : 프로시저에서 쓰려는 물건(stuff)
- 좋은 프로그래밍 언어의 세가지 표현 방식
- 기본식
- 엮어내는 수단(means of combination) : 간단한것을 모아 복잡한것으로 만든다
- 요약하는 수단(means of abstraction) : 복잡한 것에 이름을 붙여 하나로 다룰 수 있게끔 간추린다
--> 기본 데이터와 기본 프로시저를 나타내고, 프로시저들과 데이터들을 엮어서 더 복잡한 것을 만들고 이를 간단하게 요약하는 수단이 반드시 있어야 함
1.1.1 식(expression)
- 셈(evaluation)
- 엮은식(combination) : 여러식을 괄호로 묶어 리스트를 만들고 프로시저 적용(procedure application)을 하도록 엮어 놓은 식
- 연산자(operator), 피연산자(operand), 인자(argument)
- 앞가지 쓰기(prefix notation) : 연산자를 피연산자 왼쪽에 두는 방식
- 장점 1 : 인자가 많아져도 따로 문법을 만들 필요 없이 쓰던 그대로 쓸수 있다
- 장점 2 : 식속에 다시 식을 넣어서 식을 여러 겹으로 엮어 늘리기가 쉽다
- 가지런히 쓰기(pretty-printing) : 식을 깊이 겹쳐 써야 할 때 인자를 중심으로 줄을 맞추고 알맞게 들여 쓰는 방식
- read-eval-print loop
1.1.2 이름과 환경
- 변수 : 계산하는 물체(computational object)에 붙이는 이름
- define : 이름을 붙이는 방법(scheme)
- 합친연산(compound operation)을 간추리는 수단
- 실행기(interpreter)구성
- 메모리 : 이름-물체의 쌍을 저장 --> 환경
1.1.3 엮은식(combination)을 계산하는 방법
- 엮은식의 값을 계산하는 차례
1. 엮은식에서 부분 식(subexpression)의 값을 모두 구한다
2. 엮은식에서 맨 왼쪽에 있는 식(연산자)의 값은 프로시저가 되고, 나머지 식(피연산자)의 값은 인자가 된다. 프로시저를 인자에 적용하여 엮은식의 값을 구한다
--> 되도는(recursive)프로세스
- 나무꼴 어큐뮬레이션(tree accumulation)
- node : 연산자
- branch : 연산할 것들
1.1.4 묶음 프로시저(compound procedure)
- 프로시저 정의 : 복잡한 연산에 이름을 붙여서 쓰는 방법
- (define (<name> <formal paramegters>) <body>)
- <name> : 환경에서 프로시저를 가리키는 이름
- <formal parameters> : 프로시저가 받아오는 인자를 가리키기 위해서 프로시저의 몸속에서 쓰는 이름
- <body> : 프로시저를 불러 쓸 때마다 계산할 식
1.1.5 맞바꿈 계산법(substitution model)으로 프로시저를 실행하는 방법
- 묶음 프로시저를 인자에 맞춘다는것은 프로시저의 몸 속에 있는 모든 인자 이름(formal parameter)을 저마다 그에 대응하는 인자 값으로 맞바꾼 다음, 그렇게 얻어낸 식의 값을 구하는 것이다
- 샘플 (f 5)
(define (square x) (* x x))
(define (sum-of-squares x y)
(+ (square x) (square y)))
(define (f a)
(sum-of-squares (+ a 1) (* a 2)))
1. a -> 5로 맞바꿈 : (sum-of-square (+ 5 1) (* 5 2)) --> 연산자 : sum-of-square, 피연산자 : (+ 5 1), (* 5 2)
2. sum-of-squares x y -> x=6, y=10으로 맞바꿈 : (+ (square 6) (square 10)) --> 연산자 : + , 피연산자 : (square 6), (square 10)
3. square x -> x = 6 , x= 10 두개 식 맞바꿈 : (+ (* 6 6) (* 10 10))
4. (+ 36 100)
5. 136
- 실제 실행기가 돌아가는 방법은 아니며, 식의 계산 과정(evaluation process)에 대하여 제대로 형식을 갖추고 생각을 정리하려는 출발점일 뿐
- 실제로는 갇힌환경(local environment)에 인자 이름을 넣어놓고 계산하는 방법을 사용함
- 인자먼저 계산법(applicative order) : LISP 실행기 적용 방식
- 인자값 먼저 구하는 방법
- 같은식을 여러번 되풀이 계산하는 경우가 생기지 않아 좀 더 빠름, 정의 대로 계산하는 규칙이 더 복잡함
- 정의대로 계산법(normal order)
- 인자값을 계산하지 않고 식 자체를 인자이름으로 맞바꾸어 가다가 마지막에 기본연산으로만 이루어진 식을 얻을때 값을 구하는 방법
- 끝까지 펼친 다음에 줄이는 계산 방법
(f 5)
1. (sum-of-square (+ 5 1) (* 5 2))
2. (+ (square (+ 5 1) (square (* 5 2))))
3. (+ (* (+5 1) (+5 1)) (* (* 5 2) (*5 2))) : (+ 5 1), (* 5 2)를 두번 계산함
4. (+ (* 6 6) (* 10 10))
5. (+ 36 100)
6. 136
1.1.6 조건 식과 술어(predicate)
- 조건식 문법(cond)
(cond (<p1> <e1>)
(<p2> <e2>)
...
(<pn> <en>)
(else <e>)
- 절(clause) : 두식을 괄호로 묶어 놓음 (<p1> <e1>)
- 술어(predicate) : 참/거짓
- 조건식 문법(if)
(if <predicate> <consequent> <alternative>)
- 논리연산(and, or, not)
(and <e1> ... <en>) : 왼쪽에서 오른쪽으로 계산, 그 가운데 거짓이라 대답하는 <e>가 나오면 and 값은 거짓이 되고 나머지<e>는 구하지 않음. 모든<e>가 참일 때에는 마지막 식의 값을 반환
(or <e1> ... <en>) : 왼쪽에서 오른쪽으로 계산, 그 가운데 참이라 답하는 <e>가 나오면 참이되고, 나머지는 구하지 않음. 모든<e>가 거짓일 때에는 마지막 식의 값을 반환
(not <e>) : <e>가 참이면 거짓, 거짓이면 참
1.1.7 연습 : 뉴튼 법으로 제곱근 찾기
- 함수와 프로시저 차이
- 프로시저는 효율성을 갖춰야 함
- 함수 : 무엇이 어떤 성질을 지니는지 밝힘
- 프로시저 : 무엇을 어떻게 만들지/구할지 나타내는 일
- 뉴튼법 : 얻고자 하는 값에 가까운 값을 차례로 되풀이해서 구해 나감
1.1.8 블랙박스처럼 간추린 프로시저
- 프로시저 요약하기 : 프로시저를 묶어서 간추려 놓은 이름
- Block Structure : 프로시저 정의를 겹쳐쓰는 모양
- lexical scoping 규칙 : 문범에 따라 변수가 보이는 넓이가 정해지는 규칙