미분하는 프로그램 4 - Object-Oriented Programming
OOP스타일로 메시지 패싱을 사용하는 형태로 작성했습니다.
Scheme과 OOP 언어와의 차이라면 self(또는 dispatch)의 동작이 자동이냐 아니냐의 차이입니다.
원래 Scheme이 Cuncurrent OO언어인 Actor의 의미 모델을 설명하기 위해 개발되었기 때문에, 이런 방법이 확실히 이상한 꼼수는 아닙니다.
만약, OOP 스타일로 짜여진 미분 프로그램에 공식을 추가하고 싶으면 오브젝트만 하나 더 만들면 됩니다.
물론 입력받는 형태가 원래의 방식에 비해서 좀 불편할지는 모르겠습니다.
확실히 "'(x + 1)"과 "(Sum (Symbol 'x) (Symbol 1))"은 차이가 크죠.
하지만 이런 문법적인 변환은 Lisp이나 Scheme에서 그리 어렵지 않을뿐더러, 설계의 유연성이 크게 향상되었으니 단점이라 할 것도 못됩니다.
아래 소스에서 문제점을 꼽으라면 number? 메소드와 number?= 메소드의 중복된 구현이 눈에 확 띕니다.
이러한 문제를 잡으려면 super 클래스를 만들어서 공통된 구현을 올리고, 상속받으면 될 것 같군요.
다음에는 그렇게 한 번 해보겠습니다.
(define (Sum u v) (define (self m) (cond ((eq? 'deriv m) (lambda (x) (Sum ((u'deriv) x) ((v'deriv) x)))) ((eq? '->s m) (list (u'->s) '+ (v'->s))) ((eq? 'number? m) false) ((eq? 'number?= m) (lambda (x) false)) (else (error "Undefined message")))) (cond (((u'number?=) 0) v) (((v'number?=) 0) u) ((and (u'number?) (v'number?)) (Symbol (+ (u'->s) (v'->s)))) (else self))) (define (Product u v) (define (self m) (cond ((eq? 'deriv m) (lambda (x) (Sum (Product u ((v'deriv) x)) (Product ((u'deriv) x) v)))) ((eq? '->s m) (list (u'->s) '* (v'->s))) ((eq? 'number? m) false) ((eq? 'number?= m) (lambda (x) false)) (else (error "Undefined message")))) (cond ((or ((u'number?=) 0) ((v'number?=) 0)) (Symbol 0)) (((u'number?=) 1) v) (((v'number?=) 1) u) ((and (u'number?) (v'number?)) (Symbol (* (u'->s) (v'->s)))) (else self))) (define (Difference u v) (define (self m) (cond ((eq? 'deriv m) (lambda (x) (Difference ((u'deriv) x) ((v'deriv) x)))) ((eq? '->s m) (list (u'->s) '- (v'->s))) ((eq? 'number? m) false) ((eq? 'number?= m) (lambda (x) false)) (else (error "Undefined message")))) (cond (((u'number?=) 0) (Negative v)) (((v'number?=) 0) u) ((and (u'number?) (v'number?)) (Symbol (- (u'->s) (v'->s)))) (else self))) (define (Exponentiation u v) (define (self m) (cond ((eq? 'deriv m) (lambda (x) (Product (Product v (Exponentiation u (Difference v (Symbol 1))) ((u'deriv) x)))) ((eq? '->s m) (list (u'->s) '** (v'->s))) ((eq? 'number? m) false) ((eq? 'number?= m) (lambda (x) false)) (else (error "Undefined message")))) (cond ((or ((u'number?=) 0) ((u'number?=) 1)) e1) (((v'number?=) 0) (Symbol 1)) (((v'number?=) 1) u) ((and (u'number?) (v'number?)) (Symbol (** (u'->s) (v'->s)))) (else self))) (define (Negative u) (define (self m) (cond ((eq? 'deriv m) (lambda (x) (Negative ((u'deriv) x)))) ((eq? '->s m) (list '- (u'->s))) ((eq? 'number? m) false) ((eq? 'number?= m) (lambda (x) false)) (else (error "Undefined message")))) (cond ((u'number?) (Symbol (- (u'->s)))) (else self))) (define (Symbol s) (define (variable? x) (symbol? x)) (define (same-variable? v1 v2) (and (variable? v1) (variable? v2) (eq? v1 v2))) (define (self m) (cond ((eq? 'deriv m) (lambda (x) (if (same-variable? s x) (Symbol 1) (Symbol 0)))) ((eq? '->s m) s) ((eq? 'number? m) (number? s)) ((eq? 'number?= m) (lambda (x) (and (number? s) (number? x) (= s x)))) (else (error "Undefined message")))) self) ;; infix로 풀어본 문제 ;(deriv '(x + 3) 'x) ;;1 ;(deriv '(x * y) 'x) ;;y ;(deriv '((x * y) * (x + 3)) 'x) ;;((x * y) + (y * (x + 3))) ;(deriv '(- x) 'x) ;;-1 ;(deriv '((x * 3) - 4) 'x) ;;3 ;(deriv '(- (x ** 2)) 'x) ;;(- (2 * x)) ((((Sum (Symbol 'x) (Symbol 3)) 'deriv) 'x)'->s) ;1 ((((Product (Symbol 'x) (Symbol 'y)) 'deriv) 'x)'->s) ;y ((((Product (Product (Symbol 'x) (Symbol 'y)) (Sum (Symbol 'x) (Symbol 3))) 'deriv) 'x)'->s) ;((x * y) + (y * (x + 3))) ((((Negative (Symbol 'x)) 'deriv) 'x)'->s) ;-1 ((((Difference (Product (Symbol 'x) (Symbol 3)) (Symbol 4)) 'deriv) 'x)'->s) ;3 ((((Negative (Exponentiation (Symbol 'x) (Symbol 3))) 'deriv) 'x)'->s) ;(- (2 * x))
* 추가 : super 클래스를 만들어서 공통된 구현을 뽑아내봤는데, 결과적으로 그리 좋은 방법 같지가 않았습니다.
겨우 공통된 두 메소드를 끄집어 내어봤자 설계의 유연성만 떨어질 뿐 효과적이지 못하다는 결론을 얻었습니다.
원래는 Scheme에서 관용적으로 쓰는 기법을 써서 super클래스를 표현하고 인터페이스를 정의하려 했는데, 클래스내에서 modifier가 없어서인지, 제대로 바꾸기가 힘드는 군요.
참고로 제가 하려했던 기법을 쓴 다른 예제의 일부를 아래에 보여드리겠습니다.
이 기법을 위의 코드에 적용할 수 있으신 분은 알려주시기 바랍니다.
세미콜론은 원래 주석이고, 대괄호로 쳐져있는 부분이 제가 추가한 주석입니다.
; Object-Oriented Programming in pure Scheme. ; Written by Myung Ho Kim, <a href="mailto:mhkim@esther.donga.ac.kr" rel="nofollow">mhkim@esther.donga.ac.kr</a>, 1998-1999. [김명호라는 분은 지금은 MS에 계시다고 알고있습니다. -semmal] ; Graphics *abstract* class (define (<graphics> origin color) (lambda (m) (cond ((eq? m 'location) (lambda (self) origin)) ((eq? m 'set-location!) ; protected (lambda (self new-origin) (set! origin new-origin))) ((eq? m 'color) (lambda (self) color)) ((eq? m 'draw) ; [(ask self ...)코드에 의해 자식 클래스에서 draw메소드를 부르면, 여기서 펜색을 정하고, 해당 도형을 그리게 됩니다. -semmal] (lambda (self) (set-pen-color! color) (ask self 'draw-shape))) ((eq? m 'erase) (lambda (self) (set-pen-color! 'white) (ask self 'draw-shape))) ((eq? m 'change-location) (lambda (self new-loc) (ask self 'erase) (ask self 'set-location! new-loc) (ask self 'draw))) ((eq? m 'change-color) (lambda (self new-color) (set! color new-color) (ask self 'draw))) (else (error "message no understood" m))))) ; Line class (define (<line> origin extent color) (define super (<graphics> origin color)) ; [상속 받을 부모 클래스를 super로 정합니다. -semmal] (lambda (m) (cond ((eq? m 'extent) (lambda (self) extent)) ((eq? m 'draw-shape) (lambda (self) (apply position-pen (ask self 'location)) (apply draw-line-to (ask self 'extent)))) ((eq? m 'set-location!) (lambda (self new-loc) (let ((origin (ask self 'location))) (delegate super self m new-loc) ; [부모 클래스의 set-location 메소드를 delegate합니다. -semmal] (set! extent (point (+ (x@ extent) (- (y@ new-loc) (x@ origin))) (+ (y@ extent) (- (y@ new-loc) (y@ origin)))))))) (else (super m))))) ;[중간은 너무 많아서 생략 -semmal] ; The protocol for invoking methods (define (ask obj m. args) (apply delegate obj obj m args)) (define (delegate super obj m . args) (apply (super m) obj args)) ; Utilities (define point cons) (define x@ car) (define y@ cdr) ; Samples (define line (<line> (point 0 0) (point 100 100) 'blue)) ;[이하 생략 -semmal]
댓글
열심히 보고 있는데
열심히 보고 있는데 눈에 잘 들어오질 않는군요. 너무 앞서나가시는 것 같은 ㅠㅠ
May the F/OSS be with you..
----------------------------
May the F/OSS be with you..
저도
예전에 대충 본 것, 복습하면서 다시 공부하는 중이라 그렇습니다.
한 번 해봤던 만큼 조금 빨리 나가지네요.
사실 이 OOP 코드도 3번은 다시 짠 코드라서, 바로 전 소스에 비해 그 차이가 꽤나 벌어졌습니다.
처음에는 Object로 일단 만들기는 했는데 구조가 이상했었고요.
두번째는 만들기는 제대로 만든 것 같았는데, 모든 요소가 Object가 아니었습니다.
사실, 세번째로 만든 이 코드는 Ruby로 옮겨가기 위한 중간단계로 선택한 것입니다.
모든 요소를 오브젝트로 만드는 것이지요.
------------------------------
How many legs does a dog have?
------------------------------
How many legs does a dog have?
첫번째 코드는...
첫번째 코드는 대강 눈에 들어오는데,
두번째 그래픽예제는 좀더 봐야겠군요.
첫번째 코드를 ML style로 바꾸어 봤는데, 한번 보시라고 올립니다.
먼제, 이전 글에서 나온 스타일로 가보면.
이걸, 객체지향스타일로 바꾸면.
정도로 바꿀 수 있겠지요.
첫번째 스타일의 경우엔 새로운 operation을 추가하기 쉽습니다.
예를 들면, print 함수를 추가하고자 하면 그냥 정의하면 되지요.
하지만, semmal님의 설명처럼 새로운 class (혹은 variant나 datatype)를
추가하고자 하면 함수마다 일일이 고쳐 주어야 하지요.
두번째 스타일의 경우엔 새로운 class의 추가가 기존 코드를 수정하지 않아도 됩니다.
하지만, 이 경우엔 새로운 함수를 추가하고자 하면 기존의 모든 코드를
수정해야 합니다.
이 문제가 expression problem이라고 알고 있습니다.
객체지향언어들은 두번째 스타일을 따르기 때문에 새로운 함수를
추가하는 유연한 방법들이 필요하기 마련인데,
대개는 design pattern중에 visitor pattern의 변형된 것들을
사용한다는군요.
semmal님의 새로운 글을 기다리다가 지쳐서
이것저것 공부하던 것을 적어봤습니다~
실행예제...
실행예제가 빠졌군요...
두번째 경우는 뒤에 (Deriv "x")가 scheme예제에서 'deriv) 'x 와 같은 역할입니다.
참고로 ->s 는 Eval로 정의했습니다.
댓글 달기