Getting Started with CLISP (13) - 조건문 The Conditionals: if and Beyond
The Conditionals: if and Beyond
true 나 false 에 따라 서로 다른 값이 반환되도록 할 때 if 를 사용한다.
1 2 3 4  | [57]> (if (= (+ 1 2) 3) 'yup 'nope) YUP  [57]>(if (= (+ 1 2) 4) 'yup 'nope) NOPE  | cs | 
물론 리스트가 비었는지 체크할 때도 사용할 수 있다.
1 2 3 4  | [58]> (if '(1) 'the-list-has-stuff-in-it 'the-list-is-empty) THE-LIST-HAS-STUFF-IN-IT [58]> (if '() 'the-list-has-stuff-in-it 'the-list-is-empty) THE-LIST-IS-EMPTY  | cs | 
아래 예에서는 5가 홀수인지 살펴본다. 5는 홀수이므로 odd-number을 반환한다.
1 2 3 4  | [59]> (if (oddp 5)           'odd-number           'even-number) ODD-NUMBER  | cs | 
2가지 중요한 점을 관찰할 수 있다.
- if 다음의 오직 하나의 표현식만 평가된다
- if 문에서는 오직 한 가지만 수행할 수 있다
보통은 리스프에서 함수가 평가될 때, 함수 뒤에 위치한 모든 표현식이 평가의 대상이지만, if 문에서는 위의 룰을 따르지 않는다. 다음의 경우를 보자.
1 2 3 4  | [60]> (if (oddp 5)          'odd-number          (/ 1 0)) ODD-NUMBER  | cs | 
1을 0으로 나눠야하기 때문에 일반적인 경우라면 위의 구문은 실행이 되지 않을 것이다. 하지만 if 문에서는 앞서 관찰한대로 if 다음의 하나의 표현식만 평가되므로, 이 경우 5는 홀수이고, 홀수인 경우에는 odd-number를 반환하기만 하면 되므로 1을 0으로 나눈다는 것 자체는 아무 문제가 되지 않는다. 하지만 6을 넣는 경우는 사정이 달라진다. 이 경우에는 6이 홀수가 아니므로 (/ 1 0)을 평가하게 되고, 다음과 같이 에러가 발생한다.
1 2 3 4 5  | [60]> (if (oddp 6)          'odd-number          (/ 1 0)) *** - /: division by zero  | cs | 
앞서 말했듯이 if 문의 경우 하나의 표현식만 평가받기 때문에, 둘 이상의 독립된 조건을 걸 수 없습니다. 둘 이상의 분기를 두고자 하는 경우, 특별한 명령어인 progn 을 사용할 수 있습니다. progn 을 사용하면 마지막 평가만 전체 표현식의 값으로 반환됩니다. 다음의 예를 봅시다.
1 2 3 4 5 6 7  | [62]> (defvar *number-was-odd* t) *NUMBER-WAS-ODD* [64]> (if (oddp 5)        (progn (setf *number-was-odd* t) 'odd-number) 'even-number) ODD-NUMBER [64]> *number-was-odd* T  | cs | 
Going Beyond if: The when and unless Alternatives
매번 progn 을 사용하는 것은 쉽지 않은 일이다. 리스프는 이를 위해 암묵적인 progn 을 포함하는 몇 가지 명령어를 가지고 있다. 일반적인으로 다음과 같이 사용한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14  | [65]> (defvar *number-is-odd* nil) *NUMBER-IS-ODD* [65]> (when (oddp 5)          (setf *number-is-odd* t)          'odd-number) ODD-NUMBER [65]> *number-is-odd* T [65]> (unless (oddp 4)          (setf *number-is-odd* nil)          'even-number) EVEN-NUMBER [65]>  *number-is-odd* NIL  | cs | 
when 을 사용할 때는 조건이 true 일때 표현식이 평가되고, unless 의 경우에는 조건이 false 일 때 표현식이 평가된다.
The Command That Does It All: cond
cond 폼은 리스프에서 브랜칭을 처리하는 전통적인 방법이다. 다음의 예를 보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17  | [66]> (defvar *arch-enemy* nil) *ARCH-ENEMY* [66]> (defun pudding-eater (person)           (cond ((eq person 'henry) (setf *arch-enemy* 'stupid-lisp-alien) '(curse you lisp alien – you ate my pudding))                 ((eq person 'johnny) (setf *arch-enemy* 'useless-old-johnny) '(i hope you choked on my pudding johnny))                 (t '(why you eat my pudding stranger ?)))) PUDDING-EATER [66]>  (pudding-eater 'johnny) (I HOPE YOU CHOKED ON MY PUDDING JOHNNY) [66]>  *arch-enemy* USELESS-OLD-JOHNNY [66]> (pudding-eater 'george-clooney) (WHY YOU EAT MY PUDDING STRANGER ?)  | cs | 
cond 내부에서는 서로 다른 브랜치를 처리하기 위해 괄호를 이용한다. 그리고 괄호 내의 첫 번째 표현식이 해당 분기를 실행시키는 조건을 포함하고 있다. 위의 예에서, 각각의 경우에 이름별로 서로 다른 분기를 가지고 있는 것을 볼 수 있다. 하나는 Henry, 다른 하나는 Johnny, 그리고 마지막은 나머지 모두를 위한 경우이다.
제공된 사람의 이름을 비교하기 위해 eq 함수를 사용한다. 모든 조건은 위에서 아래로 비교된다. 마지막에 나오는 t (for true) 라는 조건은 최소한 맨 마지막 라인이라도 실행되도록 하기 위한 조치이다.
when 과 unless 를 가지고, 암묵적인 progn이 존재하므로 트리거된 분기는 하나 이상의 명령을 가질 수 있다. 위의 경우첫 2개의 분기는 반환값 뿐만 아니라 추가적인 *arch-enemy* 변수를 설정한다.
Branching with case
그럼 마지막으로 case 에 대해서 살펴보자. eq 함수를 사용하고, case를 통해 값을 비교한다. case 를 사용하면 앞의 예제는 다음과 같이 된다.
1 2 3 4 5 6  | [70]> (defun pudding-eater (person)           (case person               ((henry) (setf *arch-enemy* 'stupid-lisp-alien) '(curse you lisp alien – you ate my pudding))              ((johnny) (setf *arch-enemy* 'useless-old-johnny) '(i hope you choked on my pudding johnny))           (otherwise '(why you eat my pudding stranger ?)))) PUDDING-EATER  | cs | 
각각의 case 문에 다루는 사람의 이름이 명시되기 때문에 앞의 코드보다 훨씬 더 보기 편해졌다. 다만, case는 비교를 위해 eq를 사용하기 때문에 심볼에 대해서만 사용되는 점에 주의할 필요가 있다.