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를 사용하기 때문에 심볼에 대해서만 사용되는 점에 주의할 필요가 있다.
'프로그래밍 Programming' 카테고리의 다른 글
NLTK (1) - NLTK 및 NLTK 데이터 설치 (0) | 2017.09.25 |
---|---|
주피터 노트북 파이썬3 추가하기 How do I add python3 kernel to jupyter (IPython) (0) | 2017.09.23 |
Getting Started with CLISP (12) - nil 과 () (0) | 2017.09.16 |
Getting Started with CLISP (11) - 중첩리스트 Nested Lists (0) | 2017.09.16 |
Getting Started with CLISP (10) - Car, Cdr, List Function (0) | 2017.09.16 |