프로그램을 설계하는 두가지 방법
1. 시스템을 이루는 낱낱이 물체(Object)라고 보고, 시간에 따라 상태가 변하는 여러 물체를 한데 엮어서 커다란 시스템을 만드는 방법이다.
2. 정보가 강물처럼 끝없이 시스템 속에서 흘러간다 여기고, 시간에 따라 정보가 변하는 모습을 스트림으로 나타낸다.
3.1 덮어쓰기와 갇힌 상태
3.1.1 갇힌 상태변수
물체 상태가 시간에 따라 달라진다.
ex)
(define (balance 100))
(define (withdraw amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds"))
단점)
프로시저가 마음대로 balance 값을 읽을 수도 있고 덮어써 버릴 수도 있다.
해결책 => balance를 withdraw 프로시저 안에 가둔다.
(define (withdraw amount)
(let ((balance 100))
(lambda (amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds"))))
(withdraw 25) => 75
(withdraw 25) => 50 //똑같은 식을 두번계산했지만 답은 다르게 나온다.
(withdraw 60) => "Insufficient funds"
/* 특별한 형태
(set! <name> <new-value>)
이 식을 계산하고 나면, <name>이 가리키던 값이 <new-value> 식을 계산한 값으로 바뀐다
(begin <exp> <exp2> ... <expk>)
<exp>부터 <expk>까지 k개 싱을 차례대로 계산한다. 그리고 마지막 <expk>를 계산한 값을, 전체 begin식을 계산한 값으로 삼는다.
*/
(define (make-withdraw balance)
(lambda (amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds"))))
(define W1 (make-withdraw 100))
(define W2 (make-withdraw 100))
//make-withdraw의 성질을 가진 물체를 얼마든지 만들수 있다. 위의 프로시저만 있다면
(define (make-account balance)
(define (withdraw amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds"))
(define (deposit amount)
(set! balance (+ balance amount))
balance)
(define (dispatch m)
(cond ((eq? m 'withdraw) withdraw)
((eq? m 'deposit) deposit)
(else (error "Unknown request -- MAKE-ACCOUNT"
m))))
dispatch)
//자바에서 make-account 객체를 정의하고 그 안에 withdraw와 deposit 메서드를 만든 것과 비슷하다.
(define acc (make-account 100))
((acc 'withdraw) 50) => 50
3.1.2 덮어쓰기가 있어서 좋은 점
변수를 물체에 가두어 놓고 그 값을 알맞은 때에 덮어써서, 물체 속에 상태를 숨겨두는 기법을 알게 되었다. 앞서 프로시저에 인수를 건네주는 방법만 쓰면, 모든 상태를 밖으로 꺼내놓을 수밖에 없다. 모듈 방식으로 프로그램을 짤 때 물체 속에 변하는 상태를 감춰두는 방법이 더 좋다.
3.1.3 덮어쓰기를 끌어들인 대가
함수형 프로그래밍: 덮어쓰기 없이 프로그램 짜는 법
(define (make-simplified-withdraw balance)
(lambda (amount)
(set! balance (- balance amount))
balance))
(define W (make-simplified-withdraw 25))
(W 20) => 5
(W 10) => -5
/*계산과정
((make-simplified-withdraw 25) 20)
((lambda (amount) (set! balance (- 25 amount)) 25) 20)
//(balance == 5) == 25
//맞바꿈 계산법으로 계산하면 모순이 생김
//여기서 깨달아야 하는 중요한 점!!!
//변수는 값의 이름이 아니다!!!
//변수는 값을 넣어둔 자리의 이름이다!!!
*/
(define (make-decrementer balance)
(lambda (amount)
(- balance amount)))
(define D (make-decrementer 25))
(D 20) => 5
(D 10) => 15
/* 계산과정
((make-decrementer 25) 20)
((lambda (amount) (- 25 amount)) 20)
(- 25 20)
*/
같음과 달라짐(변함)
'같다'의 개념 따져보기
(define D1 (make-decrementer 25)) == (define D2 (make-decrementer 25))
(define W1 (make-simplified-withdraw 25)) != (define W2 (make-simplified-withdraw 25))
이유)
(W1 20) => 5
(W1 20) => -15
(W2 20) => 5
=> W1 와 W2가 같은 식을 계산하여 만들어 낸 물체이기는 하지만
'어떤 식에서 W1을 W2로 맞바꾸더라도 계산 결과가 달라지지 않는다.'라는 관점에서는 틀린 말이다.
=>'같은 것을 같은 것으로 맞바꿀 수 있다'는 원칙에 따라 식을 계산할 때 식의 값이 달라지지 않는다는 성질이 뒷받침된다면, 그런 언어를 '뜻이 한결같은' 언어라고 한다.
명령을 내려서 프로그램 짤 때 생기는 문제
명령 중심 프로그래밍: 덮어쓰기처럼 명령을 내려서 프로그램을 짜는 방식
(define (factorial n)
(define (iter product counter)
(if (> counter n)
product
(iter (* counter product)
(+ counter 1))))
(iter 1 1))
(define (factorial n)
(let ((product 1)
(counter 1))
(define (iter)
(if (> counter n)
product
(begin (set! product (* counter product)) // 두 식의 순서만 바뀌어도
(set! counter (+ counter 1)) // 답이 다르게 나온다.
(iter))))
(iter)))
3.2 환경 계산법
//맞바꿈 계산법말고 새로운 계산법
환경: 변수 일람표를 한 줄로 이어놓은 것
//변수값을 정의하여 모아둔 표
환경 계산법에서 프로시저는 코드와, 환경을 가리키는 꼬리를 쌍으로 묶어서 나타낸다.
(define square (lambda (x) (* x x)))
- 프로시저를 여러 인자에 적용하기 위해서는, 먼저 새 변수 일람표를 하나 만들고 그 일람표에서 인자의 이름과 값을 묶어 인자를 정의한다. 그 다음, 새로 만든 환경을 계산 문맥으로 보고 프로시저의 몸을 계산한다. 새 일람표를 둘러싸는 환경은 프로시저 물체가 가리키던 환경이다.
- 어떤 환경에서 lambda 식을 계산하면 새 프로시저 물체가 나온다. 이 물체는 코드와 환경 꼬리를 쌍으로 묶어 만들어진다. 코드는 lambda 식을 그대로 가져온 것이고, 꼬리가 가리키는 환경은 lambda 식의 값을 구할 때 쓴 환경이다.
- 어떤 환경에서 set!을 계산하려면, 그 환경에서 변수가 정의된 곳을 찾아낸 다음에 그 변수의 값을 덮어쓰면 된다.
3.2.2 간단한 프로시저 계산하기
ex)
(define (make-withdraw balance)
(lambda (amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds")))
=>
(define W1 (make-withdraw 100))
=>
(W1 50)
=>
(define W2 (make-withdraw 100))으로 두 번째 물체를 만들 때 |
3.2.4 안쪽 정의
(define (sqrt x)
(define (good-enough? guess)
(< (abs (- (square guess) x)) 0.001))
(define (improve guess)
(average guess (/ x guess)))
(define (sqrt-iter guess)
(if (good-enough? guess)
guess
(sqrt-iter (improve guess))))
(sqrt-iter 1.0))
- 프로시저를 안쪽에 가두어 정의하면, 그 프로시저 이름은 저를 둘러싼 프로시저 밖에 있는 이름과 뒤섞일 염려가 없다. 갇힌 프로시저는, 프로시저를 불러쓸 때마다 새로 생긴 일람표 속에서 정의되기 때문이다.
- 안쪽에 가두어 정의한 프로시저는 저를 둘러싼 프로시저의 인자를 자유 변수처럼 쓸 수 있다. 갇힌 프로시저를 불러쓰는 환경이, 저를 감싸는 프로시저의 적용 환경에 둘러싸여 있기 때문이다.
댓글 없음:
댓글 쓰기